接着上篇继续对C语言做一些探索,这篇主要分析C语言中一些基本特性的实现。包括全局变量,静态变量,以及他们是否初始化以及链接属性做一些解析

##一、全局变量 全局变量有初始化或未初始化之分,初始化了的全局变量保存在data段,未初始化全局变量保存在BSS段, data段和bss段都是程序的数据段。

对于初始化的全局变量,看下面程序:

int global1 = 100;

int main()
{
     global1 = 101;
    
     extern int global2;
     global2 = 201;
     return 0;
}

int global2 = 200;

对比汇编代码来分析:

    .file     "demo.c"
.globl global1                      ;声明全局符号 global1
     .data                          ;位于数据段
     .align 4
     .type     global1, @object
     .size     global1, 4           ;空间大小字节数
global1:                            ;符号global1定义
     .long     100                  ;初始值
     .text
.globl main                         ;声明全局符号main
     .type     main, @function
main:
     pushl     %ebp
     movl     %esp, %ebp
     subl     $8, %esp
     andl     $-16, %esp
     movl     $0, %eax
     addl     $15, %eax
     addl     $15, %eax
     shrl     $4, %eax
     sall     $4, %eax
     subl     %eax, %esp
     movl     $101, global1         ;通过符号global1访问全局变量global1
     movl     $201, global2         ;通过符号global2访问全局变量global2
     movl     $0, %eax
     leave
     ret
     .size     main, .-main
.globl global2                      ;全局符号global2
     .data                          ;位于数据段
     .align 4
     .type     global2, @object
     .size     global2, 4           ;字节数
global2:                            ;符号global2定义
     .long     200                  ;初始值
     .section     .note.GNU-stack,"",@progbits
     .ident     "GCC: (GNU) 3.4.4 20050721 (Red Hat 3.4.4-2)"

对于未初始化的全局变量:

int global1;

int main()
{
     global1 = 101;
     
     extern int global2;
     global2 = 201;
     return 0;
}

int global2;

生成汇编代码:

     .file     "demo.c"
     .text                           ;注意这里没有数据段,并没有为未初始化的全局变量分配空间,而是运行时在内存映像中分配
.globl main
     .type     main, @function
main:
     pushl     %ebp
     movl     %esp, %ebp
     subl     $8, %esp
     andl     $-16, %esp
     movl     $0, %eax
     addl     $15, %eax
     addl     $15, %eax
     shrl     $4, %eax
     sall     $4, %eax
     subl     %eax, %esp
     movl     $101, global1         ;通过符号访问全局变量,这个符号可以在之后,或其他文件中定义
     movl     $201, global2
     movl     $0, %eax
     leave
     ret
     .size     main, .-main
     .comm     global1,4,4          ;标明这是个未初始化全局变量,声明空间大小,调入内存时在bss段分配空间
     .comm     global2,4,4
     .section     .note.GNU-stack,"",@progbits
     .ident     "GCC: (GNU) 3.4.4 20050721 (Red Hat 3.4.4-2)"

可以得出结论:全局变量独立于函数存在,所有全局变量都可以通过符号访问(当然这里有链接属性,下文会说到), 并且在运行期,其地址不变。

##二、跨编译单元如何链接 看下面这个程序链接出错,找不符号a,print, 但生成汇编代码并没有问题。这是因为编译的时候只是把符号 地址标记记录下来,等到链接的时候会去所有的编译单元中去找符号的定义,如果符号定义了才会变成具体 的地址。如果链接的时候所有符号地址都有定义,那么生成可执行文件。如果有不确定地址的符号,则链接出错。

#include<stdio.h>

int main()
{
    extern int a;
    print("a = %d\n", a);

    return 0;
}

编译出错信息如下:

$ gcc demo.c  
/tmp/cc6toKHG.o(.text+0x21): In function `main':
: undefined reference to `a'
/tmp/cc6toKHG.o(.text+0x2b): In function `main':
: undefined reference to `print'
collect2: ld returned 1 exit status

但是可以正确的生成汇编代码:

     .file     "demo.c"
     .section     .rodata             
.LC0:                               ;字符串常量通过符号.LC0访问,放在代码段,只读区域
     .string     "a = %d\n"
     .text
.globl main                         ;全局符号main,编译后会进入符号表
     .type     main, @function
main:                                      
     pushl     %ebp
     movl     %esp, %ebp
     subl     $8, %esp
     andl     $-16, %esp
     movl     $0, %eax
     addl     $15, %eax
     addl     $15, %eax
     shrl     $4, %eax
     sall     $4, %eax
     subl     %eax, %esp
     subl     $8, %esp
     pushl     a                    ;通过符号a访问全局变量,这里a并没有定义,所以链接当然出错了
     pushl     $.LC0
     call     print                 ;通过符号print访问全局函数,这里print并没有定义
     addl     $16, %esp
     movl     $0, %eax
     leave
     ret
     .size     main, .-main
     .section     .note.GNU-stack,"",@progbits
     .ident     "GCC: (GNU) 3.4.4 20050721 (Red Hat 3.4.4-2)"

##三、static全局变量 这里说下全局变量的链接属性,全局变量的默认是extern的,可以参照第一节对全局变量的描述。全局变量 最终存放在数据段,整个程序的所有文件都能访问,如果加上static则表明值能被当前文件访问。这就是所谓 全局变量的static链接属性。

对于初始化了的static全局变量

static int a = 10;

int main()
{
    a = 20;

    return 0;
}

对应的汇编代码:

     .file     "demo.c"
     .data                      ;数据段
     .align 4
     .type     a, @object       ;数据段为a分配空间
     .size     a, 4
a:                              ;符号a的定义,没有global表明是局部不会进入符号表
     .long     10               ;初始值
     .text
.globl main
     .type     main, @function
main:
     pushl     %ebp
     ;.....

对比一下,初始化了的extern全局变量

int a = 10;

int main()
{
    a = 20;

    return 0;
}

汇编代码:

     .file     "demo.c"
.globl a                    ;声明全局符号a,编译后a会进入符号表
     .data                  ;数据段
     .align 4
     .type     a, @object   ;数据段为a分配空间
     .size     a, 4
a:                          ;符号a 的定义                       
     .long     10           ;初始值
     .text
.globl main
     .type     main, @function
main:
     pushl     %ebp
     ;......

那么对于未初始化的static全局变量呢?

static int a;

int main()
{
    a = 20;

    return 0;
}

汇编代码

     .file     "demo.c"
     .text                      ;未初始化全局变量编译后,并不分配空间,而是运行时bss分配
.globl main
     .type     main, @function
main:
     pushl     %ebp
     movl     %esp, %ebp
     subl     $8, %esp
     andl     $-16, %esp
     movl     $0, %eax
     addl     $15, %eax
     addl     $15, %eax
     shrl     $4, %eax
     sall     $4, %eax
     subl     %eax, %esp
     movl     $20, a
     movl     $0, %eax
     leave
     ret
     .size     main, .-main
     .local     a               ;声明a 为局部符号,不进入符号表
     .comm     a,4,4            ;未初始化符号a,以及所需空间
     .section     .note.GNU-stack,"",@progbits
     .ident     "GCC: (GNU) 3.4.4 20050721 (Red Hat 3.4.4-2)"

和extern的未初始化全局变量对比下

int a;

int main()
{
    a = 20;

    return 0;
}

汇编代码:

     .file     "demo.c"
     .text
.globl main
     .type     main, @function
main:
     pushl     %ebp
     movl     %esp, %ebp
     subl     $8, %esp
     andl     $-16, %esp
     movl     $0, %eax
     addl     $15, %eax
     addl     $15, %eax
     shrl     $4, %eax
     sall     $4, %eax
     subl     %eax, %esp
     movl     $20, a
     movl     $0, %eax
     leave
     ret
     .size     main, .-main
     .comm     a,4,4          ;未初始全局变量a,空间大小,没有local声明,进入符号表
     .section     .note.GNU-stack,"",@progbits
     .ident     "GCC: (GNU) 3.4.4 20050721 (Red Hat 3.4.4-2)"

由此可见,static全局变量和extern全局变量存储方式基本一样,最大区别是extern的会进入符号表,其他编译 单元可以链接, 而statc则是本文件的一个符号,本文件的所有函数都可以访问。

##四、static局部变量 static局部变量具备外部变量的生存期,但作用域却和局部变量一样,离开函数就不能访问。 对于初始化的static局部变量

#include<stdio.h>

int fun()
{
     static int a = 10;
     return (++a);
}
int main()
{
     printf("a = %d\n",fun());
     printf("a = %d\n",fun());
}

汇编代码:

     .file     "demo.c"
     .data                          ;数据段
     .align 4
     .type     a.0, @object         ;符号a.0在.data 段分配空间
     .size     a.0, 4
a.0:                                ;符号a.0定义,没有global不进入符号表
     .long     10                   ;初始值
     .text
.globl fun
     .type     fun, @function
fun:
     pushl     %ebp
     movl     %esp, %ebp
     incl     a.0                   ;通过符号a.0访问static局部变量a,这样只用该函数知道a.0,其他地方通过a访问不到
     movl     a.0, %eax             ;返回值实际上方到寄存器eax,所以函数返回后,可以取到返回值
     leave
     ret
     .size     fun, .-fun
     .section     .rodata
.LC0:
     .string     "a = %d\n"
     .text
.globl main
     .type     main, @function
main:
    ;.....

对于未初始化的全局变量:

#include<stdio.h>

int fun()
{
     static int a;
     return (++a);
}
int main()
{
     printf("a = %d\n",fun());
     printf("a = %d\n",fun());
}

汇编代码:

     .file     "demo.c"
     .local     a.0                 ;声明 a.0为local  不进入符号表
     .comm     a.0,4,4              ;未初始化的符号 a.0,空间大小
     .text
.globl fun
     .type     fun, @function
fun:
     pushl     %ebp
     movl     %esp, %ebp
     incl     a.0                   ;通过符号a.0访问static局部变量a,这样只用该函数知道a.0,其他地方通过a访问不到
     movl     a.0, %eax
     leave
     ret
     .size     fun, .-fun
     .section     .rodata
.LC0:
     .string     "a = %d\n"
     .text
.globl main
     .type     main, @function
main:
    ;......

通过以上说明,实际上还是把static局部变量放在数据段存储(要么怎么可能在程序运行期间地址不变呢), static局部变量的存储和static全局变量的存储基本差不多。唯一的区别是符号名编译器会动点手脚 (这样出了函数就访问不了了),同时候多个函数中定义同名的static局部变量,实际上是不同的 符号名,大家互补干涉了。



-EOF-
Simple is beautiful博客,转载请注明出处