GDB程序调试笔记

最近程序在linux系统中出现程序的进程意外死掉的现象,查看日志发现是信号量异常导致。结合进程死机异常产生的core文件和程序版本通过GDB工具进行程序的调试,发现是内存的问题。

为了更好的使用GDB工具,查阅资料进行总结,也希望能够帮助开始使用GDB的新手。

1、GDB是什么

GDB就是一个帮助程序猿调试程序的工具,类似于windows下的IDE自带的调试工具(如断点调试、内存查看、堆栈信息显示等)。

一般来说 GDB 主要调试的是 C/C++的程序。要调试 C/C++的程序,首先在编译时,必须要把调试信息加到可执行文件中。使用编译器(cc/gcc/g++)的 -g 参数可以做到这一点。

> cc -g hello.c -o hello

> g++ -g hello.cpp -o hello

如果没有-g,你将看不见程序的函数名、变量名,所代替的全是运行时的内存地址

2、如何启动GDB

    1、gdb <program>
    program 也就是你的执行文件,一般在当前目录下。

    2、gdb <program> core
    用gdb同时调试一个运行程序和core 文件,core 是程序非法执行后core dump 后产生的文件。

    3、gdb <program> <PID>
    如果你的程序是一个服务程序或者已经在运行了,那么可以指定这个服务程序运行时的进程 ID。gdb 会自动attach 上去,并调试他。program 应该在PATH 环境变量中搜索得到

    也可以这样:先用 gdb <program>关联上源代码,并进行 gdb,在 gdb 中用 attach 命令来挂接进程的 PID。并用detach 来取消挂接的进程

3、简单使用

l <-------------------- l 命令相当于list,从第一行开始例出原码
<-------------------- 直接回车表示,重复上一次命令
break 16 <-------------------- 设置断点,在源程序第16行处
break func <-------------------- 设置断点,在函数func()入口处
r <--------------------- 运行程序,run 命令简写
n <--------------------- 单条语句执行,next命令简写
c <--------------------- 继续运行程序,continue 命令简写
p i <--------------------- 打印变量i的值,print命令简写
bt <--------------------- 查看函数堆栈
finish <--------------------- 退出函数
q <--------------------- 退出 gdb

4、程序运行参数和环境操作

    1、GDB中调用 UNIX 的 shell 来执行linux命令

    shell <command string>      

    如:(gdb) shell $PWD
            bash: /home/duxx/test_gdb: is a directory

    2、GDB中执行make命令,重新build 自己的程序

    make <make-args>    等价于“shell make <make-args>”

    3、设置程序运行参数

    set args         可指定运行时参数。
    show args     命令可以查看设置好的运行参数。

    (gdb) set  args 5
    (gdb) show  args 
    Argument list to give program being debugged when it is started is “5”.

    4、运行路径和环境变量

    path <dir>         可设定程序的运行路径。
    show paths       查看程序的运行路径。
    set environment varname [=value]     设置环境变量。如:set env USER=hchen

5、断点调试

    1、设置断点BreakPoint 

我们用break 命令来设置断点。正面有几点设置断点的方法:
    break <function>        eg:break func() 
    在进入指定函数时停住。C++中可以使用class::function 或 function(type,type)格式来指定函数名。

    break <linenum>        eg:break 8
    在指定行号停住。

    break +offset    (当前行号+offset)    
    break -offset     (当前行号-offset)  
    在当前行号的前面或后面的offset行停住(是在程序运行前和运行时都可设置)。offiset 为自然数。

    break filename:linenum        eg:break  test.cpp:18
    在源文件filename 的linenum 行处停住。

    break filename:function        eg:break test.cpp:func() 
    在源文件filename 的function 函数的入口处停住。

    break *address        eg:break  0x0804895b
    在程序运行的内存地址处停住。

    break
    break 命令没有参数时,表示在下一条指令处停住。

    break … if <condition>
    …可以是上述的参数,condition 表示条件,在条件成立时停住。比如在循环境体中,可以设置
    break if i=100,表示当i为 100 时停住程序。

    查看断点时,可使用info 命令,如下所示:(注:n 表示断点号)
    info breakpoints [n]
    info break [n]
    eg:
    1       breakpoint     keep y   0x080488bd in func() at test.cpp:19
             breakpoint already hit 1 time
    2       breakpoint     keep y   0x08048852 in test1() at test.cpp:8
    3       breakpoint     keep y   <PENDING>  +2

    2、设置观察点WatchPoint 

观察点一般来观察某个表达式(变量也是一种表达式)的值是否有变化了,如果有变化,马上停住程序。我们有下面的几种方法来设置观察点:
    watch <expr>    为表达式(变量)expr设置一个观察点。当表达式值有变化时,马上停住程序。
    rwatch <expr>     当表达式(变量)expr被读时,停住程序。
    awatch <expr>     当表达式(变量)的值被读或被写时,停住程序。
    info watchpoints     列出当前所设置了的所有观察点

    注意:设置观察点时,要在其观察的变量处设置断点,然后run程序,走到此断点时,才可设置观察点,continue观察设置的观察点是否有变化

    3、清除停止点

    clear
    清除所有的已定义的停止点,测试不能全部删除,使用delete即可

    clear <function>
    clear <filename:function>    
    清除所有设置在函数上的停止点。

    clear <linenum>
    clear <filename:linenum>
    清除所有设置在指定行上的停止点。

    delete [breakpoints] [range…]
    删除指定的断点,breakpoints 为断点号。如果不指定断点号,则表示删除所有的断点。range 表示断点号的范围(如:3-7)。其简写命令为d。

    4、修改停止的条件

可以在设置断点的时候加入条件
     break foo if value_a > value_b

    也可以用 condition 命令来修改断点的条件。(只有break 和watch 命令支持if,catch 目前暂不支持if)

    condition <bnum> <expression>      eg:  condition 3  i=5(不需要 if)
    修改断点号为bnum的停止条件为expression

    condition <bnum>
    清除断点号为bnum的停止条件。

    还有一个比较特殊的维护命令ignore,你可以指定程序运行时,忽略停止条件几次。
    ignore <bnum> <count>
    表示忽略断点号为bnum的停止条件count次。

6、查看栈信息

    1、查看栈简单信息 bt

    bt        打印当前的函数调用栈的所有信息   调用顺序:从下向上依次调用
    bt <n>
    n 是一个正整数,表示只打印栈顶上n个层的栈信息。
    -n 表一个负整数,表示只打印栈底下n个层的栈信息        eg:

(gdb) bt  <------------------------栈的信息
#0  test1 () at test.cpp:8     <------------------------栈的第0层(栈顶)
#1  0x080488f6 in func () at test.cpp:22      <------------------------栈的第1层
#2  0x0804895b in main (argv=1, argc=0xbffff4c4) at test.cpp:33
(gdb) bt 2       <------------------------打印栈顶2个层的栈信息
#0  test1 () at test.cpp:8
#1  0x080488f6 in func () at test.cpp:22
(More stack frames follow...)

    2、切换当前栈,查看当前栈详细信息 frame

frame <n>   
    f <n>             n 是一个从0 开始的整数,是栈中的层编号。比如:frame 0,表示栈顶,frame 1,表示栈的第二层。
    up <n>         表示向栈的上面移动 n层,可以不打n,表示向上移动一层。
    down <n>     表示向栈的下面移动n 层,可以不打 n,表示向下移动一层。 

    info frame    会打印出这些信息:栈的层编号,当前的函数名,函数参数值,函数所在文件及行号,函数执行到的语句
    info args 打印出当前函数的参数名及其值。
    info locals 打印出当前函数中所有局部变量及其值。
    info catch 打印出当前的函数中的异常处理信息

(gdb) info frame          <------------------ 打印出当前栈的详细信息
Stack level 0, frame at 0xbffff3f0:
 eip = 0x8048852 in test1 (test.cpp:8); saved eip = 0x80488f6
 called by frame at 0xbffff410
 source language c++.
 Arglist at 0xbffff3e8, args: 
 Locals at 0xbffff3e8, Previous frame's sp is 0xbffff3f0
 Saved registers:
  ebx at 0xbffff3e4, ebp at 0xbffff3e8, eip at 0xbffff3ec
(gdb) f 0       <------------------ 打印栈顶信息和代码
#0  test1 () at test.cpp:8
8               int* ppp = (int *)new int[10];
(gdb) f 2
#2  0x0804895b in main (argv=1, argc=0xbffff4c4) at test.cpp:33
33              func();
(gdb) up 1      <------------------ 当前栈的指向向上移动一层即#1的栈
#1  0x080488f6 in func () at test.cpp:22
22              test1();
(gdb) info frame    <------------------ 打印当前栈的详细信息
Stack level 1, frame at 0xbffff410:
 eip = 0x80488f6 in func (test.cpp:22); saved eip = 0x804895b
 called by frame at 0xbffff440, caller of frame at 0xbffff3f0
 source language c++.
 Arglist at 0xbffff408, args: 
 Locals at 0xbffff408, Previous frame's sp is 0xbffff410
 Saved registers:
  ebp at 0xbffff408, eip at 0xbffff40c

7、代码查看

查看源码 list

在程序编译时一定要加上-g 的参数,把源程序信息编译到执行文件中,list简写l。

list <linenum>
显示程序第linenum行的周围的源程序(一般是打印当前行的上 5 行和下 5 行)。

list <function>
显示函数名为function 的函数的源程序(如果显示函数是是上 2 行下 8 行,默认是 10 行)。

list
显示当前行后面的源程序(默认行数)。

list –
显示当前行前面的源程序(默认行数)。

指定显示的行数

也可以定制显示的范围,使用下面命令可以设置一次显示源程序的行数。
set listsize <count>
设置一次显示源代码的行数。

show listsize
查看当前listsize 的设置。

list 命令还有下面的用法:
list <first>, <last> 显示从first行到last行之间的源代码。
list , <last> 显示从当前行到 last 行之间的源代码(包含首尾行)。
list + 往后显示源代码(默认行数)。

一般来说在list 后面可以跟以下这们的参数:
<linenum> 行号。
<+offset> 当前行号的正偏移量。
<-offset> 当前行号的负偏移量。
<filename:linenum> 哪个文件的哪一行。
<function> 函数名。
<filename:function> 哪个文件中的哪个函数。
<*address> 程序运行时的语句在内存中的地址。

搜索源代码

forward-search <regexp>
search <regexp>
向前面搜索。
reverse-search <regexp>
全部搜索。
其中,<regexp>就是正则表达式,也主一个字符串的匹配模式

8、程序运行时数据查看

查看程序的变量或地址
1、全局变量(所有文件可见的)    eg:    p   g
2、静态全局变量(当前文件可见的)
3、局部变量(当前Scope 可见的)    eg: p   m
如果你的局部变量和全局变量发生冲突(也就是重名),一般情况下是局部变量会隐藏全局变量,查看格式:file::variable eg:  p ‘test.cpp’::m
查看某个函数的变量:function::variable  eg:没有成功

数组查看 “@”
格式:*内存地址@内存长度   “@”的左边是第一个内存的地址的值,“@”的右边则你你想查看内存的长度

int arr[]={2,23,5,6,7,8};    
(gdb) p *ppp@10
$2 = {1, 2, 0, 0, 0, 0, 0, 0, 0, 0}
int* ppp = (int *)new int[10];
(gdb) p *arr@6
$3 = {2, 23, 5, 6, 7, 8}

输出格式
用GDB的数据显示格式:
x 按十六进制格式显示变量。
d 按十进制格式显示变量。
u 按十六进制格式显示无符号整型。
o 按八进制格式显示变量。
t 按二进制格式显示变量。
a 按十六进制格式显示变量。
c 按字符格式显示变量。
f 按浮点数格式显示变量。

(gdb) p m
$5 = 10
(gdb) p/x m
$6 = 0xa
(gdb) p/o m
$7 = 012
(gdb) p/c m
$8 = 10 '\n'
(gdb) p/f m
$9 = 1.40129846e-44

查看内存的值 examine
格式:x/<n/f/u> <addr>    examine 命令(简写是x)来查看内存地址中的值
n、f、u是可选的参数
n 是一个正整数,表示显示内存的长度,也就是说从当前地址向后显示几个地址的内容。
f 表示显示的格式,参见上面。如果地址所指的是字符串,那么格式可以是 s,如果地十是指令地址,那么格式可以是 i。
u 表示从当前地址往后请求的字节数,如果不指定的话,GDB 默认是 4 个 bytes。u 参数可以用下面的字符来代替,b 表示单字节,h 表示双字节,w 表示四字节,g 表示八字节。当我们指定了字节长度后,GDB会从指内存定的内存地址开始,读写指定字节,并把其当作一个值取出来。
<addr>表示一个内存地址

(gdb) p/x m   (查看变量m的值)
$10 = 0xa
(gdb) p/x &m    (查看变量m的地址)
$11 = 0xbffff3d8
(gdb) x 0xbffff3d8 (根据内存地址查看其值)
0xbffff3d8:     0x0000000a
(gdb) x/4ub 0xbffff3d8 (查看此内存后4个单字节的值)
0xbffff3d8:     10      0       0       0
(gdb) x/2uh 0xbffff3d8    (查看此内存后2个双字节的值)
0xbffff3d8:     10      0

GDB环境变量设置与查看
1、GDB的环境变量和UNIX 一样,也是以$起头  
2、环境变量可以定义任一的类型。包括结构体和数组
3、查看当前所设置的所有的环境变量    show convenience
4、查看单一变量  p  $变量名  eg:p   $foo
eg: set $foo = 12  
      set $pp=”abcdef”


查看寄存器的值
info registers 查看寄存器的情况。(除了浮点寄存器)
info all-registers 查看所有寄存器的情况。(包括浮点寄存器)
info registers <regname …> 查看所指定的寄存器的情况。
p $寄存器名字    看看指定的寄存器的情况 eg: p $eip

9、改变程序的执行

1、修改变量值

使用GDB的 print 命令即可完成:eg  (gdb) print x=4【C/C++的语法,Pascal的语法:x:=4】
变量和GDB中的参数冲突,set var 命令来修改:eg  set var width=47

2、跳转执行

GDB 可以修改程序的执行顺序,可以让程序执行随意跳跃。
jump <linespec>
指定下一条语句的运行点。<linespce>可以是文件的行号,可以是 file:line 格式,可以是+num 这种偏移量格式
jump <address>
这里的<address>是代码行的内存地址。
注意,jump 命令不会改变当前的程序栈中的内容,所以,当你从一个函数跳到另一个函数时,当函数运行完返回时进行弹栈操作时必然会发生错误,可能结果还是非常奇怪的,甚至于产生程序 CoreDump。所以最好是同一个函数中进行跳转

3、产生信号

使用 singal 命令,可以产生一个信号量给被调试的程序
语法是:signal <singal>,UNIX 的系统信号量通常从1 到 15。所以<singal>取值也在这个范围
注意:single 命令和 shell 的 kill 命令不同,系统的 kill 命令发信号给被调试程序时,运行程序马上会被GDB停住,而single 命令所发出一信号则是直接发给被调试程序的

4、强制函数返回
return
return <expression>
使用 return 命令取消当前函数的执行,并立即返回,如果指定了<expression>,那么该表达式的值会
被认作函数的返回值。

5、强制调用函数

call <expr>
表达式中可以一是函数,以此达到强制调用函数的目的。并显示函数的返回值,如果函数返回值是
void,那么就不显示。

最后给大家推荐一本书:陈皓的《用GDB调试程序》。希望对大家有所帮助。

来源

gdb查看内存地址和栈中的值

db查看指定地址的内存地址的值:examine 简写 x—–使用gdb> help x 来查看使用方式
x/ (n,f,u为可选参数)
n: 需要显示的内存单元个数,也就是从当前地址向后显示几个内存单元的内容,一个内存单元的大小由后面的u定义
f:显示格式
x(hex) 按十六进制格式显示变量。
d(decimal) 按十进制格式显示变量。
u(unsigned decimal) 按十进制格式显示无符号整型。
o(octal) 按八进制格式显示变量。
t(binary) 按二进制格式显示变量。
a(address) 按十六进制格式显示变量。
c(char) 按字符格式显示变量。
f(float) 按浮点数格式显示变量
u:每个单元的大小,按字节数来计算。默认是4 bytes。GDB会从指定内存地址开始读取指定字节,并把其当作一个值取出来,并使用格式f来显示
b:1 byte h:2 bytes w:4 bytes g:8 bytes
比如x/3uh 0x54320表示从内存地址0x54320读取内容,h表示以双字节为单位,3表示输出3个单位,u表示按照十六进制显示。

gdb打印表达式的值:print/f 表达式
f是输出的格式,x/d/u/o/t/a/c/f

表达式可以是当前程序的const常量,变量,函数等内容,但是GDB不能使用程序中所定义的宏

查看当前程序栈的内容: x/10x $sp–>打印stack的前10个元素
查看当前程序栈的信息: info frame—-list general info about the frame
查看当前程序栈的参数: info args—lists arguments to the function
查看当前程序栈的局部变量: info locals—list variables stored in the frame
查看当前寄存器的值:info registers(不包括浮点寄存器) info all-registers(包括浮点寄存器)
查看当前栈帧中的异常处理器:info catch(exception handlers)

来源

失落之城

书书

第一章 出发

我的奶奶从前是一名考古学家,在我很小的时候,她就跟我讲她考古时候的事,我很喜欢听。我从小听到大,也对奶奶口中讲述的失落之城充满了兴趣。

这天,镜子里扎着辫子,插着紫罗兰头花,长长的睫毛,有着挺挺的鼻子的我正坐在床上想象失落之城的种种神秘事件,突然,奶奶的声音从房间里传来:“黛娜,快点儿进来,我有话对你说。”“哦,我马上进来,奶奶。”于是,我三步并作两步冲进了房间,金色的卷发随风飘扬。奶奶的房间是这整个屋子里我最爱的,里面有一个大号的古铜色望远镜,一大串神秘的钥匙,老旧的墙纸上挂着一顶棕色的探险帽。在那靠着床边的小圆桌上有一个老旧的笔记本和一张神秘小岛的地图,一个布袋里还装着一个什么石头。奶奶对我说:“黛娜呀,我常跟你说的失落之城就是地图上的地方,这次,我希望你能带上布袋里的东西,”说着,奶奶就从袋子里拿出了一块闪闪发光的宝石,它是金色的爱心形状,形态美极了,一闪一闪的,亮得竟能从里面照到自己。我看呆了,我还从不知道奶奶有这种东西呢!

“拿上宝石,带上地图,准备好行李,去把这块宝石归还给失落之城,它才能恢复它原有的样子!”奶奶严肃地说道。“奶奶,您的意思是……呃……让我自己去岛里冒险,并把宝石归还回去?”“是的,黛娜,记得当年我们去探险,却把那个如此美丽而多样化的小岛搞得一团糟,所以,你要把它恢复原样。”“好的,奶奶,我会令您满意的!”我坚定地说道。奶奶那布满皱纹的脸上又溢满笑容:“黛娜,叫上你那几位爱探险的好朋友吧!有了你们几位,那座小岛一定会比以前更加生机勃勃的!”“嗯,我现在就去收拾行李,明天就出发!”

我回到房间里后,从衣柜里抽出一个很大的黑色旅行包,把要带的东西全部都拿了出来——春夏秋冬的衣服、洗漱用具,以及一个有蓝白相见花纹的大帐篷。收拾好这些东西之后,我走出房间,经过种有一大片紫罗兰的花园之后,来到了有无数川流不息的车辆经过的马路边。驶来了一辆公交车,正好可以到达我朋友的家门口。走上车,投了一枚硬币,随处找了一个座位坐了下来。

车子动了,我望向窗外,窗前的景物飞速般流去,所有东西都如五彩缤纷的水粉颜料一般流走了,只有新的事物扑面而来。到了,映入眼帘的是一幢拥有大花园的房子。走进用木栅栏围住的花园,一颗颗樱花树的花瓣掉在了人的身上,香气透人。地上种有三三两两的满天星,如白色星星般的小巧玲珑的小花缀在万绿丛中。我走到门前,墙壁上布满了美丽的玫瑰花,白色和粉色交织在绿色藤蔓中,很美。

我按了一下门铃,门打开了,映入眼帘的是一头棕色头发,大大眼睛,小小嘴巴的少女:“嗨,黛娜!快进来,今天你来有什么事吗?”“罗拉,我确实有事,”我说着进了门,她跑去冲了两杯咖啡,“就是我的奶奶叫我们去寻找她说的那个失落之城,并把这个,”我拿出了那颗宝石,“归还回去。请问你能跟我一起去吗?”’当然可以咯!你也知道……”她泯了一口咖啡,“我喜欢探险,对吧!”“好吧!你先收拾好行李,我去买船票,明天就出发!”

愉快的拜访结束了,我搭上公车去买船票,每个人都整装待发。第二天,晴空万里,我和罗拉搭上游轮,随着清凉的海风的伴随,我们向神秘小岛进发了!

未完待续……

堆和栈的区别

一、预备知识—程序的内存分配 

  一个由C/C++编译的程序占用的内存分为以下几个部分 
  1、栈区(stack)—   由编译器自动分配释放   ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。 
  2、堆区(heap)   —   一般由程序员分配释放,   若程序员不释放,程序结束时可能由OS回收   。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。 
  3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域, 程序结束后由系统释放。 
  4、文字常量区   —常量字符串就是放在这里的,程序结束后由系统释放 。
  5、程序代码区—存放函数体的二进制代码。 

    例子程序   

这是一个前辈写的,非常详细   
  //main.cpp   

 int   a   =   0;   //全局初始化区   
  char   *p1;   //全局未初始化区   
  main()   
  {   
  int   b;   //栈   
  char   s[]   =   "abc";  // 栈   
  char   *p2;  // 栈   
  char   *p3   =   "123456";  // 123456/0在常量区,p3在栈上。   
  static   int   c   =0;   //全局(静态)初始化区   
  p1   =   (char   *)malloc(10);   
  p2   =   (char   *)malloc(20);    // 分配得来得10和20字节的区域就在堆区。   
  strcpy(p1,   "123456");   //123456/0放在常量区,编译器可能会将它与p3所指向的"123456"  优化成一个地方。   
  }   

  二、堆和栈的理论知识   

  2.1申请方式   
  stack:   
  由系统自动分配。   例如,声明在函数中一个局部变量   int   b;   系统自动在栈中为b开辟空间   
  heap:   
  需要程序员自己申请,并指明大小。

\\在c中malloc函数如:
p1   =   (char   *)malloc(10);  
\\  在C++中用new运算符,如:
 p2   =   new   char[10];   \\但是注意p1、p2本身是在栈中的。    

2.2     申请后系统的响应   
  栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢   出。   
  堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。 
  另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。   

  2.3 申请大小的限制   
  栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。   
  堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。   
2.4申请效率的比较:   
 栈由系统自动分配,速度较快。但程序员是无法控制的。   
 堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便。
 另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是直接在进程的地址空间中保留一块内存,虽然用起来最不方便。但是速度快,也最灵活。 
2.5堆和栈中的存储内容   
 栈:   在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。   
当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。   
堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容由程序员安排。   

  2.6存取效率的比较   
  char   s1[]   =   “aaaaaaaaaaaaaaa”;   
  char   *s2   =   “bbbbbbbbbbbbbbbbb”;   
  aaaaaaaaaaa是在运行时刻赋值的;   
  而bbbbbbbbbbb是在编译时就确定的;   
  但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。   
  比如:   


  #include   
  void   main()   
  {   
  char   a   =   1;   
  char   c[]   =   "1234567890";   
  char   *p   ="1234567890";   
  a   =   c[1];   
  a   =   p[1];   
  return;   
  }   


  对应的汇编代码   
  10:   a   =   c[1];   
  00401067   8A   4D   F1   mov   cl,byte   ptr   [ebp-0Fh]   
  0040106A   88   4D   FC   mov   byte   ptr   [ebp-4],cl   
  11:   a   =   p[1];   
  0040106D   8B   55   EC   mov   edx,dword   ptr   [ebp-14h]   
  00401070   8A   42   01   mov   al,byte   ptr   [edx+1]   
  00401073   88   45   FC   mov   byte   ptr   [ebp-4],al   
  第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到edx中,再根据edx读取字符,显然慢了。   
2.7小结:   
堆和栈的区别可以用如下的比喻来看出:   
使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。   
使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。

Dev-C++中利用gdb查看内存地址中的值

Dev-C++中调用gdb

进行程序调试时我们可以看到调试框内有“ 发送命令到GDB ”这一项, 在此中输入命令,然后按回车即可调用GDB命令 。

Dev-C++中利用gdb查看内存地址中的值

  • 命令格式为
x/nfu <addr>

其中n,f,u为可选参数,为对应的内存地址

下面介绍这三个参数的作用

参数n-需要显示的内存单元个数

也就是从当前地址向后显示几个内存单元的内容,一个内存单元的大小由后面的u定义

参数f-显示格式

有时候我们希望数据内容以不同格式呈现,如2进制,16进制,修改f参数可以调整

x(hex)———————-按十六进制格式显示变量。
d(decimal)—————–按十进制格式显示变量。
u(unsigned decimal)—–按十进制格式显示无符号整型。
o(octal) ——————–按八进制格式显示变量。
t(binary)——————-按二进制格式显示变量。
a(address)—————–按十六进制格式显示变量。
c(char)———————按字符格式显示变量。
f(float) ——————–按浮点数格式显示变量

参数u-每个单元的大小,按字节数来计算

默认是4 bytes,GDB会从指定内存地址开始读取指定字节,并把其当作一个值取出来,并使用格式f来显示

b: 1byte

h: 2 bytes

w: 4 bytes

g:8 bytes

表达式可以是当前程序的const常量,变量,函数等内容,但是GDB不能使用程序中所定义的宏

举例使用

下面为示例代码,并在第5行处添加断点

#include<stdio.h>
int main()
{
    float d;
    d=8.25; 
    return 0;
}

在发送命令到GDB中输入如下代码

x/4xb &d

4代表输出4个内存单元

x代表以十六进制形式输出

b代表每个单元大小1个字节

输出内容如下

->->post-prompt
0x70fe1c:	0x00	0x00	0x04	0x41

则取得了变量d所在内存单元的十六进制数据