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中存储的是对象的指针,查看该命令的帮助信息:

点击(此处)折叠或打开

  1. pvector
  2.         Prints std::vector<T> information.
  3.         Syntax: pvector <vector> <idx1> <idx2>
  4.         Note: idx, idx1 and idx2 must be in acceptable range [0..<vector>.size()-1].
  5.         Examples:
  6.         pvector v - Prints vector content, size, capacity and T typedef
  7.         pvector v 0 - Prints element[idx] from vector
  8.         pvector v 1 2 - Prints elements in range [idx1..idx2] from vector

该命令只有3种使用方式:查看vector信息,查看某个元素的值,查看某个区间的值。


此次,通过前期问题初步判断,需要查看该对象中的string值。为了下文描述方便,大致举例数据:

点击(此处)折叠或打开

  1. class Student
  2. {
  3. public:
  4.     int sno;
  5.     string name;
  6. };
  7. 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里面嵌入了一个结构体:

点击(此处)折叠或打开

  1. template<typename _Tp, typename _Alloc>
  2.     struct _Vector_base
  3.     {
  4.       typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template
  5.         rebind<_Tp>::other _Tp_alloc_type;
  6.       typedef typename __gnu_cxx::__alloc_traits<_Tp_alloc_type>::pointer
  7.            pointer;

  8.       struct _Vector_impl
  9.       : public _Tp_alloc_type
  10.       {
  11.         pointer _M_start;
  12.         pointer _M_finish;
  13.         pointer _M_end_of_storage;

从下面的代码来看,主要是使用特定类型,方便内存管理,以及使用移动语意。此处就不展开研究,我们直接看begin函数: 

点击(此处)折叠或打开

  1. const_iterator
  2.  begin() const _GLIBCXX_NOEXCEPT
  3.  { return const_iterator(this->_M_impl._M_start); }


 因此打印第一个变量,应该输入

点击(此处)折叠或打开

  1. P stus._M_impl._M_start

贴一下GDB结果:

点击(此处)折叠或打开

  1. (gdb) P this->manager._M_impl._M_start        --打印第一个变量地址
  2. $21 = (std::_Vector_base<Student*, std::allocator<Student*> >::pointer) 0x619e20
  3. (gdb) P *this->manager._M_impl._M_start        --打印第一个变量地址
  4. $22 = (Student *) 0x7fffffffd1f0    
  5. (gdb) P *(Student*)0x7fffffffd1f0    --打印第一个变量中存储的值

打印容器后序存储数据:

点击(此处)折叠或打开

  1. (gdb) x/16bx 0x619e20            --[1]
  2. 0x619e20: 0xf0 0xd1 0xff 0xff 0xff 0x7f 0x00 0x00
  3. 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::pointer


然后再打印该处的值
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

09-22 05:19