mirror of
https://github.com/beyondx/Notes.git
synced 2026-02-12 23:06:31 +08:00
Add New Notes
This commit is contained in:
241
Zim/Utils/gdb/手把手教你玩转GDB.txt
Normal file
241
Zim/Utils/gdb/手把手教你玩转GDB.txt
Normal 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命令后才执行):
|
||||
|
||||
(1)gdb program ///最常用的用gdb启动程序,开始调试的方式
|
||||
(2)gdb program core ///用gdb查看core dump文件,跟踪程序core的原因
|
||||
(3)gdb program pid ///用gdb调试已经开始运行的程序,指定pid即可
|
||||
|
||||
2. 应用程序带命令行参数的情况,可以通过下面两种方法启动:
|
||||
|
||||
(1)启动GDB的时候,加上- **–args**选项,然后把应用程序和其命令行参数带在后面,具体格式为:gdb -–args program args
|
||||
(2)先按1中讲的方法启动GDB, 然后再执行run命令的时候,后面加上参数
|
||||
|
||||
3. 退出GDB:
|
||||
|
||||
(1)End-of-File(Ctrl+d)
|
||||
(2)quit或者q
|
||||
|
||||
4. 在GDB调试程序的时候执行shell命令:
|
||||
|
||||
(1)shell command args(也可以先执行shell命令,GDB会退出到当前shell, 执行完command后,然后在shell中执行exit命令,便可回到GDB)
|
||||
(2)make 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)。
|
||||
(2)help class-name:查看该类型的命令的详细帮助说明
|
||||
(3)help all:列出所有命令的详细说明
|
||||
(4)help command:列出命令command的详细说明
|
||||
(5)apropos word:列出与word这个词相关的命令的详细说明
|
||||
(6)complete args:列出所有以args为前辍的命令
|
||||
|
||||
6. info和show:
|
||||
|
||||
(1)info:用来获取和被调试的应用程序相关的信息
|
||||
(2)show:用来获取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
|
||||
|
||||
(2)info breakpoints [n]:
|
||||
|
||||
查看第n个breakpoints的相关信息,如果省略了n,则显示所有breakpoints的相关信息
|
||||
|
||||
(3)pending 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)
|
||||
|
||||
(4)breakpoints的删除:
|
||||
|
||||
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指定的范围内的breakpoints,range范围是指breakpoint的序列号的范围
|
||||
|
||||
(5)breakpoints的禁用、启用:
|
||||
|
||||
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时,程序都会停止运行
|
||||
(2)info watchpoints:
|
||||
|
||||
查看当前调试的程序中设置的watchpoints相关信息
|
||||
(3)watchpoints和breakpoints很相像,都有enable/disabe/delete等操作,使用方法也与breakpoints的类似
|
||||
|
||||
3. Catchpoint: 的作用是让程序在发生某种事件的时候停止运行,比如C++中发生异常事件,加载动态库事件
|
||||
(1)设置catchpoints:
|
||||
|
||||
a. catch event: 当事件event发生的时候,程序停止运行,这里event的取值有:
|
||||
|
||||
1)throw: C++抛出异常
|
||||
2)catch: C++捕捉到异常
|
||||
3)exec: exec被调用
|
||||
4)fork: fork被调用
|
||||
5)vfork: vfork被调用
|
||||
6)load: 加载动态库
|
||||
7)load libname: 加载名为libname的动态库
|
||||
8)unload: 卸载动态库
|
||||
9)unload libname: 卸载名为libname的动态库
|
||||
10)syscall [args]: 调用系统调用,args可以指定系统调用号,或者系统名称
|
||||
|
||||
b. tcatch event: 设置只停一次的catchpoint,第一次生效后,该catchpoint被自动删除
|
||||
(2)catchpoints和breakpoints很相像,都有enable/disabe/delete等操作,使用方法也与breakpoints的类似
|
||||
|
||||
**手把手教你玩转GDB(三)——常用命令**
|
||||
|
||||
1.attach process-id/detach
|
||||
(1)attach process-id: 在GDB状态下,开始调试一个正在运行的进程,其进程ID为process-id
|
||||
(2)detach: 停止调试当前正在调试有进程,与attach配对试用
|
||||
|
||||
2.__kill__
|
||||
(1)基本功能:杀掉当前GDB正在调试的应用程序所对应的**子进程**
|
||||
(2)如果想不退出GDB而对当前正在调试的应用程序重新编译、链接,可以在GDB中执行kill杀掉子进程,等编译、链接完后,再重新执行run,GDB便可加载新的可执行程序启动调试
|
||||
|
||||
3.多线程程序调试相关:
|
||||
(1)thread threadno:切换当前线程到由threadno指定的线程
|
||||
(2)info threads:查看GDB当前调试的程序的各个线程的相关信息
|
||||
(3)thread __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进入子进程调试,与父进程不再相关
|
||||
(3)show follow-fork-mode:查看当前GDB多进程跟踪模式的设置
|
||||
|
||||
5.step & stepi
|
||||
(1)step [count]: 如果没有指定count, 则继续执行程序,直到到达与当前源文件不同的源文件中时停止;如果指定了count, 则重复行上面的过程count次
|
||||
(2)stepi [count]: 如果没有指定count, 继续执行下一条__机器指令__,然后停止;如果指定了count,则重复上面的过程count次
|
||||
(3)step比较常见的应用场景:在函数func被调用的某行代码处设置断点,等程序在断点处停下来后,可以用step命令进入该函数的实现中,但前提是该函数编译的时候把调试信息也编译进去了,否则step会跳过该函数。
|
||||
|
||||
6.next & nexti
|
||||
(1)next [count]: 如果没有指定count, 单步执行下一行程序;如果指定了count,单步执行接下来的count行程序
|
||||
(2)nexti [count]: 如果没有指定count, 单步执行下一条指令;如果指定了count, 音频执行接下来的count条执行
|
||||
(3)stepi和nexti的区别:nexti在执行某机器指令时,如果该指令是函数调用,那么程序执行直到该函数调用结束时才停止。
|
||||
|
||||
7.continue [ignore-count] 唤醒程序,继续运行,至到遇到下一个断点,或者程序结束。如果指定ignore-count,那么程序在接下来的运行中,忽略ignore-count次断点。
|
||||
8. __finish & return__
|
||||
(1)finish: 继续执行程序,直到当前**被调用的函数结束**,如果该函数有返回值,把返回值也打印到控制台
|
||||
(2)return [expression]: **中止**当前函数的调用,如果指定了expression,把expresson值当做当前函数的返回值;如果没有,直接结束当前函数调用
|
||||
|
||||
9.信号的处理
|
||||
(1)info signals & info handle:打印所有的信号相关的信息,以及GDB缺省的处理方式:
|
||||
(2)handle signal action: 设置GDB对具体某个信号的处理方式。signal可以为信号整数值,也可以为SIGSEGV这样的符号。action的取值有:
|
||||
|
||||
a. stop和nostop: nostop表示当GDB收到指定的信号,不会应用停止程序的执行,只会打印出一条收到信号的消息,因此,nostop也暗含了下面的print; 而stop则表示,当GDB收到指定的信号,停止应用程序的执行。
|
||||
b. print和noprint: print表示如果收到指定的信号,打印出一条信息; noprint与print表示相反的意思
|
||||
c. pass和nopass:pass表示如果收到指定的信号,把该信号通知给应用程序; nopass表示与pass相反的意思
|
||||
d. ignore和noignore: ignore与nopass同义,同理,noignore与pass同义
|
||||
|
||||
**手把手教你玩转GDB(四)——–函数调用栈(call stack)探密**
|
||||
|
||||
本文是GDB系列的第四篇,感兴趣的朋友可以阅读本系列的前三篇。本文的主要内容是讲如何用GDB来查看C/C++程序中函数调用栈(call stack)的相关信息,通过介绍一些相关的命令及其用法,让读者朋友能够循序渐进了解调用栈的各个方面,更好的驾驭程序。下面开始今天的内容。
|
||||
|
||||
我们知道,通常一个程序的运行,不外乎是A函数调用B,B函数调用C等等,等所有的调用都完成后,整个程序的运行也就ok了。在这个过程中,每当有新的函数调用,系统都会把该函数的一些信息,包括函数的参数,以及一些寄存器的值等,保存到__调用栈__(call stack)上。等该函数运行完成后,这些信息再从调用栈上弹出(pop)。如下图所示,是一个完整的调用栈:
|
||||
{{~/sync/notes/zim/Utils/gdb/手把手教你玩转GDB/callstack.jpg}}
|
||||
在上图中,整体叫做调用栈(call stack),每一行叫做__一桢__(frame)。我们来看看桢信息的组成有哪些:
|
||||
(1)桢号:调用栈中对桢的一个编号,从0开始,依次增大
|
||||
(2)PC:Program counter寄存器,指向当前桢中下一条要执行的指令的地址
|
||||
(3)函数名:当前桢中被调用的函数的名字
|
||||
(4)参数及传入的值:当前桢中被调用的函数在调用时传入的参数及其值
|
||||
(5)源码位置:当前桢执行到的源码位置,格式为 file:linenum
|
||||
|
||||
这里还有一点需要说明,不知道细心的读者朋友有没有发现,foo那一桢没有PC的地址,GDB通过这样来标示该桢是当前__正在执行__到的桢(即当前帧),因此我们通过看调用栈的信息,便可得知程序执行到哪里了。
|
||||
|
||||
读者朋友有没有觉得原来函数调用的过程还有这么多信息可以知道啊,下面我就开始介绍一些GDB命令,通过这些命令你便可以查看到上面介绍的这些信息,甚至更加详细的信息。
|
||||
|
||||
1. 查看调用栈信息:(具体信息的内容,与上面第二部分中介绍的相同)
|
||||
(1)backtrace: 显示程序的调用栈信息,可以用bt缩写
|
||||
(2)backtrace n: 显示程序的调用栈信息,只显示栈顶n桢(frame)
|
||||
(3)backtrace -n: 显示程序的调用栈信息,只显示栈底部n桢(frame)
|
||||
(4)set backtrace limit n: 设置bt显示的最大桢层数
|
||||
(5)where, info stack:都是bt的__别名__,功能一样
|
||||
|
||||
2. 查看桢信息:
|
||||
(1)frame n: 查看第n桢的信息, frame可以用f缩写
|
||||
(2)frame addr: 查看pc地址为addr的桢的相关信息
|
||||
(3)up n: 查看当前桢上面第n桢的信息
|
||||
(4)down 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 frame’s sp is 0xbffff400
|
||||
k. 当前桢中存储的寄存器: Saved registers: ebp at 0xbffff3f8, eip at 0xbffff3fc
|
||||
(2)info args:查看当前桢中的参数
|
||||
(3)info locals:查看当前桢中的局部变量
|
||||
(4)info catch:查看当前桢中的异常处理器(exception handlers)
|
||||
BIN
Zim/Utils/gdb/手把手教你玩转GDB/callstack.jpg
Normal file
BIN
Zim/Utils/gdb/手把手教你玩转GDB/callstack.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
BIN
Zim/Utils/gdb/手把手教你玩转GDB/frame.jpg
Normal file
BIN
Zim/Utils/gdb/手把手教你玩转GDB/frame.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
BIN
Zim/Utils/gdb/手把手教你玩转GDB/gdb_help.jpg
Normal file
BIN
Zim/Utils/gdb/手把手教你玩转GDB/gdb_help.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 80 KiB |
BIN
Zim/Utils/gdb/手把手教你玩转GDB/guanggun.jpg
Normal file
BIN
Zim/Utils/gdb/手把手教你玩转GDB/guanggun.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
218
Zim/Utils/gdb/手把手教你调试STL容器.txt
Normal file
218
Zim/Utils/gdb/手把手教你调试STL容器.txt
Normal 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的介绍,我们便可以方便的查看到这些容器中的所有内容。
|
||||
Reference in New Issue
Block a user