) 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无法判断出变长数组的数组名实际上是一个地址指针。
GDB为什么无法准确判断出变长数组的类型的原因尚不清楚,但是作者猜测这和变长数组的动态特性有关,由于变长数组是在程序动态执行的过程生成的,GDB无法向对待常规数组一样从目标文件包含的.stabs节中获得长度信息,于是给出了错误的类型信息。
另外,作者对变长数组的作用域进行了测试,测试代码根据上例修改得到,如下所示:
1 int n;
2 char arr[n+1];
3
4 int
5 main(int argc, char *argv[])
6 {
7 int i;
8
9 n = atoi(argv[1]);
10 bzero(arr, (n+1) * sizeof(char));
11 for (i = 0; i < n; i++) {
12 arr[i] = (char)('A' + i);
13 }
14 arr[n] = '';
15 printf("%sn", arr);
16
17 return (0);
18 }
当如下编译的时候,gcc会提示出错:
[root@cyc test]# gcc -g dynarray.c
dynarray.c:2: variable-size type declared outside of any function
可见gcc不允许在文件域定义变长数组。
对于gcc中的变长数组能否用static修饰则使用如下代码进行测试:
1 int
2 main(int argc, char *argv[])
3 {
4 int i, n;
5
6 n = atoi(argv[1]);
7 static char arr[n+1];
8 bzero(arr, (n+1) * sizeof(char));
9 for (i = 0; i < n; i++) {
10 arr[i] = (char)('A' + i);
11 }
12 arr[n] = '';
13 printf("%sn", arr);
14
15 return (0);
16 }
当编译此源文件的时候,gcc给出如下错误提示:
[root@cyc test]# gcc -g dynarray.c
dynarray.c: In function `main':
dynarray.c:7: storage size of `arr' isn't consta