当我观察到 libxml2 xmlNodePtr 的类型时,它发生了变化
libxml2 xmlNodePtr's type changes when I observe it
昨天我花了一些时间调试一个可爱的 Heisenbug,其中 xmlNodePtr
的类型会在我身上发生变化。此示例显示错误:
#include <iostream>
#include <vector>
#include <string>
#include <memory> // std::unique_ptr
#include <cstdint>
#include <libxml/tree.h>
#include <libxml/parser.h>
struct SomeDataType {
std::vector<std::vector<std::string>> data;
explicit SomeDataType(uint32_t rows_, uint32_t columns_)
: data(rows_)
{
for (uint32_t row = 0; row < rows_; ++row) {
data[row].resize(columns_);
}
}
};
static std::vector<xmlNodePtr> GetChildren(xmlNodePtr node)
{
std::vector<xmlNodePtr> children;
xmlNodePtr child = node->children;
while (child) {
if (child->type == XML_ELEMENT_NODE) {
children.push_back(child);
}
child = child->next;
}
return children;
}
int main() {
std::unique_ptr<xmlDoc, void(*)(xmlDoc*)> document = { xmlParseEntity("libxml2-fail.xml"), xmlFreeDoc };
SomeDataType{ 3, 2 };
xmlNodePtr root = xmlDocGetRootElement(document.get());
for (const xmlNodePtr &child : GetChildren(root)) {
const xmlNodePtr &entry = GetChildren(child)[0]; // Problem here...
std::cout << "Expected " << XML_ELEMENT_NODE << " but was " << entry->type << std::endl;
std::cout << entry->name << std::endl;
}
}
编译:
g++ -g -std=c++14 -Wall -Wextra -pedantic -I/usr/include/libxml2 libxml2-fail.cpp -lxml2 -o fail.out
xml 文件:
<?xml version="1.0" encoding="utf-8"?>
<data>
<tag>
<subtag>1</subtag>
</tag>
</data>
运行 给我以下输出:
Expected 1 but was 17
单步执行 gdb,一切正常,直到我们到达行 const xmlNodePtr & = ...
。它没有 XML_ELEMENT_NODE
类型,而是 XML_ENTITY_DECL
类型。但是,如果我 运行 以下命令,引用 xmlNodePtr
会变成我期望的类型:
48 const xmlNodePtr &entry = GetChildren(child)[0];
(gdb) n
49 std::cout << "Expected " << XML_ELEMENT_NODE << " but was " << entry->type << std::endl;
(gdb) p *entry
= {_private = 0x0, type = XML_ENTITY_DECL, name = 0x0, children = 0xb7e67d7c <std::string::_Rep::_S_empty_rep_storage+12>, last = 0x0, parent = 0x69, next = 0x0, prev = 0x9, doc = 0x0, ns = 0x805edb8, content = 0x805edb8 "", properties = 0x0, nsDef = 0x0, psvi = 0x0, line = 60648, extra = 2053}
(gdb) p *child
= {_private = 0x0, type = XML_ELEMENT_NODE, name = 0x805ee98 "tag", children = 0x805eea8, last = 0x805ef98, parent = 0x805edb8, next = 0x805efe8, prev = 0x805ee08, doc = 0x805ece8, ns = 0x0, content = 0x0, properties = 0x0, nsDef = 0x0, psvi = 0x0, line = 3, extra = 0}
(gdb) p GetChildren(child)
= std::vector of length 1, capacity 1 = {0x805eef8}
(gdb) p *entry
= {_private = 0x0, type = XML_ELEMENT_NODE, name = 0x805ef38 "subtag", children = 0x805ef48, last = 0x805ef48, parent = 0x805ee58, next = 0x805ef98, prev = 0x805eea8, doc = 0x805ece8, ns = 0x0, content = 0x0, properties = 0x0, nsDef = 0x0, psvi = 0x0, line = 4, extra = 0}
(gdb)
当我像这样遍历一个元素时,我没有遇到问题:
for (const xmlNodePtr &entry : GetChildren(child)) {
...
}
当我没有像这样将 xmlNodePtr
设为 const 引用时,我也没有问题:
xmlNodePtr entry = GetChildren(child)[0];
不过,根据this Whosebug question,这应该不是问题。
SomeDataType
结构是非常必要的;否则我会得到一个段错误,因为 entry
变成了一个空指针。
这个错误是从哪里来的?
当你这样做时:
const xmlNodePtr &entry = GetChildren(child)[0]; // Problem here...
您正在以一种不延长生命周期的方式有效地将引用绑定到临时对象。 operator[]
returns 引用,因此您没有将引用绑定到临时对象 - 您是将引用绑定到引用。但是从 operator[]
返回的引用指的是 GetChildren()
返回的基础 temporary vector
中的一个元素,它在行,给自己留下一个悬空的参考。
但是,当您改为尝试时:
for (const xmlNodePtr &entry : GetChildren(child)) {
这是语法糖:
{
auto&& __range = GetChildren(child); // bind temporary to reference
// lifetime IS extended
auto b = begin(__range);
auto e = end(__range);
for (; b != e; ++b) {
const xmlNodePtr& entry = *b;
// ...
}
}
这里,*b
不是临时的或临时的任何部分 - 它是对容器的引用,其生命周期与 __range
一样长,贯穿整个容器循环。没有悬挂参考。
同样,
xmlNodePtr entry = GetChildren(child)[0];
纯属抄袭,不存在参考问题。
昨天我花了一些时间调试一个可爱的 Heisenbug,其中 xmlNodePtr
的类型会在我身上发生变化。此示例显示错误:
#include <iostream>
#include <vector>
#include <string>
#include <memory> // std::unique_ptr
#include <cstdint>
#include <libxml/tree.h>
#include <libxml/parser.h>
struct SomeDataType {
std::vector<std::vector<std::string>> data;
explicit SomeDataType(uint32_t rows_, uint32_t columns_)
: data(rows_)
{
for (uint32_t row = 0; row < rows_; ++row) {
data[row].resize(columns_);
}
}
};
static std::vector<xmlNodePtr> GetChildren(xmlNodePtr node)
{
std::vector<xmlNodePtr> children;
xmlNodePtr child = node->children;
while (child) {
if (child->type == XML_ELEMENT_NODE) {
children.push_back(child);
}
child = child->next;
}
return children;
}
int main() {
std::unique_ptr<xmlDoc, void(*)(xmlDoc*)> document = { xmlParseEntity("libxml2-fail.xml"), xmlFreeDoc };
SomeDataType{ 3, 2 };
xmlNodePtr root = xmlDocGetRootElement(document.get());
for (const xmlNodePtr &child : GetChildren(root)) {
const xmlNodePtr &entry = GetChildren(child)[0]; // Problem here...
std::cout << "Expected " << XML_ELEMENT_NODE << " but was " << entry->type << std::endl;
std::cout << entry->name << std::endl;
}
}
编译:
g++ -g -std=c++14 -Wall -Wextra -pedantic -I/usr/include/libxml2 libxml2-fail.cpp -lxml2 -o fail.out
xml 文件:
<?xml version="1.0" encoding="utf-8"?>
<data>
<tag>
<subtag>1</subtag>
</tag>
</data>
运行 给我以下输出:
Expected 1 but was 17
单步执行 gdb,一切正常,直到我们到达行 const xmlNodePtr & = ...
。它没有 XML_ELEMENT_NODE
类型,而是 XML_ENTITY_DECL
类型。但是,如果我 运行 以下命令,引用 xmlNodePtr
会变成我期望的类型:
48 const xmlNodePtr &entry = GetChildren(child)[0];
(gdb) n
49 std::cout << "Expected " << XML_ELEMENT_NODE << " but was " << entry->type << std::endl;
(gdb) p *entry
= {_private = 0x0, type = XML_ENTITY_DECL, name = 0x0, children = 0xb7e67d7c <std::string::_Rep::_S_empty_rep_storage+12>, last = 0x0, parent = 0x69, next = 0x0, prev = 0x9, doc = 0x0, ns = 0x805edb8, content = 0x805edb8 "", properties = 0x0, nsDef = 0x0, psvi = 0x0, line = 60648, extra = 2053}
(gdb) p *child
= {_private = 0x0, type = XML_ELEMENT_NODE, name = 0x805ee98 "tag", children = 0x805eea8, last = 0x805ef98, parent = 0x805edb8, next = 0x805efe8, prev = 0x805ee08, doc = 0x805ece8, ns = 0x0, content = 0x0, properties = 0x0, nsDef = 0x0, psvi = 0x0, line = 3, extra = 0}
(gdb) p GetChildren(child)
= std::vector of length 1, capacity 1 = {0x805eef8}
(gdb) p *entry
= {_private = 0x0, type = XML_ELEMENT_NODE, name = 0x805ef38 "subtag", children = 0x805ef48, last = 0x805ef48, parent = 0x805ee58, next = 0x805ef98, prev = 0x805eea8, doc = 0x805ece8, ns = 0x0, content = 0x0, properties = 0x0, nsDef = 0x0, psvi = 0x0, line = 4, extra = 0}
(gdb)
当我像这样遍历一个元素时,我没有遇到问题:
for (const xmlNodePtr &entry : GetChildren(child)) {
...
}
当我没有像这样将 xmlNodePtr
设为 const 引用时,我也没有问题:
xmlNodePtr entry = GetChildren(child)[0];
不过,根据this Whosebug question,这应该不是问题。
SomeDataType
结构是非常必要的;否则我会得到一个段错误,因为 entry
变成了一个空指针。
这个错误是从哪里来的?
当你这样做时:
const xmlNodePtr &entry = GetChildren(child)[0]; // Problem here...
您正在以一种不延长生命周期的方式有效地将引用绑定到临时对象。 operator[]
returns 引用,因此您没有将引用绑定到临时对象 - 您是将引用绑定到引用。但是从 operator[]
返回的引用指的是 GetChildren()
返回的基础 temporary vector
中的一个元素,它在行,给自己留下一个悬空的参考。
但是,当您改为尝试时:
for (const xmlNodePtr &entry : GetChildren(child)) {
这是语法糖:
{
auto&& __range = GetChildren(child); // bind temporary to reference
// lifetime IS extended
auto b = begin(__range);
auto e = end(__range);
for (; b != e; ++b) {
const xmlNodePtr& entry = *b;
// ...
}
}
这里,*b
不是临时的或临时的任何部分 - 它是对容器的引用,其生命周期与 __range
一样长,贯穿整个容器循环。没有悬挂参考。
同样,
xmlNodePtr entry = GetChildren(child)[0];
纯属抄袭,不存在参考问题。