0.前言
早前使用gdb调试特别不习惯: 1)没有图形界面IDE(比如Visual Studio)的强大功能:边打断点边代码跟进,退出断点保存,可以随时查看当前变量数据,对stl变量显示友好。2)gdb打印输出的内容有时难以理解,比如gdb只会打印出stl相关容器、复杂的类对象,智能指针的成员数据,而不会做格式化内容输出。比如原生gdb打印输出vector变量:
p v # v为vector变量,初始化为vector<int> v(10, 1),直接print打印输出内容如下
$1 = {
<_Vector_base<int, std::allocator<int> >> = {
_M_impl = {
<allocator<int>> = {
<new_allocator<int>> = {<No data fields>}, <No data fields>},
members of _Vector_base<int, std::allocator<int> >::_Vector_impl:
_M_start = 0x100501ef0,
_M_finish = 0x100501f18,
_M_end_of_storage = 0x100501f18
}
}, <No data fields>}
可以看到,vector成员变量仅有_M_impl, 而_M_impl成员变量又包含_M_start, _M_finish, _M_end_of_storage。不难推测:vector数据位于一块堆内存中,_M_start指向其分配的内存,_M_finish表示有效数据的结束位置,由于vector是动态数据,真正内存分配大小会大于实际使用,因为得考虑后期数据的增长,会预留内存,_M_end_of_storage指向的就是分配内存的结束位置。要想看vector对应的数据内容,需要复杂的gdb语句:
print *(v._M_impl._M_start)@v.size()
发现用好gdb还是需要稍微了解下各种stl容器内存布局,而且stl容器也没有想象中的复杂,难懂主要是stl库用了各种高级模板封装和宏定义,本文以vector的标准库stl实现作为说明,vector还是个比较综合的例子,stl的其他容器比如string、map过程类似。想要配置对应的gdb请移动到《gdb调试配置》
1.stl库位置
stl标准库不同版本差异很大,实现版本有HP STL、PJ STL、 SGI STL(侯捷的《STL源码剖析》分析的版本)等,而且stl库大致分为容器(containers)、迭代器(iterators)、空间配置器(allocator)、配接器(adapters)、算法(algorithms)、仿函数(functors)六个部分。安装了c++编译环境后,会安装上相应的stl库文件。mac系统下,代码#include <vector>
语句包含的vector库下会是以下几种中的一种:
- /usr/include/c++/4.2.1/vector # mac/linux # 系统stl库位置
- /usr/local/Cellar/gcc/6.4.0/include/c++/6.4.0/vector # 安装GNU的gcc自带的stl库
- /Library/Developer/CommandLineTools/usr/include/c++/v1/vector # mac系统下xcode编译clang自带stl库
其中第3种,是clang编译器自带的stl,文件即为定义文件,如果使用clang编译会包含改版本的vector实现,成员变量为__begin_、__end_、__end_cap_,与前面是有差别的,同样gdb打印如下:
$1 = {
<std::__1::__vector_base<int, std::__1::allocato<int> >> = {
<std::__1::__vector_base_common<true>> = {<No data fields>},
members of std::__1::__vector_base<int, std::__1::allocator<int> >:
__begin_ = 0x100300d00,
__end_ = 0x100300d28,
__end_cap_ = {
<std::__1::__libcpp_compressed_pair_imp<int*, std::__1::allocator<int>, 2>> = {
<std: