[汇编与C语言关系]三. 变量的存款和储蓄布局

  • 语句标号单独属于贰个命名空间。例如在函数中部分变量和言语标号能够重名,互不影响。由于应用标号的语法和利用此外标识符的语法都不1致,编写翻译器不会把它和别的标识符弄混。
  • struct,
    enum和union的品类Tag属于二个命名空间。由于Tag前边总是带struct, enum,
    union关键字,所以编写翻译器不会把它和别的标识符弄混。
  • strcut和union的分子名属于三个命名空间。由于成员名总是通过.或->运算符来访问而不会独自使用,所以编译器不会把ta和其他标识符弄混。
  • 不无别的标识符,例如变量名、函数名、宏定义、typedef的品类名、enum成员等等都属于同三个命名空间,假设有重名的话,宏定义覆盖全数其余标识符,因为它在预处理阶段而不是编写翻译阶段处理,除了宏定义之外任何几类标识符按上边所说的平整处理,内层功用域覆盖外层作用域。

  它在文件中的地址是0x53八~0x554,大家用hexdump命令看那些段的内容:

  数组成分b[n]的地点 = 数组的集散地址(b做右值就代表那个营地址) + n x
每种成分的字节数,当n=0时,成分b[0]正是数组的营地址,由此数组下标要从0最先而不是从一起来。变量c并从未在栈上分配存款和储蓄空间,而是直接存在eax寄存器里,后面调用printf也是直接从eax寄存器里取出c的值当参数压栈,那就是register关键字的效果,提醒编写翻译器尽恐怕分配多个寄存器来囤积那一个变量。调用printf时对于”Hello
world
%d\n”那几个参数压栈的是它在.rodata段中的首地址,而不是把全体字符串压栈。所以字符串在运用时得以看做数组名,若是做右值则意味数组首成分的地方。

  从上边readelf的输出可以看出.data段从地址0x804a0十起来,长度是0x1四,也便是到地址0x804a024结束。在.data段中有四个变量,a,b和a.1589。

  • 静态生存期(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 函数能够释放那种存储空间。

  标识符的链接属性有二种:

以上面C程序为例:

  C语言 1

  C语言 2

  C语言 3

  注意,像A那种const变量在概念时务必开始化。因为唯有先导化才有时机给它三个值,1旦定义之后就不能够再改写了,即不能够再赋值。

  可知,给b起始化用的这一个字符串”Hello
world”并未分配在.rodata段,而是直接写在指令里了,通过三条movl指令把十个字节写到栈上,那正是b的存款和储蓄空间,如下图所示:

  个中0x540地址处的0a 00 00 00
正是变量A,我们还观察程序中的字符串字面值”Hello world
%d\n”分配在.rodata段的末梢,字符串的字面值是只读的,约等于在全局成效域定义了1个const数组:

  a是二个GLOBAL符号,而b被static关键字修饰了,导致它变成2个LOCAL的符号,所以static在此地的效用是证明b那个标记为LOCAL的,不被链接器处理,如若把多少个对象文件链接在一齐,LOCAL的记号只辛亏某2个目的文件中定义和动用,而不可能定义在八个对象文件中却在另1个目的文件中采用。二个函数定义前面也得以用static修饰,表示那一个函数名符号是LOCAL的。

#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语法:

  今后还剩余函数中的b和c这五个变量未有分析。函数的参数和某些变量是分配在栈上的,b是数组也同样,也是分配在栈上的,我们看main函数的反汇编代码:

  还有2个a.1589是怎么着啊?它是main函数中的static int
a。函数中的static变量差别于局部变量,它并不是在调用函数时分配在函数重回时释放,而是像全局变量壹样静态分配,所以用”static”这么些词。另1方面,函数中的static变量的功用域和一些变量一样只在函数中起成效,比如main函数中的a那几个变量名只在main函数中起效果,所以编写翻译器给它的号子加了3个后缀以便和大局变量a以及任何函数的变量a区分开。

  下边介绍的 const 关键字不是3个Storage Class
Specifier,固然看起来它也修饰三个变量注脚,不过在后来介绍的更扑朔迷离的宣示中
const 在语法结构中允许出现的职位和Storage Class
Specifier是不完全一样的。 const 和今后要介绍的 restrict 和 volatile
关键字属于同一类语法成分,称为类型限定符(Type Qualifier)。

  变量的生存期(Storage Duration,也许Lifetime)分为以下几类:

  C语言 4

  C语言 5

  效用域这些定义使用于所有标识符,而不光是变量,C语言的功能域分为一下几类:

  C语言 6

  存款和储蓄类修饰符(Storage Class
Specifier)有以下三种首要字,能够修饰变量或函数注明:

  • 函数功效域,标识符在全体函数中都有效。唯有语句标号属于函数成效域。标号在函数中不须求先表明后选择,在前面用二个goto语句也能够跳转到后边的某部标号,但仅限于同二个函数之中。
  • 文本成效域,
    标识符从它申明的岗位上马通晓这么些程序文件的末段都使得。例如上例中main函数外面包车型客车A、a、b、c还有main也算,printf其实是在stdio.h中宣称的被含有到这一个程序文件中了,所以也算文件功效域的。
  • 块效率域,
    标识符位于1对{}括号中(函数体或语句块),从它表明的岗位上马到右}括号之间有效。例如上例中main函数里的a、b、c,别的,函数定义中的形参也算块效率域的,从注脚的地方上马到函数末尾之间有效。
  • C语言,函数原型作用域,
    标识符出现在函数原型中,这些函数原型只是2个注明而不是概念(未有函数体),那么标识符从申明的地方上马到在这些原型末尾之间有效。例如int
    foo(int a, int b);中的a和b。

  即便栈是从高地址向低地址增加的,但数组总是从低地址向高地址排列的,按从低地址到高地址的相继依次是b[0]、b[1]、b[2]……

  程序加载运维时,.rodata段和.text段日常合并到二个Segment中,操作系统将以此Segment只读保护起来,防止意外改写。那一点从readelf的出口也得以看出来:

  • static
    ,用它修饰的变量的储存空间是静态分配的,用它修饰的文本成效域的变量或函数具有Internal
    Linkage。
  • auto
    ,用它修饰的变量在函数调用时自动在栈上分配存款和储蓄空间,函数再次来到时自动释放,例如上例中
    main 函数里的 b 其实便是用 auto 修饰的,只但是 auto 能够不难不写,
    auto 不能够修饰文件功用域的变量。
  • register ,编写翻译器对于用 register
    修饰的变量会尽量分配二个专门的寄存器来囤积,但一旦实在分配不开寄存器,编写翻译器就把它当
    auto 变量处理了, register
    不可能修饰文件功用域的变量。今后貌似编写翻译器的优化都做得很好了,它自身会想艺术有效地使用CPU的寄存器,所以未来register 关键字也用得相比较少了。
  • extern
    ,上边讲过,链接属性是依照2个标识符数11遍声称时是或不是表示同八个变量或函数来分类的,
    extern 关键字就用来数十四回扬言同一个标识符,下1章再详尽介绍它的用法。
  • typedef ,它并不是用来修饰变量的,而是定义二个品类名。typedef
    在语法结构中出现的岗位和是面多少个相当重要字壹样,也是修饰变量定义的,所以从语法(而不是语义)的角度把它和前面多少个基本点字归类到1块。

  C语言 7

  • 外表链接(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) 。除上述气象之外的标识符都属于No
    Linkage的,例如函数的部分变量以及不意味变量和函数的别样标识符。

  .bss段从地址0x804a0二4初叶,长度为0xc,也正是到地址0x80肆a030收场。变量c位于那一个段。从地方的readelf输出可以看到.data和.bss在加载时合并到一个Segment中,这一个Segment是可读写的。.bss段和.data段的不相同之处在于.bss段在文件中不占存款和储蓄空间,在加载时那一个段用0填充。所以全局变量要是不开端化则初值为0,也分配在.bss段。

  变量A用const修饰,表示A是只读的,不可修改,它被分配的地点是0x8048540,从readelf的输出能够见到那一个地址位于.rodata段:

  C语言 8