std::list<>用于遍历的奇怪内部布局是什么?
当我使用像这样的python助手时:
def invokeCore(self, headlines, data, valueType, valueHandler):
valueType = gdb.lookup_type (valueType)
head = data['_M_impl']['_M_node'].address
current = data['_M_impl']['_M_node']['_M_next']
no = 0
while current != head:
realData = current.cast(valueType)
if realData.address != 0:
valueHandler(no, realData)
else:
print('### PrintGenericList::invokeCore(): failed downcast for list node')
current = current['_M_next']
no += 1
return ''
然后回调'valueHandler‘打印的第一个条目为空,最后一个条目丢失。
根据这里的另一个参考:https://github.com/johnathan79717/gdb-stl-view/blob/master/.gdbinit
有用
p **($arg1**)($current + 1)
但这是什么($current + 1)呢?
初始化$current时
set $current = $arg0._M_impl._M_node._M_next
发布于 2022-07-06 11:25:03
首先,链接到的宏不再可靠。我使用std::list和GCC 9.3.1进行了测试,plist
不能正常工作,所以不要太依赖它们来提供指导。
然而,这些宏试图做的是绕过用于在列表中存储数据的类型。
考虑一下,这个GDB会话中my_list
是一个包含3个元素的std::list<int>
:
(gdb) p/r my_list
$1 = {
<std::__cxx11::_List_base<int, std::allocator<int> >> = {
_M_impl = {
<std::allocator<std::_List_node<int> >> = {
<__gnu_cxx::new_allocator<std::_List_node<int> >> = {<No data fields>}, <No data fields>},
members of std::__cxx11::_List_base<int, std::allocator<int> >::_List_impl:
_M_node = {
<std::__detail::_List_node_base> = {
_M_next = 0x416eb0,
_M_prev = 0x416ef0
},
members of std::__detail::_List_node_header:
_M_size = 3
}
}
}, <No data fields>}
(gdb) ptype my_list._M_impl._M_node
type = struct std::__detail::_List_node_header : public std::__detail::_List_node_base {
public:
std::size_t _M_size;
_List_node_header(void);
_List_node_header(std::__detail::_List_node_header &&);
void _M_move_nodes(std::__detail::_List_node_header &&);
void _M_init(void);
private:
std::__detail::_List_node_base * _M_base(void);
}
(gdb) ptype my_list._M_impl._M_node._M_next
type = struct std::__detail::_List_node_base {
std::__detail::_List_node_base *_M_next;
std::__detail::_List_node_base *_M_prev;
public:
static void swap(std::__detail::_List_node_base &, std::__detail::_List_node_base &);
void _M_transfer(std::__detail::_List_node_base *, std::__detail::_List_node_base *);
void _M_reverse(void);
void _M_hook(std::__detail::_List_node_base *);
void _M_unhook(void);
} *
(gdb) p *my_list._M_impl._M_node._M_next
$7 = {
_M_next = 0x416ed0,
_M_prev = 0x7fffffffad40
}
因此,_M_node
是一个_List_node_header
,它链接到一个_List_node_base
,它链接到进一步的_List_node_base
,直到我们最终链接回_M_node
,这表明列表的结束。
您可以在代码中使用while current != head:
循环来处理这个问题。
那么问题是,数据存储在哪里呢?原来,_M_next
指出的东西也不是_List_node_base
对象,而是std::__cxx11::list<int, std::allocator<int> >::_Node
,它看起来如下所示:
(gdb) ptype std::__cxx11::list<int, std::allocator<int> >::_Node
type = struct std::_List_node<int> [with _Tp = int] : public std::__detail::_List_node_base {
__gnu_cxx::__aligned_membuf<_Tp> _M_storage;
public:
_Tp * _M_valptr(void);
const _Tp * _M_valptr(void) const;
}
注意,这是_List_node_base
的一个子类。因此,如果我们加入一个适当的演员阵容,我们可以看到以下内容:
(gdb) p *(std::__cxx11::list<int, std::allocator<int> >::_Node *) my_list._M_impl._M_node._M_next
$2 = {
<std::__detail::_List_node_base> = {
_M_next = 0x416ed0,
_M_prev = 0x7fffffffad40
},
members of std::_List_node<int>:
_M_storage = {
_M_storage = "\003\000\000"
}
}
现在,当您链接的GDB宏说$current + 1
$current
是一个_M_next
指针,并且是_List_node_base
类型时,情况变得更加清楚了。+ 1
将跳过一个跨越_M_next
和_M_prev
指针的_List_node_base
的大小,然后从子类中查看_M_storage
。
然后,**($arg1**)($current + 1)
只将_M_storage
当作指向列表元素类型指针的指针,然后尝试取消引用并获取列表值。这一点不再适用于我,因为int
值直接存储在_M_storage
缓冲区中,而不是将指向对象的指针放在缓冲区中,在这里GCC似乎比以前更理想,但这意味着您需要更聪明地从列表中获取值。
这就是为什么我一开始就说那些宏是个陷阱。上述任何一个都不是真正可靠的。我注意到,宏中的评论谈到GCC 4.x,所以这可能在当时看起来并不那么糟糕,但现在真的不太好了。
那么,接下来你该怎么办呢?那么,对于打印std::清单,我建议尝试使用GCC现有的漂亮打印机。大多数发行版应该有一个包含这些包的包,GDB应该自动加载它们。有了这些措施之后:
(gdb) p my_list
$14 = std::__cxx11::list = {
[0] = 3,
[1] = 4,
[2] = 5
}
但是,如果您真的想学习如何遍历STL数据结构进行打印,那么我建议使用你读过GCC为他们漂亮的打印机提供的资料,这些应该会提供更可靠的知识来源。
https://stackoverflow.com/questions/72872113
复制相似问题