那谁的技术博客

感兴趣领域:高性能服务器编程,存储,算法,Linux内核
随笔 - 210, 文章 - 0, 评论 - 1183, 引用 - 0
数据加载中……

使用gdb跟踪C语言中变长数组的实现

 项目的代码中出现的一个问题,问题的表现是,在一个函数中使用到了变长数组,而对超过这个数组 范围的一个赋值,导致了数组首地址为空.

我把这个问题抽出来形成了一个示例函数,在i386下也出现类似的问题,代码如下:

#include <stdio.h>

int test(int n)
{
    
char *arg[n + 4];

    printf(
"before:arg = %p\n"&arg[0]);
    arg[
16= NULL;
    printf(
"after:arg = %p\n"&arg[0]);

    
return 0;
}

int main()
{
    test(
2);

    
return 0;
}

这段代码在i386平台下面,执行完"arg[16] = NULL"语句之后,再打印arg的首地址,显示为NULL
使用gdb跟踪这个问题:
(gdb) b test
Breakpoint 1 at 0x804835b: file test.c, line 4.
(gdb) display /i $pc
(gdb) run
Starting program: /home/lichuang/test/a.out

Breakpoint 1, test (n=2) at test.c:4
4    {
1: x/i $pc  0x804835b <test+7>:    mov    %esp,%eax
(gdb) si
0x0804835d    4    {
1: x/i $pc  0x804835d <test+9>:    mov    %eax,%ebx
(gdb)
5        char *arg[n + 4];
1: x/i $pc  0x804835f <test+11>:    mov    0x8(%ebp),%eax
(gdb)
0x08048362    5        char *arg[n + 4];
1: x/i $pc  0x8048362 <test+14>:    add    $0x4,%eax
(gdb)
0x08048365    5        char *arg[n + 4];
1: x/i $pc  0x8048365 <test+17>:    shl    $0x2,%eax
(gdb)
0x08048368    5        char *arg[n + 4];
1: x/i $pc  0x8048368 <test+20>:    add    $0xf,%eax
(gdb)
0x0804836b    5        char *arg[n + 4];
1: x/i $pc  0x804836b <test+23>:    add    $0xf,%eax
(gdb)
0x0804836e    5        char *arg[n + 4];
1: x/i $pc  0x804836e <test+26>:    shr    $0x4,%eax
(gdb)
0x08048371    5        char *arg[n + 4];
1: x/i $pc  0x8048371 <test+29>:    shl    $0x4,%eax
(gdb)
0x08048374    5        char *arg[n + 4];
1: x/i $pc  0x8048374 <test+32>:    sub    %eax,%esp
(gdb)
0x08048376    5        char *arg[n + 4];
1: x/i $pc  0x8048376 <test+34>:    lea    0x8(%esp),%eax
(gdb)
0x0804837a    5        char *arg[n + 4];
1: x/i $pc  0x804837a <test+38>:    mov    %eax,0xffffffe8(%ebp)
(gdb)
0x0804837d    5        char *arg[n + 4];
1: x/i $pc  0x804837d <test+41>:    mov    0xffffffe8(%ebp),%eax
(gdb)
0x08048380    5        char *arg[n + 4];
1: x/i $pc  0x8048380 <test+44>:    add    $0xf,%eax
(gdb)
0x08048383    5        char *arg[n + 4];
1: x/i $pc  0x8048383 <test+47>:    shr    $0x4,%eax
(gdb)
0x08048386    5        char *arg[n + 4];
1: x/i $pc  0x8048386 <test+50>:    shl    $0x4,%eax
(gdb)
0x08048389    5        char *arg[n + 4];
1: x/i $pc  0x8048389 <test+53>:    mov    %eax,0xffffffe8(%ebp)
(gdb)
0x0804838c    5        char *arg[n + 4];
1: x/i $pc  0x804838c <test+56>:    mov    0xffffffe8(%ebp),%eax
(gdb)
0x0804838f    5        char *arg[n + 4];
1: x/i $pc  0x804838f <test+59>:    mov    %eax,0xfffffff8(%ebp)
(gdb)
7        printf("before:arg = %p\n", &arg[0]);
1: x/i $pc  0x8048392 <test+62>:    mov    0xfffffff8(%ebp),%eax


上面是使用gdb跟踪汇编代码显示的结果,可以看到,在定义变长数组arg[n + 4]的时候,执行了很多语句,秘密都在这些汇编代码里面了,把这个程序用objdump -d命令反汇编出来,抽出上面的那部分汇编代码查看:
804835b:    89 e0                    mov    %esp,%eax
804835d:    
89 c3                    mov    %eax,%ebx
804835f:    8b 
45 08                 mov    0x8(%ebp),%eax
8048362:    83 c0 04                 add    $0x4,%eax
8048365:    c1 e0 02                 shl    $0x2,%eax
8048368:    83 c0 0f                 add    $0xf,%eax
804836b:    
83 c0 0f                 add    $0xf,%eax
804836e:    c1 e8 
04                 shr    $0x4,%eax
8048371:    c1 e0 04                 shl    $0x4,%eax
8048374:    29 c4                    sub    %eax,%esp
8048376:    8d 44 24 08              lea    0x8(%esp),%eax
804837a:    
89 45 e8                 mov    %eax,0xffffffe8(%ebp)
804837d:    8b 
45 e8                 mov    0xffffffe8(%ebp),%eax
8048380:    83 c0 0f                 add    $0xf,%eax
8048383:    c1 e8 04                 shr    $0x4,%eax
8048386:    c1 e0 04                 shl    $0x4,%eax
8048389:    89 45 e8                 mov    %eax,0xffffffe8(%ebp)
804838c:    8b 
45 e8                 mov    0xffffffe8(%ebp),%eax
804838f:    
89 45 f8                 mov    %eax,0xfffffff8(%ebp)
8048392:    8b 45 f8                 mov    0xfffffff8(%ebp),%eax

逐句进行分析如下:

804835b:    89 e0                    mov    %esp,%eax
804835d:    
89 c3                    mov    %eax,%ebx
将esp寄存器地址通过eax保存到ebx寄存器中


804835f:    8b 45 08                 mov    0x8(%ebp),%eax
8048362:    83 c0 04                 add    $0x4,%eax
首先获得传入test函数的参数n的值(在内存地址为ebp+8的位置),再将它的值加上4,也就得到了数组arg[n+4]的元素数量


8048365:    c1 e0 02                 shl    $0x2,%eax
8048368:    83 c0 0f                 add    $0xf,%eax
804836b:    
83 c0 0f                 add    $0xf,%eax
804836e:    c1 e8 
04                 shr    $0x4,%eax
8048371:    c1 e0 04                 shl    $0x4,%eax
首先将前面得到的元素数量左移两位(shl 0x2),也就是乘以4,4是sizeof(char*)的大小,于是得到了char* arg[n+4]所容纳元素的空间大小.之后两次加上0xf,然后又右移4位左移4位的原因是,编译器要将这个大小按照16来对齐,而又要留够足够的空 间,所以前面两次加上0xf.在上面几个操作完成之后,eax里面的值就是可以容纳char* arg[n+4]的按照16对齐的数据

8048374:    29 c4                    sub    %eax,%esp
根据前面得到的eax值调整esp指针,也就是在test函数的栈帧地址的低位置留出了足够容纳arg数组的空间. 注意到,esp值已经在最开始保存到ebx寄存器中了,所以,在test函数的结束位置,还要使用ebx寄存器恢复esp寄存器.

8048376:    8d 44 24 08              lea    0x8(%esp),%eax
804837a:    
89 45 e8                 mov    %eax,0xffffffe8(%ebp)
804837d:    8b 
45 e8                 mov    0xffffffe8(%ebp),%eax
8048380:    83 c0 0f                 add    $0xf,%eax
8048383:    c1 e8 04                 shr    $0x4,%eax
8048386:    c1 e0 04                 shl    $0x4,%eax
8048389:    89 45 e8                 mov    %eax,0xffffffe8(%ebp)
804838c:    8b 
45 e8                 mov    0xffffffe8(%ebp),%eax
804838f:    
89 45 f8                 mov    %eax,0xfffffff8(%ebp)
第一句将地址esp+8赋值到eax中(注意lea指令和mov的区别,前者是取指针操作,后者是取指针所指向的内存数据操作),然后将eax赋值到 ebp+0xffffffe8内存处,而后面一句又将这个值赋值回到eax中,我不明白这句话有什么作用,看上去没有什么影响.最后又执行前面看到的按照 16对齐的操作,对齐后的结果仍然保存在eax中.这个值就是最后arg的首地址所在,最后三句将这个值分别存放到ebp+0xffffffe8和 ebo+0xffffff8处.也就是说,arg的真实地址,其实在内存中有两处进行了保存.在gdb中查看,确实如此:
(gdb) x/20 &arg
0xbfde96e0:    0x00000000    0x00000000    0x00000000    0x00000000
0xbfde96f0:    0x00000000    0x00000000    0x00000000    0x080482e0
0xbfde9700:    0x00000000    0x080495d8    0xbfde9718    0x08048265
0xbfde9710:    0xbfde96e0    0x00632214    0xbfde9748    0x08048429
0xbfde9720:    0xbfde96e0    0x00633ff4    0xbfde9738    0x080483eb
可以看到,查看&arg的内存地址后发现,它的地址是0xbfde96e0,而在地址为0xbfde9710和0xbfde9720,都存放着地 址&arg的地址0xbde96e0,它们分别位于&arg[13]和&arg[16]处,而这两处的赋值,就是前面:
8048389:    89 45 e8                 mov    %eax,0xffffffe8(%ebp)
804838c:    8b 
45 e8                 mov    0xffffffe8(%ebp),%eax
804838f:    
89 45 f8                 mov    %eax,0xfffffff8(%ebp)
的结果.我不清楚为什么会在两个地方保存这个地址. 所以,在test函数的代码中,将arg[16]赋值为NULL之后,再次打印arg的值就是NULL了.

现在,可以得出以下的结论:

变长数组在实现时实际上退化为了指针,原先定长的数组,它的地址同时也就是容纳数组元素位置的首地址,而变长数组,退化为了指针,该指针指向的位置才是真 正容纳数组元素的首地址.在对汇编代码的分析中,可以看到,在定义变长数组arg的时候,首先通过函数参数,以及数组元素的尺寸 (sizeof(char*))获得这个数组的总大小(需要对齐),然后调整esp指针留出空间给数组.这就是因为变长数组的大小只有在运行时才确定,所 以需要在运行时动态计算并且在函数栈帧的最低位置留出空间.

大概的示意图如下:


BTW:在gdb中,打印&arg可以看到:

(gdb) print &arg
$
1 = (char *(*)[0]) 0xbfde96e0

也就是,arg是一个指针,指向的是一个元素类型为char*的数组,而这个数组的元素是0.这是C中常用的技巧了,定义一个0元素的数组,实际上里面存放的元素数量是不确定的.这也证明了我们前面提到的:变长数组被编译器退化为指针来处理.


最后,注意前面的描述都在i386平台下面,根据编译器,体系结构的不同,可能稍有区别,即使在同一个平台下面,加不同的优化参数(如-O2等),具体的实现可能有差异,但是大体上还是上面提到的原理.

我想,从这个例子里,也可以进一步加深对指针和数组区别的理解.

posted on 2009-10-31 10:25 那谁 阅读(6544) 评论(4)  编辑 收藏 引用 所属分类: gdb

评论

# re: 使用gdb跟踪C语言中变长数组的实现  回复  更多评论   

学习中
2009-10-31 11:07 | 寻舟

# re: 使用gdb跟踪C语言中变长数组的实现  回复  更多评论   

感谢提供详细的asm分析,但是看了你这篇文章后,我还是没有明白为什么要在arg[16]的位置保存了arg[0]的地址,另外:

变长数组在实现时实际上退化为了指针,原先定长的数组,它的地址同时也就是容纳数组元素位置的首地址,而变长数组,退化为了指针,该指针指向的位置才是真正容纳数组元素的首地址.

二维数组或是char** p的原理应该都是这样的吧?
请指教
2009-11-02 09:45 | zuhd

# re: 使用gdb跟踪C语言中变长数组的实现  回复  更多评论   

@zuhd
可能我的意思表达不清楚,我补充了一个示意图,你可以再看看.
BTW:那个不是二维数组,而是存放的元素类型都是char*的数组,本质还是一维数组,说到这个点又是"指针与数组的区别"了,呵呵.

2009-11-03 09:03 | 那谁

# re: 使用gdb跟踪C语言中变长数组的实现  回复  更多评论   

www.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.comwww.mardanjan2020.51.com
2011-05-21 14:25 | mardanjan

只有注册用户登录后才能发表评论。
【推荐】超50万行VC++源码: 大型组态工控、电力仿真CAD与GIS源码库
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理