[汇编与C语言关系]3. 变量的囤布局

以下面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命令看它的记号表,了解各个变量的地点分布。下面的清单华夏作者把符号表按地址从低及大重新排列了,并且仅截取了我们关注的那几执行:

  C语言 1

  变量A用const修饰,表示A是只有读之,不可修改,它叫分配的地点是0x8048540,从readelf的出口可以见见是地址位于.rodata段:

  C语言 2

  C语言 3

  它在文书中之地点是0x538~0x554,我们所以hexdump命令看之段子的内容:

  C语言 4

  其中0x540地址处的0a 00 00 00
就是变量A,我们尚察看程序中之字符串字面值”Hello world
%d\n”分配在.rodata段的终极,字符串的字面值是单独念之,相当给以全局作用域定义了一个const数组:

  C语言 5

  程序加载运行时,.rodata段和.text段通常合并到一个Segment中,操作系统将这Segment只念保护起来,防止意外改写。这等同触及于readelf的出口为堪拘留出来:

  C语言 6

  注意,像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函数的反汇编代码:

  C语言 7

  可见,给b初始化用底此字符串”Hello
world”并不曾分配在.rodata段,而是直接写于命令里了,通过三漫漫movl指令把12只字节写到栈上,这就算是b的蕴藏空间,如下图所示:

  C语言 8

  虽然栈是从高地址为低地址增长之,但数组总是从低地址为强地址排列的,按从低地址及高地址之一一依次是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语言的作用域分为一下几乎接近:

  • 函数作用域,标识符在整个函数中还使得。只有告句标号属于函数作用域。标号在函数中不需要先声明后用,在前面用一个goto语词也堪跨反到末端的之一标号,但不过限于和一个函数之中。
  • 文本作用域,
    标识符从它声明的职务上马了解者顺序文件之结尾都使得。例如上例被main函数外面的A、a、b、c还发main也算,printf其实是在stdio.h中声明的于含有到之次文件被了,所以也终究文件作用域的。
  • 片作用域,
    标识符位于平针对性{}括号中(函数体或语句块),从她声明的职上马交右}括号中有效。例如上例被main函数里之a、b、c,此外,函数定义着的形参也算块作用域的,从声明的职上马至函数末尾中有效。
  • 函数原型作用域,
    标识符出现在函数原型中,这个函数原型只是一个声称如未是概念(没有函数体),那么标识符从声明的职位上马到当这原型末尾中有效。例如int
    foo(int a, int b);中的a和b。

  对属于同一命名空间的重名标识符,内层作用域会覆盖外层作用域的标识符。命名空间不过分为以下几近乎:

  • 晓句标号单独属于一个命名空间。例如当函数中有的变量和言语标号可以重名,互不影响。由于应用标号的语法和以任何标识符的语法都非雷同,编译器不见面管其与别的标识符弄混。
  • struct,
    enum和union的路Tag属于一个命名空间。由于Tag前面总是带struct, enum,
    union关键字,所以编译器不见面把它和别的标识符弄混。
  • strcut和union的分子称属于一个命名空间。由于成员叫总是通过.或->运算符来访问使未见面独自行使,所以编译器不会见拿ta和别的标识符弄混。
  • 怀有其他标识符,例如变量名、函数名、宏定义、typedef的档次名、enum成员等等都属于同一个命名空间,如果发重名的话,宏定义覆盖有其他标识符,因为她于优先处理等如果不是编译阶段处理,除了宏定义之外任何几类似标识符按上面所说的平整处理,内层作用域覆盖外层作用域。

  标识符的链接属性有三种植:

  • 标链接(External Linkage),
    如果最终之可执行文件由多单程序文件链接而成为,一个标识符在自由程序文件被便声明多次吧还意味着及一个变量或函数,则这标识符具有External
    Linkage。具有External的标识符编译后当符号表中是GLOBAL的号子。例如上例被main函数外面的a和c,main和printf也终究。
  • 其间链接(Internal
    Linkage),如果一个标识符在某程序文件中尽管声明多次吗还代表和一个变量或函数,则是标识符具有Internal
    Linkage。例如上例被main函数外面的b。具有Internal
    Linkage的标识符编译后以符号表中是LOCAL的号,但main函数里面非常b不克算是Internal
    Linkage,因为纵在和一个主次文件中,在不同的函数中宣示多次,也未意味着及一个变量。
  • 无链接(No Linkage) 。除上述状况外的标识C语言符都属于No
    Linkage的,例如函数的局部变量和不意味变量和函数的任何标识符。

  存储类修饰符(Storage Class
Specifier)有以下几种植重点字,可以修饰变量或函数声明:

  • static
    ,用其修饰的变量的蕴藏空间是静态分配的,用它修饰的文书作用域的变量或函数具有Internal
    Linkage。
  • auto
    ,用她修饰的变量在函数调用时自动在栈上分配存储空间,函数返回时自动释放,例如上例被
    main 函数里的 b 其实就是故 auto 修饰的,只不过 auto 可以简单不写,
    auto 不克修饰文件作用域的变量。
  • register ,编译器对于因此 register
    修饰的变量会尽可能分配一个专门的寄存器来囤积,但倘若实在分配不起头寄存器,编译器就拿它当
    auto 变量处理了, register
    不可知修饰文件作用域的变量。现在一般编译器的优化都做得甚好了,它自己会怀念方法有效地利用CPU的寄存器,所以现在
    register 关键字为用得比较少了。
  • extern
    ,上面说过,链接属性是依据一个标识符多次声明时是休是代表及一个变量或函数来分类的,
    extern 关键字就用于多次扬言和一个标识符,下一样章还详尽介绍她的用法。
  • typedef ,它并无是因此来修饰变量的,而是定义一个色名。typedef
    以语法结构中起的职务以及是冲几个重要字一样,也是修饰变量定义之,所以由语法(而无是语义)的角度将它们跟眼前几个重点字归类到联合。

  上面介绍的 const 关键字勿是一个Storage Class
Specifier,虽然看起它吗修饰一个变量声明,但是以之后介绍的更复杂的声明中
const 在语法结构中允许出现的职务及Storage Class
Specifier是勿完全相同的。 const 和后来要是介绍的 restrict 和 volatile
关键字属于同一类语法元素,称为类型限定符(Type Qualifier)。

  变量的生存期(Storage Duration,或者Lifetime)分为以下几近乎:

  • 静态生存期(Static Storage
    Duration),具有外部或内部链接属性,或者叫 static
    修饰的变量,在次开始执行时分配和初始化一不好,此后就是径直留存直到程序结束。这种变量通常位于
    .rodata , .data 或 .bss 段,例如上例被 main 函数外的 A , a , b ,
    c ,以及 main 函数里之 a 。
  • 活动生存期(Automatic Storage Duration),链接属性为无链接以没有叫
    static
    修饰的变量,这种变量在登块作用域时在栈上或寄存器中分配,在离块作用域时释放。例如上例被
    main 函数里之 b 和 c 。
  • 动态分配生存期(Allocated Storage Duration),以后会讲话到调用 malloc
    函数在经过的堆空间中分红内存,调用 free 函数可以纵这种囤空间。