在我上一篇随笔中,我讨论了一个由于数组越界导致程序陷入死循环的情况及其原因。不过,其中还是有些疑问:如果变量跟数组处于同一数据段时(或是栈,或是非初始化数据段等等),它们在内存中是怎样安排的?于是,我重新又小实验了一下。对于代码:
void foo() { int a[4]; int i; for (i = 0; i <= 4; i++) { a[i] = 0; } }
使用gcc -c生成目标文件后利用objdump -d反汇编一下,生成如下:
0: push %rbp 1: mov %rsp,%rbp 4: movl $0x0,-0x4(%rbp) b: jmp 1e <foo+0x1e> d: mov -0x4(%rbp),%eax 10: cltq 12: movl $0x0,-0x20(%rbp,%rax,4) 1a: addl $0x1,-0x4(%rbp) 1e: cmpl $0x4,-0x4(%rbp) 22: jle d <foo+0xd> 24: pop %rbp 25: retq
观察一下此汇编代码知:对i赋值的语句是 movl $0x0,-0x4(%rbp) ,对数组a元素赋值的语句是movl $0x0,-0x20(%rbp,%rax,4) 。其中$0x0对应整数0自然不必多说,而%rbp则代表栈底地址寄存器,%rax我觉得应该是寄存器%eax的最低32位(我是64位机子,%eax也应是64位的),结合前面的代码知道这里就代理着i的值。有此认识后,变量i的地址 -0x4(%rbp) 意义也就清晰了,它位于栈底!而数组i的地址 -0x20(%rbp,%rax,4) 可解析为:%rbp-0x20+%rax*4,也就是说,数组a的地址是 -0x20(%rbp)。(注:第一眼看到这我就懵了,这样的话a[4]不正代表着i吗?因为(20-4)/4=4。后来突然意识到,0x20是16进制数。。。)于是乎,栈的分布图如下:
-0x4 | i |
-0x8 | |
-0xc | |
-0x10 | |
-0x14 | a[3] |
-0x18 | a[2] |
-0x1c | a[1] |
-0x20 | a[0] |
哈,奇怪吧。变量i跟数组a之间竟然存在一段空白!那么,这段空白里面竟然存在有什么数据呢?我一开始也不明白,还以为是APUE里面说的函数调用时所需要保存的信息。后来觉得这还是说不通,因为这些信息应该位于i的上面才是。看着这张表,我想起了struct结构体的变量对齐问题,觉得这两者之中貌似有些类似啊。于是我往函数里面又增加了一个变量,果断不是所料,新增加的变量被放入-0x8的位置!那么,如果这段空白已经被填满了,再加一个变量情况又会变成什么样呢?答案是:栈的空间又增加了0x10的大小,然后,栈会变成这个样子:
-0x4 | i |
-0x8 | j |
-0xc | k |
-0x10 | l |
-0x14 | m |
-0x18 | |
-0x1c | |
-0x20 | |
-0x24 | a[3] |
-0x28 | a[2] |
-0x2c | a[1] |
-0x30 | a[0] |
(可能你还会想那么变量i,j,k...是怎么安排的,虽然我没做过验证,但我想应该跟他们声明的顺序以及有没有被初始化有关)
至此,为什么foo函数在我这不会发生死循环的原因应该算是清楚了。而对于把数组a跟变量i放在foo函数外面的情况,其反汇编出来的代码是:
0: push %rbp 1: mov %rsp,%rbp 4: movl $0x0,0x0(%rip) e: jmp 32 <foo+0x32> 10: mov 0x0(%rip),%eax 16: cltq 18: movl $0x0,0x0(,%rax,4) 23: mov 0x0(%rip),%eax 29: add $0x1,%eax 2c: mov %eax,0x0(%rip) 32: mov 0x0(%rip),%eax 38: cmp $0x4,%eax 3b: jle 10 <foo+0x10> 3d: pop %rbp 3e: retq
有能力的同学自己分析下看看了,我汇编基础不大好,所以还是有些不明白%rip是什么玩意。不过,实验中我曾经打印过a跟i的地址,发现i总会比a大0x10,故a[4]的地址即是i的地址。
为什么编译器会这样安排呢?我认为,跟结构体的内存对齐是同个道理。具体可参考:http://zhangyu.blog.51cto.com/197148/673792
最后的最后,不妨再给出一段代码,让大家猜猜输出结果会是什么 。(实践出真知,不妨运行一下,看与你想的一不一样)
#include <stdio.h> int glob_v1; int glob_v2; int glob_array[4]; int glob_v1_with_value = 1; int glob_v2_with_value = 2; int glob_array_with_value[4] = {1, 2, 3, 4}; int main(int argc, const char *argv[]) { int loc_v1; int loc_v2; int loc_array[4]; static int sloc_v1; static int sloc_v2; static int sloc_array[4]; printf("glob_v1:\t\t0X%08x\n", &glob_v1); printf("glob_v2:\t\t0X%08x\n", &glob_v2); printf("glob_array:\t\t0X%08x\n", glob_array); printf("glob_v1_with_value:\t0X%08x\n", &glob_v1_with_value); printf("glob_v2_with_value:\t0X%08x\n", &glob_v2_with_value); printf("glob_array_with_value:\t0X%08x\n", glob_array_with_value); printf("loc_v1:\t\t\t0X%08x\n", &loc_v1); printf("loc_v2:\t\t\t0X%08x\n", &loc_v2); printf("loc_array:\t\t0X%08x\n", &loc_array); printf("sloc_v1:\t\t0X%08x\n", &sloc_v1); printf("sloc_v2:\t\t0X%08x\n", &sloc_v2); printf("sloc_array:\t\t0X%08x\n", &sloc_array); return 0; }