VS中是可以方便的查看容器中的变量的,GDB如何打印STL中容器的变量呢?
GDB官方提供了一个符号文件,如果是简单的打印变量信息,可以下载到项目中加载进来使用。
符号文件地址:https://sourceware.org/gdb/wiki/STLSupport 下载gdb-stl-views
使用GDB调试代码时,使用source命令加载该符号文件(如:source ~/stl-views-1.0.3.gdb
)。
此次使用的是vector. 使用pvetor命令可以打印出对应的序列:pvector v 0 该命令就可以打印vd的第一个元素的值。
但是此次由于某些原因,在vector中存储的是对象的指针,查看该命令的帮助信息:
点击(此处)折叠或打开
- pvector
- Prints std::vector<T> information.
- Syntax: pvector <vector> <idx1> <idx2>
- Note: idx, idx1 and idx2 must be in acceptable range [0..<vector>.size()-1].
- Examples:
- pvector v - Prints vector content, size, capacity and T typedef
- pvector v 0 - Prints element[idx] from vector
- pvector v 1 2 - Prints elements in range [idx1..idx2] from vector
该命令只有3种使用方式:查看vector信息,查看某个元素的值,查看某个区间的值。
此次,通过前期问题初步判断,需要查看该对象中的string值。为了下文描述方便,大致举例数据:
点击(此处)折叠或打开
- class Student
- {
- public:
- int sno;
- string name;
- };
- vector<Student*> stus;
如何打印某个元素指向的地址中的对象呢?
我们知道,vector 是一个数组,存储了数组的首尾指针,以及分配的内存大小的指针。如果要打印第i个元素所指向的对象,先用首指针+ i * sizeof(指针), 这就是迭代器的地址; 再得到它存储的地址p,该地址就是我们要找的对象的首地址。
我们先打印第一个元素来验证方法的正确性:
P stus.start
提示没有该变量!该代码使用的是《STL 源码剖析》中下载的代码,好像STL是有很多厂商哈。但是STL大多是采用的模版,因此源码在机器上是有的,也很容易找到(顺着库的包含目录,不多说, 比如:/usr/include/c++/4.8.5/bits/stl_vector.h)
确实,该版本的vector里面嵌入了一个结构体:
点击(此处)折叠或打开
- template<typename _Tp, typename _Alloc>
- struct _Vector_base
- {
- typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template
- rebind<_Tp>::other _Tp_alloc_type;
- typedef typename __gnu_cxx::__alloc_traits<_Tp_alloc_type>::pointer
- pointer;
- struct _Vector_impl
- : public _Tp_alloc_type
- {
- pointer _M_start;
- pointer _M_finish;
- pointer _M_end_of_storage;
从下面的代码来看,主要是使用特定类型,方便内存管理,以及使用移动语意。此处就不展开研究,我们直接看begin函数:
点击(此处)折叠或打开
- const_iterator
- begin() const _GLIBCXX_NOEXCEPT
- { return const_iterator(this->_M_impl._M_start); }
因此打印第一个变量,应该输入
点击(此处)折叠或打开
- P stus._M_impl._M_start
贴一下GDB结果:
点击(此处)折叠或打开
- (gdb) P this->manager._M_impl._M_start --打印第一个变量地址
- $21 = (std::_Vector_base<Student*, std::allocator<Student*> >::pointer) 0x619e20
- (gdb) P *this->manager._M_impl._M_start --打印第一个变量地址
- $22 = (Student *) 0x7fffffffd1f0
- (gdb) P *(Student*)0x7fffffffd1f0 --打印第一个变量中存储的值
打印容器后序存储数据:
点击(此处)折叠或打开
- (gdb) x/16bx 0x619e20 --[1]
- 0x619e20: 0xf0 0xd1 0xff 0xff 0xff 0x7f 0x00 0x00
- 0x619e28: 0x60 0xab 0x61 0x00 0x00 0x00 0x00 0x00
得到 第二个变量内存地址:0x61ab60
打印该数据:
(gdb) P *(Student*) 0x61ab60
至此,就得到了打印数据的。查看数据,可以明显看到,name 的指针是一个栈指针。插入数据时,string 只拷贝了指针(=),而创建stus使用栈变量。后续问题就不再展开。
总结:
1.打印出vector首地址X:P this->manager._M_impl._M_start
2.打印首地址开始N个内存数据:x/Ngx X
3.将第N个地址Y转化成需要的类型:*(Student*) Y
拓展:
在写总结的时候,发现也没有那么复杂,只是当时在其中:
直接根据首地址打印 P **(this->manager._M_impl._M_start + 1)
这种方法当时是想到的,只是后面的+1使用了内存地址(+8)导致没有打印出来。
还有一种跟简单的方法,不需要知道vector的变量:
(gdb) pvector this->manager 0 1
elem[0]: $31 = (Student *) 0x7fffffffd1f0
elem[1]: $32 = (Student *) 0x61ab60
Vector size = 2
Vector capacity = 2
Element type = std::_Vector_base
然后再打印该处的值
P *(Student *)0x61ab60
这种方法当时也是想到的,但是使用的是pvector this->manager 1,一方面,只有一个数据,只看到下面的容器;二是地址太短,以为又错。
知识这种东西,还真是不知道的时候,什么搞都不对,搞出来了真不要意思说以前那么SB。
[1] 打印内存命令:x/ ——n 为要打印多少内存单元(大小由U确定),u 表示每次显示的字节数:GDB 默认4字节,b-1B, h-2B, w-4B, g-8B.f 表示格式,打印字符串用s.
修改一下命令,打印:
x/2gx 0x619e20
0x619e20: 0x00007fffffffd1f0 0x000000000061ab60