标签:
以下面C程序为例:
#include <stdio.h> const int A = 10; int a = 20; static int b = 30; int c; int main(void) { static int a = 40; char b[] = "Hello World"; register int c = 50; printf("Hello World%d\n", c); return 0; }
我们在全局作用域和main函数的局部作用域各定义了一些变量,并且引入一些新的关键字const, static, register来修饰变量,那么这些变量的存储空间是怎么分配的呢?我们编译之后用readelf命令看它的符号表,了解各变量的地址分布。下面的清单中原作者把符号表按地址从低到高重新排列了,并且只截取了我们关心的那几行:
变量A用const修饰,表示A是只读的,不可修改,它被分配的地址是0x8048540,从readelf的输出可以看到这个地址位于.rodata段:
它在文件中的地址是0x538~0x554,我们用hexdump命令看这个段的内容:
其中0x540地址处的0a 00 00 00 就是变量A,我们还看到程序中的字符串字面值"Hello world %d\n"分配在.rodata段的末尾,字符串的字面值是只读的,相当于在全局作用域定义了一个const数组:
程序加载运行时,.rodata段和.text段通常合并到一个Segment中,操作系统将这个Segment只读保护起来,防止意外改写。这一点从readelf的输出也可以看出来:
注意,像A这种const变量在定义时必须初始化。因为只有初始化才有机会给它一个值,一旦定义之后就不能再改写了,即不能再赋值。
从上面readelf的输出可以看到.data段从地址0x804a010开始,长度是0x14,也就是到地址0x804a024结束。在.data段中有三个变量,a,b和a.1589。
a是一个GLOBAL符号,而b被static关键字修饰了,导致它成为一个LOCAL的符号,所以static在这里的作用是声明b这个符号为LOCAL的,不被链接器处理,如果把多个目标文件链接在一起,LOCAL的符号只能在某一个目标文件中定义和使用,而不能定义在一个目标文件中却在另一个目标文件中使用。一个函数定义前面也可以用static修饰,表示这个函数名符号是LOCAL的。
还有一个a.1589是什么呢?它是main函数中的static int a。函数中的static变量不同于局部变量,它并不是在调用函数时分配在函数返回时释放,而是像全局变量一样静态分配,所以用"static"这个词。另一方面,函数中的static变量的作用域和局部变量一样只在函数中起作用,比如main函数中的a这个变量名只在main函数中起作用,所以编译器给它的符号加了一个后缀以便和全局变量a以及其他函数的变量a区分开。
.bss段从地址0x804a024开始,长度为0xc,也就是到地址0x804a030结束。变量c位于这个段。从上面的readelf输出可以看到.data和.bss在加载时合并到一个Segment中,这个Segment是可读写的。.bss段和.data段的不同之处在于.bss段在文件中不占存储空间,在加载时这个段用0填充。所以全局变量如果不初始化则初值为0,也分配在.bss段。
现在还剩下函数中的b和c这两个变量没有分析。函数的参数和局部变量是分配在栈上的,b是数组也一样,也是分配在栈上的,我们看main函数的反汇编代码:
可见,给b初始化用的这个字符串"Hello world"并没有分配在.rodata段,而是直接写在指令里了,通过三条movl指令把12个字节写到栈上,这就是b的存储空间,如下图所示:
虽然栈是从高地址向低地址增长的,但数组总是从低地址向高地址排列的,按从低地址到高地址的顺序依次是b[0]、b[1]、b[2]......
数组元素b[n]的地址 = 数组的基地址(b做右值就表示这个基地址) + n x 每个元素的字节数,当n=0时,元素b[0]就是数组的基地址,因此数组下标要从0开始而不是从1开始。变量c并没有在栈上分配存储空间,而是直接存在eax寄存器里,后面调用printf也是直接从eax寄存器里取出c的值当参数压栈,这就是register关键字的作用,指示编译器尽可能分配一个寄存器来存储这个变量。调用printf时对于"Hello world %d\n"这个参数压栈的是它在.rodata段中的首地址,而不是把整个字符串压栈。所以字符串在使用时可以看做数组名,如果做右值则表示数组首元素的地址。
我们用全局变量和局部变量这两个概念主要是从作用域上区分的,现在看来用着两个概念给变量区分太笼统了,需要进一步细分。我们总计一下相关的C语法:
作用域这个概念使用于所有标识符,而不仅仅是变量,C语言的作用域分为一下几类:
对属于同一命名空间的重名标识符,内层作用域会覆盖外层作用域的标识符。命名空间可分为以下几类:
标识符的链接属性有三种:
存储类修饰符(Storage Class Specifier)有以下几种关键字,可以修饰变量或函数声明:
上面介绍的 const 关键字不是一个Storage Class Specifier,虽然看起来它也修饰一个变量声明,但是在以后介绍的更复杂的声明中 const 在语法结构中允许出现的位置和Storage Class Specifier是不完全相同的。 const 和以后要介绍的 restrict 和 volatile 关键字属于同一类语法元素,称为类型限定符(Type Qualifier)。
变量的生存期(Storage Duration,或者Lifetime)分为以下几类:
标签:
原文地址:http://www.cnblogs.com/orlion/p/5817148.html