Add New Notes

This commit is contained in:
geekard
2012-08-08 14:26:04 +08:00
commit 5ef7c20052
2374 changed files with 276187 additions and 0 deletions

View File

@@ -0,0 +1,241 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-11-11T15:59:35+08:00
====== 手把手教你玩转GDB ======
Created Friday 11 November 2011
http://www.wuzesheng.com/?p=1327
写在最前面GDB是unix相关操作系统中C/C++程序开发必不可少的工具它的功能之强大是其它调试器所不能匹敌的。但是现实的工作中有很多开发者因为GDB本身入门门槛比较高而被拒之门与如此强大的失之交臂。笔者在近两年的C/C++开发工作中对GDB本身的有一点研究在这里总结出一系列《手把手教你玩转GDB》的文章一方面权当是对自己经验的一个总结一方面也是真的想能够对刚接触GDB的开发者朋友带去一些帮助让更多的人来使用如此强大的工具。今天推出第一篇
**第一部分 牛刀小试启动GDB开始调试**
1. 启动GDB开始调试被调试的程序只有在run命令后才执行
1gdb program ///最常用的用gdb启动程序开始调试的方式
2gdb program core ///用gdb查看core dump文件跟踪程序core的原因
3gdb program pid ///用gdb调试已经开始运行的程序指定pid即可
2. 应用程序带命令行参数的情况,可以通过下面两种方法启动:
1启动GDB的时候加上- **args**选项然后把应用程序和其命令行参数带在后面具体格式为gdb -args program args
2先按1中讲的方法启动GDB, 然后再执行run命令的时候后面加上参数
3. 退出GDB
1End-of-File(Ctrl+d)
2quit或者q
4. 在GDB调试程序的时候执行shell命令
1shell command args也可以先执行shell命令GDB会退出到当前shell, 执行完command后然后在shell中执行exit命令便可回到GDB
2make make-args等同于shell make make-args
5. 在GDB中获取帮助
1在GDB中执行help命令可以得到如图1所示的帮助信息
{{~/sync/notes/zim/Utils/gdb/手把手教你玩转GDB/gdb_help.jpg}}
图1 GDB帮助菜单
由图1可以看出GDB中的命令可以分为八类别名(aliases)、断点(breakpoints)、数据(data)、文件(files)、内部(internals)、隐含(obscure)、运行(running)、栈(stack)、状态(status)、支持(support)、跟踪点(tracepoints)和用户自定义(user-defined)。
2help class-name查看该类型的命令的详细帮助说明
3help all列出所有命令的详细说明
4help command列出命令command的详细说明
5apropos word列出与word这个词相关的命令的详细说明
6complete args列出所有以args为前辍的命令
6. info和show
1info用来获取和被调试的应用程序相关的信息
2show用来获取GDB本身设置相关的一些信息
**手把手教你玩转GDB(二)——Breakpoint, Watchpoint和Catchpoint**
本文是《手把手教你玩转GDB》系列的第二篇主要内容是用GDB调试程序中比较常用到的断点breakpoint、监视点watchpoint和捕捉点catchpoint。虽然说这三类point的功能是不一样的但它们的用法却极为相似。因此本文将以断breakpoint为例进行详细的介绍关于watchpoint和catchpoint的介绍就相对比较粗略相信读者朋友如果能够理解breakpoint的部分那么便可以触类旁通学会watchpoint和catchpoint的用法。
1. Breakpoint: 作用是让程序执行到某个特定的地方停止运行
1设置breakpoint
a. break function: 在函数funtion入口处设置breakpoint
b. break +offset: 在程序当前停止的行向前offset行处设置breakpoint
c. break offset: 在程序当前停止的行向衙offset行处设置breakpoint
d. break linenum: 在当前源文件的第linenum行处设置breakpoint
e. break filename:linenum: 在名为filename的源文件的第linenum行处设置breakpoint
f. break filename:function: 在名为filename的源文件中的function函数入口处设置breakpoint
g. break__ *__address: 在程序的地址address处设置breakpoint
h. break … if cond: …代表上面讲到的任意一个可能的参数在某处设置一个breakpoint, 但且仅但cond为true时程序停下来
i. tbreak args: 设置一个**只停止一次**的breakpoints, args与break命令的一样。这样的breakpoint当第一次停下来后就会被自己删除
k. **rbreak** regex: 在所有符合正则表达式regex的函数处设置breakpoint
2info breakpoints [n]
查看第n个breakpoints的相关信息如果省略了n则显示所有breakpoints的相关信息
3pending breakpoints:
是指设置在程序开始调试后加载的动态库中的位置处的breakpoints
a. set breakpoint pending auto: GDB缺省设置询问用户是否要设置pending breakpoint
b. set breakpoint pending on: GDB当前不能识别的breakpoint自动成为pending breakpoint
c. set breakpoint pending off: GDB当前不能识别某个breakpoint时直接报错
d. show breakpoint pending: 查看GDB关于pending breakpoint的设置的行为(auto, on, off)
4breakpoints的删除
a. clear: 清除当前stack frame中下一条指令之后的所有breakpoints
b. clear function & clear filename:function: 清除函数function**入口**处的breakpoints
c. clear linenum & clear filename:linenum: 清除第linenum行处的breakpoints
d. delete [breakpoints] [range…]: 删除由range指定的范围内的breakpointsrange范围是指breakpoint的序列号的范围
5breakpoints的禁用、启用:
a. disable [breakpoints] [range…]: 禁用由range指定的范围内的breakpoints
b. enable [breakpoints] [range…]: 启用由range指定的范围内的breakpoints
c. enable [breakpoints] once [range…]: 只启用一次由range指定的范围内的breakpoints等程序停下来后自动设为禁用
d. enable [breakpoints] delete [range…]: 启用range指定的范围内的breakpoints等程序停下来后这些breakpoints自动被删除
6条件breakpoints相关命令
a. 设置条件breakpoints可以通过break … if cond来设置也可以通过condition bnum expression来设置在这里首先要通过1中介绍的命令设置好breakpoints然后用condition命令来指定某breakpoint的条件该breakpoint由bnum指定条件由expression指定
b. condition bnum: 取消第bnum个breakpoint的条件
c. ignore bnum count: 第bnum个breakpoint跳过count次后开始生效
7指定程序在某个breakpoint处停下来后执行一串命令
a. 格式commands [bnum]
… command-list …
end
b. 用途指定程序在第bnum个breakpoint处停下来后执行由command-list指定的命令串如果没有指定bnum则对最后一个breakpoint生效
c. 取消命令列表: commands [bnum]
end
d. 例子:
break foo if x>0
commands
silent
printf “x is %d\n”,x
continue
end
上面的例子含义当x>0时在foo函数处停下来然后打印出x的值然后继续运行程序
2. Watchpoint: 它的作用是让程序在某个表达式的值**发生变化**的时候停止运行,达到‘监视’该表达式的目的
1设置watchpoints:
a. watch expr: 设置**写**watchpoint当应用程序写expr, 修改其值时,程序停止运行
b. rwatch expr: 设置**读**watchpoint当应用程序读表达式expr时程序停止运行
c. awatch expr: 设置读写watchpoint, 当应用程序读或者写表达式expr时程序都会停止运行
2info watchpoints:
查看当前调试的程序中设置的watchpoints相关信息
3watchpoints和breakpoints很相像都有enable/disabe/delete等操作使用方法也与breakpoints的类似
3. Catchpoint: 的作用是让程序在发生某种事件的时候停止运行比如C++中发生异常事件,加载动态库事件
1设置catchpoints:
a. catch event: 当事件event发生的时候程序停止运行这里event的取值有
1throw: C++抛出异常
2catch: C++捕捉到异常
3exec: exec被调用
4fork: fork被调用
5vfork: vfork被调用
6load: 加载动态库
7load libname: 加载名为libname的动态库
8unload: 卸载动态库
9unload libname: 卸载名为libname的动态库
10syscall [args]: 调用系统调用args可以指定系统调用号或者系统名称
b. tcatch event: 设置只停一次的catchpoint第一次生效后该catchpoint被自动删除
2catchpoints和breakpoints很相像都有enable/disabe/delete等操作使用方法也与breakpoints的类似
**手把手教你玩转GDB(三)——常用命令**
1.attach process-id/detach
1attach process-id: 在GDB状态下开始调试一个正在运行的进程其进程ID为process-id
2detach: 停止调试当前正在调试有进程与attach配对试用
2.__kill__
1基本功能杀掉当前GDB正在调试的应用程序所对应的**子进程**
2如果想不退出GDB而对当前正在调试的应用程序重新编译、链接可以在GDB中执行kill杀掉子进程等编译、链接完后再重新执行runGDB便可加载新的可执行程序启动调试
3.多线程程序调试相关:
1thread threadno切换当前线程到由threadno指定的线程
2info threads查看GDB当前调试的程序的各个线程的相关信息
3thread __apply __[threadno] [all] args对指定或所有的线程执行由args指定的命令
4.多进程程序调试相关(fork/vfork)
1缺省方式fork/vfork之后GDB仍然调试__父进程__与子进程不相关
2__set follow-fork-mode mode__设置GDB行为mode为parent时与缺省情况一样mode为child时fork/vfork之后GDB进入子进程调试与父进程不再相关
3show follow-fork-mode查看当前GDB多进程跟踪模式的设置
5.step & stepi
1step [count]: 如果没有指定count, 则继续执行程序直到到达与当前源文件不同的源文件中时停止如果指定了count, 则重复行上面的过程count次
2stepi [count]: 如果没有指定count, 继续执行下一条__机器指令__然后停止如果指定了count则重复上面的过程count次
3step比较常见的应用场景在函数func被调用的某行代码处设置断点等程序在断点处停下来后可以用step命令进入该函数的实现中但前提是该函数编译的时候把调试信息也编译进去了否则step会跳过该函数。
6.next & nexti
1next [count]: 如果没有指定count, 单步执行下一行程序如果指定了count单步执行接下来的count行程序
2nexti [count]: 如果没有指定count, 单步执行下一条指令如果指定了count, 音频执行接下来的count条执行
3stepi和nexti的区别nexti在执行某机器指令时如果该指令是函数调用那么程序执行直到该函数调用结束时才停止。
7.continue [ignore-count] 唤醒程序继续运行至到遇到下一个断点或者程序结束。如果指定ignore-count那么程序在接下来的运行中忽略ignore-count次断点。
8. __finish & return__
1finish: 继续执行程序,直到当前**被调用的函数结束**,如果该函数有返回值,把返回值也打印到控制台
2return [expression]: **中止**当前函数的调用如果指定了expression把expresson值当做当前函数的返回值如果没有直接结束当前函数调用
9.信号的处理
1info signals & info handle打印所有的信号相关的信息以及GDB缺省的处理方式
2handle signal action: 设置GDB对具体某个信号的处理方式。signal可以为信号整数值也可以为SIGSEGV这样的符号。action的取值有
a. stop和nostop: nostop表示当GDB收到指定的信号不会应用停止程序的执行只会打印出一条收到信号的消息因此nostop也暗含了下面的print; 而stop则表示当GDB收到指定的信号停止应用程序的执行。
b. print和noprint: print表示如果收到指定的信号打印出一条信息; noprint与print表示相反的意思
c. pass和nopasspass表示如果收到指定的信号把该信号通知给应用程序; nopass表示与pass相反的意思
d. ignore和noignore: ignore与nopass同义同理noignore与pass同义
**手把手教你玩转GDB(四)——–函数调用栈(call stack)探密**
本文是GDB系列的第四篇感兴趣的朋友可以阅读本系列的前三篇。本文的主要内容是讲如何用GDB来查看C/C++程序中函数调用栈(call stack)的相关信息,通过介绍一些相关的命令及其用法,让读者朋友能够循序渐进了解调用栈的各个方面,更好的驾驭程序。下面开始今天的内容。
我们知道通常一个程序的运行不外乎是A函数调用BB函数调用C等等等所有的调用都完成后整个程序的运行也就ok了。在这个过程中每当有新的函数调用系统都会把该函数的一些信息包括函数的参数以及一些寄存器的值等保存到__调用栈__(call stack)上。等该函数运行完成后,这些信息再从调用栈上弹出(pop)。如下图所示,是一个完整的调用栈:
{{~/sync/notes/zim/Utils/gdb/手把手教你玩转GDB/callstack.jpg}}
在上图中,整体叫做调用栈(call stack)每一行叫做__一桢__(frame)。我们来看看桢信息的组成有哪些:
1桢号调用栈中对桢的一个编号从0开始依次增大
2PCProgram counter寄存器指向当前桢中下一条要执行的指令的地址
3函数名当前桢中被调用的函数的名字
4参数及传入的值当前桢中被调用的函数在调用时传入的参数及其值
5源码位置当前桢执行到的源码位置格式为 file:linenum
这里还有一点需要说明不知道细心的读者朋友有没有发现foo那一桢没有PC的地址GDB通过这样来标示该桢是当前__正在执行__到的桢(即当前帧),因此我们通过看调用栈的信息,便可得知程序执行到哪里了。
读者朋友有没有觉得原来函数调用的过程还有这么多信息可以知道啊下面我就开始介绍一些GDB命令通过这些命令你便可以查看到上面介绍的这些信息甚至更加详细的信息。
1. 查看调用栈信息:(具体信息的内容,与上面第二部分中介绍的相同)
1backtrace: 显示程序的调用栈信息可以用bt缩写
2backtrace n: 显示程序的调用栈信息只显示栈顶n桢(frame)
3backtrace -n: 显示程序的调用栈信息只显示栈底部n桢(frame)
4set backtrace limit n: 设置bt显示的最大桢层数
5where, info stack都是bt的__别名__功能一样
2. 查看桢信息:
1frame n: 查看第n桢的信息 frame可以用f缩写
2frame addr: 查看pc地址为addr的桢的相关信息
3up n: 查看当前桢上面第n桢的信息
4down n: 查看当前桢下面第n桢的信息
3. 查看更加详细的信息:
1__info frame__、info frame n或者info frame addr
查看指定桢的详细信息,关于详细信息的内容,这里有必要做一个介绍,如下图所示:
{{~/sync/notes/zim/Utils/gdb/手把手教你玩转GDB/frame.jpg}}
上图中显示的信息有:
a. 当前桢的地址: 0xbffff400
b. 当前桢PC: eip = 0×8048516
c. 当前桢函数: bar (test.cpp:16)
d. caller桢的PC: saved eip 0×8048535
e. caller桢的地址: called by frame at 0xbffff420
f. callee桢的地址: caller of frame at 0xbffff3e0
g. 源代码所用的程序的语言(c/c++): source language c++
h. 当前桢的参数的地址及值: Arglist at 0xbffff3f8, args: name=0×8048621 “jessie”, myname=0x804861c “jack”
i. 当前相中局部变量的地址Locals at 0xbffff3f8, Previous frames sp is 0xbffff400
k. 当前桢中存储的寄存器: Saved registers: ebp at 0xbffff3f8, eip at 0xbffff3fc
2info args查看当前桢中的参数
3info locals查看当前桢中的局部变量
4info catch查看当前桢中的异常处理器exception handlers

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

View File

@@ -0,0 +1,218 @@
Content-Type: text/x-zim-wiki
Wiki-Format: zim 0.4
Creation-Date: 2011-11-11T16:24:17+08:00
====== 手把手教你调试STL容器 ======
Created Friday 11 November 2011
本文固定链接http://www.wuzesheng.com/?p=1686
**手把手教你调试STL容器(上)**
众所周知,调试(Debugging)是每个程序员所要必备的基本的技术素养尤其是对C/C++的程序员来说。对于在linux下用C/C++开发的朋友相信对GDB不会陌生当程序有bug或者是出现core dump的时候GDB是我们最好的朋友。STL是C++相较于C而言增加的非常强有力的工具它从某种程度上把C/C++程序员从繁琐的基本数据结构中解放了出来。不过STL虽然用起来十分方便但是用GDB调试过C/C++程序的朋友都有这样痛苦的经历在GDB状态下要知道某个STL对象比如容器中的数据内容并不是那么直接、简单。本文的主要内容就是介绍STL中大家比较常用到的容器的基本组成帮助大家能够在调试的时候更好的驾驭它们。
**1. string**
string是STL中最为常用的类型它是模板类basic_string用char类型特化后的结果下面我们来看一下string类型的基本组成
typedef basic_string<char> string;
struct _Rep_base
{
size_type _M_length;
size_type _M_capacity;
_Atomic_word _M_refcount;
};
struct _Alloc_hider
{
_CharT* _M_p;
};
mutable _Alloc_hider _M_dataplus;
_M_data() const { return _M_dataplus._M_p; }
_Rep* _M_rep() const { return &((reinterpret_cast<_rep *> (_M_data()))[-1]); }
从上面来看string只有一个成员_M_dataplus。但是这里需要注意的是string类在实现的时候用了比较巧的方法在_M_dataplus._M_p中保存了用户的数据在_M_dataplus._M_p的第一个元素前面的位置保存了string类本身所需要的一些信息rep。这样做的好处一方面不增加string类的额外开销另一方面可以保证用户在调试器(GDB)中用_M_dataplus._M_p查看数据内容的时候不受干扰。用户可以通过reinterpret_cast<_rep *> (_M_data()))[-1])来查看rep相关的信息也可以调用_M_rep()函数来查看。
2. vector
vector同样也是stl中最为常用类型下面我们来看一下vector类型的基本组成:
struct _Vector_impl
{
_Tp* _M_start;
_Tp* _M_finish;
_Tp* _M_end_of_storage;
};
_Vector_impl _M_impl;
vector本身很简单就是一个动态数组。它只有一个数组成员_M_impl用户可以通过_M_impl来查看vector内容的数据成员具体包括动态数组的超始地址_M_impl._M_start结束地址_M_impl._M_end_of_storage数组内容的结束地址_M_impl._M_finish
3. list
list是STL中的双向链表结构也是大家经常用来的下面我们一起来看一下list的基本组成
struct _List_node_base
{
_List_node_base* _M_next;
_List_node_base* _M_prev;
};
template<typename _Tp>
struct _List_node : public _List_node_base
{
_Tp _M_data;
};
struct _List_impl
{
_List_node_base _M_node;
};
_List_impl _M_impl;
通过上面的代码我们可以看出list本身只保留了一个空结点_M_impl._M_node用来标示list的header新加入的第一个结点是直接链到_M_imp._M_node._M_next上的后面的依次类推。我们可以把_M_impl._M_node.m_next强制转型成_List_node类型然后通过_M_data来查看具体的数据内容即((_List_node<T> *)(_M_impl._M_node->M_next))->M_data (T是数据的类型)。
我们知道,string和vector都是线型结构只要知道数据的起始地址和容器大小便可获取到所以的元素内容。但是list是非线型结构这时候就需要iterator来帮忙了下面我们来看下list的iterator的基本组成
template<typename _Tp>
struct _List_iterator
{
_List_node_base* _M_node;
};
从上面的代码我们可以看出list的iterator只是保存了一个list结点的指针有此足矣通过它我们便可以获取到iter里面的数据内容((_List_node<T> *)(iter->_M_node)->M_data (T是数据的类型)
4. deque
deque是STL中提供一个队列结构它同时兼有vector和list的基本特点因此实现要复杂一些下面我们来看一下它的基本组成
template<typename _Tp>
struct _Deque_iterator
{
typedef _Tp** _Map_pointer;
_Tp* _M_cur;
_Tp* _M_first;
_Tp* _M_last;
_Map_pointer _M_node;
};
typedef _Deque_iterator<_tp , _Tp&, _Tp*> iterator;
struct _Deque_impl
: public _Tp_alloc_type
{
_Tp** _M_map;
size_t _M_map_size;
iterator _M_start;
iterator _M_finish;
};
_Deque_impl _M_impl;
从上面的代码及STL的源码我们可以了解到deque是由多块连续buffer通过一个中控的数组_M_map来链在一起实现的。我们可以通过_M_map获取到每一个块的起始地址在每一块内的数据地址都是连续的可以像数组一起取出对应的数据内容。
另外我们还可以通过iterator来访问deque的元素上面的代码中iterator的_M_cur是指向当前元素的指针_M_first是当前块的起始地址_M_last当前块的结束地址。
本篇是〈手把手教你调试STL容器〉的上篇主要介绍了string, vector, list和deque这些基本的容器下篇将介绍map/set/multimap/multiset, hash_map/hash_set/hash_multimap/hash_multiset敬请期待
本文是《手把手教你调试STL容器》系列的下篇阅读本文之前请先阅读上篇《手把手教你调试STL容器(上)》。上篇中主要介绍了STL中string, vector, list, deque这些基本的容器。本篇将介绍由红黑树实现的map/set/multimap/multiset这些容器以及由hashtable实现的hash_map/hash_set/hash_multimap/hash_multiset这些容器。
1. 红黑树Red black tree)
我们知道, STL中的map/set/multimap/multiset都是由红黑树实现的因此我们要了解map/set/multimap/multiset这些容器是如何实现的就首先要了解红黑树的基本组成
enum _Rb_tree_color { _S_red = false, _S_black = true };
struct _Rb_tree_node_base
{
typedef _Rb_tree_node_base* _Base_ptr;
typedef const _Rb_tree_node_base* _Const_Base_ptr;
_Rb_tree_color _M_color;
_Base_ptr _M_parent;
_Base_ptr _M_left;
_Base_ptr _M_right;
};
template< typename _Val>
struct _Rb_tree_node : public _Rb_tree_node_base
{
typedef _Rb_tree_node< _val>* _Link_type;
_Val _M_value_field;
};
template< typename _Key_compare,
bool _Is_pod_comparator = std::__is_pod< _Key_compare>::__value>
struct _Rb_tree_impl : public _Node_allocator
{
_Key_compare _M_key_compare;
_Rb_tree_node_base _M_header;
size_type _M_node_count; // Keeps track of size of tree.
};
_Rb_tree_impl< _compare> _M_impl;
从上面的代码中我们知道STL中红黑树中保存的基本信息包括根结点对象_M_header和所有结点的个数_M_node_count_M_header本身只是一个哨兵功能它里面包含了指向它的左右子树的指针。在真实存放数据的结点中每个结点都是_Rb_tree_node< _val>类型_Val是数据类型用户可以通过_M_value_field得到存放的数据的值。
另外,我们也知道,红黑树本身是非线形结构,因此遍历的过程,还是需要迭代器来帮忙的,下面的红黑树的迭代器的基本结构:
template< typename _Tp>
struct _Rb_tree_iterator
{
typedef _Rb_tree_node_base::_Base_ptr _Base_ptr;
_Base_ptr _M_node;
};
从上面的定义可以看出红黑树的迭代器本身只是保存了一个_Rb_tree_node_base的指针我们只要按实际的数据类型把它转型为_Rb_tree_node类型就可以通过_M_value_field得到数据的值。
在STL中map/set/multimap/multiset都是由红黑树实现的这些容器都是在红黑树的基础上包裹了一层实现了相关功能在这些容器中都有一个这样类似的成员
typedef _Rb_tree< ...> _Rep_type;
_Rep_type _M_t;
从上面的代码我们可以知道我们要想查看某个map/set/multimap/multiset里面存放的数据只需要用_M_t再结合前面对红黑树结构的介绍便可以。另外map/set/multimap/multiset的迭代器也是在红黑树迭代器基础上包裹的这里不再做介绍。
2. HashTable
在STL中hash_map/hash_set/hash_multimap/hash_multiset中由hashtable来实现的虽说hash_map/hash_set/hash_multimap/hash_multise这些容器目前还不是标准C++的内容但是由于实际应用的需要大家目前都把这些容器当做事实标准而且大多数的编译器也支持。这里介绍的是GNU的stdext中的实现下面是hashtable的基本组成
template < class _Val>
struct _Hashtable_node
{
_Hashtable_node* _M_next;
_Val _M_val;
};
template < class _Val, class _Key, class _HashFcn,
class _ExtractKey, class _EqualKey, class _Alloc>
class hashtable
{
public:
typedef _Key key_type;
typedef _Val value_type;
typedef _HashFcn hasher;
typedef _EqualKey key_equal;
typedef _Hashtable_node< _Val> _Node;
typedef vector< _Node*, _Nodeptr_Alloc> _Vector_type;
hasher _M_hash;
key_equal _M_equals;
_ExtractKey _M_get_key;
_Vector_type _M_buckets;
size_type _M_num_elements;
};
通过上面的代码我们可以得知hashtable是由vector+list实现的一个结构纵向是一个vector横向是一个list这正是separate chain hash的实现。我们结合上篇中对vector的介绍用_M_impl._M_start拿到hashtable中vector中数据的起始地址然后通过下标便可得到每个横向链表的头指针。在每个横向的链表中只要我们得到了某个结点的指针便可以用_M_val来查看其数据值可以用_M_next来查看下一个结点的情况。
另外我们也可以通过hashtable的迭代器来查看hashtable中某个结点的数据值
template < class _Val, class _Key, class _HashFcn,
class _ExtractKey, class _EqualKey, class _Alloc>
struct _Hashtable_iterator
{
_Node* _M_cur;
_Hashtable* _M_ht;
};
上面是hashtable的迭代器的定义通过_M_cur便可得知当前位置的内容。
我们知道hash_map/hash_set/hash_multimap/hash_multi_set都是在hashtable的基础上包裹实现的在这些容器中都有一个这样类似的成员
typedef hashtable< ...> _Ht
_Ht _M_ht;
从上面可以看出只要我们拿到hash_map/hash_set/hash_multimap/hash_multiset这些容器的_M_ht成员再结合前面对hashtable的介绍我们便可以方便的查看到这些容器中的所有内容。