原理
顺着这个思路想了一下,为什么C语言的全局变量(global)就算不赋值会被自动初始化位默认值,但是局部变量(local)不会呢? 学习了一下C语言的内存布局结构,然后自己验证了一下然后明白了这个原因。
首先我们得知道C语言的内存布局结构,这篇文章 Memory layout of c program 讲的非常详细。这里列出我要用到的部分,首先看结构(图片来自这篇文章):
从这篇文章里面我明白了两点:
- 全局变量是存放在data段内存的。data段分为uninitialized data(bss)段和initalized data,未初始化的变量是放在bss段的,这部分内存存放的变量是会被自动初始化的(这是C语言的特性)
- 局部变量是存放在stack段的。这部分内存是被runtime时期动态分配的。(其实局部变量在代码编译之后就是一个地址,接下来会演示出来)
明白了这两点,那我通过一个demo来验证一下是否在内存中真的是这个样子。
//test_c_variable_init.c
#include <stdio.h>
int global_var;
int main()
{
int local_var;
local_var = local_var + 3;
printf("global_var: %d & local_var: %d \n", global_var, local_var);
return 0;
}
利用demo里面声明的一个全局变量(global_var)和局部变量(local_var)来验证现在的问题。(对local_var加常量3的操作仅仅是为了能够快速定位local_var在反编译代码中的位置)
首先可以通过linux的size命令查看总的内存分布
编译test_c_variable_init.c文件
gcc -o test_c_variable_init test_c_variable_init.c
得到可执行文件test_c_variable_init,现在可以查看到这个可执行文件的内存分布:
然后我们在test_c_variable_init.c中增加一个全局变量:
int global_var2;
按照之前的步骤我们再次查看重新编译的文件的内存分布:
可以看到bss段增加了4kb,这个大小就是我们增加的global_var2变量的大小。按照同样的方式增加local_var2我们会发现data和bss段都不会增加。这样可依初步确定上面列出的两点结论了。
然后我们通过查看反编译代码直接来查看变量在编译之后的位置,这样就明确之前的两点结论。
objdump -D test_c_variable_init > test_c_variable_init.d
我们把反编译之后的代码重定向到test_c_variable_init.d文件,然后我们查看下这个文件,其中有两个地方可以让我们得到想要的答案。
这是反编译之后的main函数:
可以看到347行的 $0xc ,之前我列出demo的时候为local_var特地添加了一个加常量3的操作,通过这个操作我可以很快定位到了$0xc 就是对应的local_var变量了, $0x3 就是常量3(addl就是汇编里面的加运算)。这里证明了local_var是分配在stack区的。此外我们注意到 0x804a020 这个地址其实就是global_var变量了,跳转到这个地址
可以很清楚的看到 0x804a020 就是global_var并且存放在bss段的。
这样我们就验证了上面的两点结论了,也知道了为什么未初始化的全局变量会被自动初始化位默认值,局部变量不会被初始化了。
一般变量看不出什么区别,对于静态变量就很明显了,如:
int func()
{
static int a = 10; //初始化
static int b;
b = 10; //赋值
a++;
b++;
printf("%d\n", a); //第一次调用函数func,a 的值为 11,第二次调用时为 12,……
printf("%d\n", b); //第一次调用函数func,b 的值为 11,第二次调用时为 11,……
}
从上例可以看出,静态变量只初始化一次,所以 a 的值会随调用的次数递增;而 b 由于重新赋值,所以他的值始终是 11。