C语言变长数组之剖析(2)

上述程序名为dynarray.c,其工作是把参数argv[1]的值n加上1作为变长数组arr的长度,变长数组arr的类型为char。然后向数组中写入一些字符,并将写入的字符串输出。

像下面这样编译这个程序:

[root@cyc test]# gcc -g -o dynarray dynarray.c

然后,用gdb观察dynarray的执行情况:

[root@cyc test]# gdb dynarray

(gdb) break main

Breakpoint 1 at 0x80483a3: file dynarray.c, line 6.

(gdb) set args 6

(gdb) run

Starting program: /root/source/test/a.out 6


Breakpoint 1, main (argc=2, argv=0xbfffe224) at dynarray.c:6

6 n = atoi(argv[1]);

(gdb) next

7 char arr[n+1];

(gdb) next

8 bzero(arr, (n+1) * sizeof(char));

(gdb) print/x arr

$2 = {0xb0, 0xe5}

(gdb) ptype arr

type = char [2]

(gdb) print &arr

$3 = (char (*)[2]) 0xbfffe1c8

这里,当程序执行流通过了为变长数组分配空间的第7行之后,用print/x命令打印出arr的值,结果居然是两个字节;而如果尝试用ptype打印出arr的类型,得到的结果居然是arr是一个长度为2的字符数组。很明显,在本例中,因为提供给main()函数的参数argv[1]是6,因此按常理可知arr应该是一个长度为7的字符数组,但很遗憾,gdb给出的却并不是这样的结果。用print &arr打印出arr的地址为0xbfffe1c8。继续上面的调试过程:

(gdb) x/4x &arr

0xbfffe5c8: 0xbfffe5b0 0xbfffe5c0 0x00000006 0x40015360

(gdb) x/8x $esp

0xbfffe5b0: 0xbffffad8 0x42130a14 0xbfffe5c8 0x0804828d

0xbfffe5c0: 0x42130a14 0x4000c660 0xbfffe5b0 0xbfffe5c0

可以看到,在&arr(即地址0xbfffe5c8)处的第一个32位值是0xbfffe5b0,而通过x/8x $esp可以发现,栈顶指针esp恰好就指向的是0xbfffe5b0这个位置。于是,可以猜想,如果arr是一个指针的话,那么它指向的就恰好是当前栈顶的指针。继续上面的调试:

(gdb) next

9 for (i = 0; i < n; i++) {

(gdb) next

10 arr[i] = (char)('A' + i);

(gdb) next

9 for (i = 0; i < n; i++) {

(gdb) until

12 arr[n] = '';

(gdb) next

13 printf("%sn", arr);

(gdb) x/8x $esp

0xbfffe5b0: 0x44434241 0x42004645 0xbfffe5c8 0x0804828d

0xbfffe5c0: 0x42130a14 0x4000c660 0xbfffe5b0 0xbfffe5c0

注意上面表示为蓝色的部分,由于Intel平台采用的是小端字节序,因此蓝色的部分实际上就是’ABCDEF’的十六进制表示。而红色的32位字则暗示着arr就是指向栈顶的指针。为了确认我们的这一想法,下面通过修改arr的值来观察程序的执行情况(需要注意的是:每一次运行时堆栈的地址是变化的):

(gdb) run

The program being debugged has been started already.

Start it from the beginning? (y or n) y

Starting program: /root/source/test/dynarray 6


Breakpoint 1, main (argc=2, argv=0xbfffde24) at dynarray.c:6

6 n = atoi(argv[1]);

(gdb) next

7 char arr[n+1];

(gdb) next

8 bzero(arr, (n+1) * sizeof(char));

(gdb) print/x &arr

$3 = 0xbfffddc8

(gdb) x/8x $esp

0xbfffddb0: 0xbffffad8 0x42130a14 0xbfffddc8 0x0804828d

0xbfffddc0: 0x42130a14 0x4000c660 0xbfffddb0 0xbfffddc0

(gdb) set *(unsigned int*)&arr=0xbfffddc0

(gdb) x/8x $esp

0xbfffddb0: 0xbffffad8 0x42130a14 0xbfffddc8 0x0804828d

0xbfffddc0: 0x42130a14 0x4000c660 0xbfffddc0 0xbfffddc0

(gdb) next

9 for (i = 0; i < n; i++) {

(gdb) next

10 arr[i] = (char)('A' + i);

(gdb) next

9 for (i = 0; i < n; i++) {

(gdb) until

12 arr[n] = '';

(gdb) next

13 printf("%sn", arr);

(gdb) x/8x $esp

0xbfffddb0: 0xbffffad8 0x42130a14 0xbfffddc8 0x0804828d

0xbfffddc0: 0x44434241 0x40004645 0xbfffddc0 0xbfffddc0

地址0xbfffddc8(也就是arr的地址)处的值本来为0xbfffddb0,我们把它改成了0xbfffddc0,于是,当程序运行到向变长数组输入数据完成之后,我们发现这次修改的地址的确是从0xbfffddc0开始的。这就表明arr的确像我们通常所理解的一样,数组名即指针。只不过这个指针指向的位置在它的下方(堆栈向下生长),而不是像大多数时候一样指向上方的某个位置。

4、分析
上面的测试结果表明:变长数组的确是在栈空间中分配的;变长数组的数组名实际上就是一个地址指针,指向数组所在的栈顶位置;而GDB无法判断出变长数组的数组名实际上是一个地址指针。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/429f5a27fd386b06c2f86732aa963224.html