Chinaunix首页 | 论坛 | 博客
  • 博客访问: 18051
  • 博文数量: 9
  • 博客积分: 1400
  • 博客等级: 上尉
  • 技术积分: 95
  • 用 户 组: 普通用户
  • 注册时间: 2009-11-21 12:21
文章分类
文章存档

2011年(1)

2010年(3)

2009年(5)

我的朋友

分类: C/C++

2009-12-03 20:37:45

来看下面的一段程序:
在文件static_test_a.c中:
#include
#include "static_test_b.h"
int g_counter = 0;
 
int main(int args, char** argv)
{
       g_counter = 100;
       print();
       set_counter(200);
       printf("g_counter = %d at %s:%d\n", g_counter, __FILE__, __LINE__);
       return 0; 
}
 
在文件static_test_b.c中:
#include
 
int g_counter;
 
int get_counter()
{
       return g_counter;
}
 
void set_counter(int c)
{
       g_counter = c;
}
 
void print()
{
       printf("g_counter = %d at %s:%d\n", g_counter, __FILE__, __LINE__);
}
 
在文件static_test_b.h中:
#ifndef _STATIC_TEST_B_H_
#define _STATIC_TEST_B_H_
 
extern int get_counter();
extern void set_counter(int c);
extern void print();
 
#endif
 
读者不必首先到编译器去编译这段代码,先听我道来,看看是否与你的预期结果一样。
高手立马要说这段代码有语法错误,原因是定义了两个相同的全局变量int g_counter。是的,但是应该说是有语法错误,但是gcc居然能编译通过,只要不为文件static_test_b.c定义的g_counter赋初始值,很邪门!
来看编译器为我们生成的汇编代码,你就明白是怎么回事了(为了直奔主题,我使用了优化选项):
       .file  "static_test_a.c"
.globl _g_counter
       .bss
       .align 4
_g_counter:
       .space 4
       .text
       .p2align 4,,15
.globl _set_counter
       .def  _set_counter;  .scl  2;    .type       32;   .endef
_set_counter:
       pushl       %ebp
       movl       %esp, %ebp
       movl       8(%ebp), %eax
       popl %ebp
       movl       %eax, _g_counter
       ret
       .section .rdata,"dr"
LC0:
       .ascii "static_test_b.c\0"
LC1:
       .ascii "g_counter = %d at %s:%d\12\0"
       .text
       .p2align 4,,15
.globl _print
       .def  _print;     .scl  2;    .type       32;   .endef
_print:
       pushl       %ebp
       movl       $17, %ecx
       movl       %esp, %ebp
       subl $24, %esp
       movl       $LC0, %edx
       movl       _g_counter, %eax
       movl       %ecx, 12(%esp)
       movl       %edx, 8(%esp)
       movl       %eax, 4(%esp)
       movl       $LC1, (%esp)
       call  _printf
       leave
       ret
       .def  ___main; .scl  2;    .type       32;   .endef
       .section .rdata,"dr"
LC2:
       .ascii "static_test_a.c\0"
       .text
       .p2align 4,,15
.globl _main
       .def  _main;     .scl  2;    .type       32;   .endef
_main:
       pushl       %ebp
       movl       $16, %eax
       movl       %esp, %ebp
       subl $24, %esp
       andl $-16, %esp
       call  __alloca
       call  ___main
       movl       $LC1, (%esp)
       movl       $100, %eax
       movl       $17, %ecx
       movl       %eax, _g_counter
       movl       $LC0, %edx
       movl       $100, %eax
       movl       %ecx, 12(%esp)
       movl       %edx, 8(%esp)
       movl       %eax, 4(%esp)
       call  _printf
       movl       $LC1, (%esp)
       movl       $200, %ecx
       movl       $11, %edx
       movl       %ecx, _g_counter
       movl       $LC2, %eax
       movl       $200, %ecx
       movl       %edx, 12(%esp)
       movl       %eax, 8(%esp)
       movl       %ecx, 4(%esp)
       call  _printf
       leave
       xorl  %eax, %eax
       ret
       .p2align 4,,15
.globl _get_counter
       .def  _get_counter;  .scl  2;    .type       32;   .endef
_get_counter:
       pushl       %ebp
       movl       _g_counter, %eax
       movl       %esp, %ebp
       popl %ebp
       ret
       .def  _printf;    .scl  3;    .type       32;   .endef
       .def  _set_counter;  .scl  3;    .type       32;   .endef
       .def  _print;     .scl  3;    .type       32;   .endef
 
从上面的代码可以看到_g_counter只定义了一次,而且定义在BSS段。先赋初始值:
movl       $100, %eax
movl       %eax, _g_counter
 
       然后打印_g_counte的值:
              movl       $17, %ecx
              movl       $LC0, %edx
              movl       $100, %eax
              movl       %ecx, 12(%esp)
              movl       %edx, 8(%esp)
              movl       %eax, 4(%esp)
              call  _printf
 
       调用函数set_counter再次为_g_counter赋值:
              movl       $200, %ecx
              movl       %ecx, _g_counter
 
       最后再次打印_g_counte的值:
              movl       $11, %edx
              movl       $LC2, %eax
              movl       $200, %ecx
              movl       %edx, 12(%esp)
              movl       %eax, 8(%esp)
              movl       %ecx, 4(%esp)
              call  _printf
 
       从编译器生成的代码,我们大致可以明白这样一件事情,两次定义的g_counter被当成了一个变量来使用了。这点就很值得我们思考了,很多程序员在写代码的时候,随意的将代码写成了以上“风格”的代码,造成调试的时候不知道什么地方出错了!
       来看看加static修饰符的情况,这次情况有所不同了:
       在文件static_test_a.c中:
#include
 
static int g_counter = 0;
 
int main(int args, char** argv)
{
       g_counter = 100;
       print();
       set_counter(200);
       printf("g_counter = %d at %s:%d\n", g_counter, __FILE__, __LINE__);
       return 0; 
}
 
在文件static_test_b.c中:
#include
 
int g_counter;
 
int get_counter()
{
       return g_counter;
}
 
void set_counter(int c)
{
       g_counter = c;
}
 
void print()
{
       printf("g_counter = %d at %s:%d\n", g_counter, __FILE__, __LINE__);
}
 
在文件static_test_b.h中:
#ifndef _STATIC_TEST_B_H_
#define _STATIC_TEST_B_H_
 
extern int get_counter();
extern void set_counter(int c);
extern void print();
 
#endif
 
再次使用命令gcc -O3 -S static_test_a.c static_test_b.c -o static_test.s生成汇编代码:
       .file  "static_test_a.c"
.lcomm _g_counter.0,16
       .text
       .p2align 4,,15
.globl _set_counter
       .def  _set_counter;  .scl  2;    .type       32;   .endef
_set_counter:
       pushl       %ebp
       movl       %esp, %ebp
       movl       8(%ebp), %eax
       popl %ebp
       movl       %eax, _g_counter
       ret
       .section .rdata,"dr"
LC0:
       .ascii "static_test_b.c\0"
LC1:
       .ascii "g_counter = %d at %s:%d\12\0"
       .text
       .p2align 4,,15
.globl _print
       .def  _print;     .scl  2;    .type       32;   .endef
_print:
       pushl       %ebp
       movl       $17, %ecx
       movl       %esp, %ebp
       subl $24, %esp
       movl       $LC0, %edx
       movl       _g_counter, %eax
       movl       %ecx, 12(%esp)
       movl       %edx, 8(%esp)
       movl       %eax, 4(%esp)
       movl       $LC1, (%esp)
       call  _printf
       leave
       ret
       .def  ___main; .scl  2;    .type       32;   .endef
       .section .rdata,"dr"
LC2:
       .ascii "static_test_a.c\0"
       .text
       .p2align 4,,15
.globl _main
       .def  _main;     .scl  2;    .type       32;   .endef
_main:
       pushl       %ebp
       movl       $16, %eax
       movl       %esp, %ebp
       subl $24, %esp
       andl $-16, %esp
       call  __alloca
       call  ___main
       movl       $LC1, (%esp)
       movl       $100, %ecx
       movl       $LC0, %edx
       movl       %ecx, _g_counter.0
       movl       _g_counter, %ecx
       movl       $17, %eax
       movl       %edx, 8(%esp)
       movl       %ecx, 4(%esp)
       movl       %eax, 12(%esp)
       call  _printf
       movl       $LC1, (%esp)
       movl       $200, %eax
       movl       $11, %edx
       movl       %eax, _g_counter
       movl       _g_counter.0, %eax
       movl       $LC2, %ecx
       movl       %edx, 12(%esp)
       movl       %ecx, 8(%esp)
       movl       %eax, 4(%esp)
       call  _printf
       leave
       xorl  %eax, %eax
       ret
       .p2align 4,,15
.globl _get_counter
       .def  _get_counter;  .scl  2;    .type       32;   .endef
_get_counter:
       pushl       %ebp
       movl       _g_counter, %eax
       movl       %esp, %ebp
       popl %ebp
       ret
       .comm    _g_counter, 16       # 4
       .def  _printf;    .scl  3;    .type       32;   .endef
       .def  _set_counter;  .scl  3;    .type       32;   .endef
       .def  _print;     .scl  3;    .type       32;   .endef
 
这次定义了两个全局变量_g_counter.0(该变量在static_test_a.c定义)_g_counte(该变量在static_test_b.c定义),并通过.lcomm限定了_g_counter.0只能在本地访问,_g_counter可以全局访问,来看定义:
.lcomm _g_counter.0,16
.comm    _g_counter, 16
 
先赋初始值:
movl       $100, %ecx
movl       %ecx, _g_counter.0
 
       然后打印_g_counte的值,这次访问的应该是在static_test_b.c定义_g_counte
              movl       $LC1, (%esp)
              movl       $LC0, %edx
              movl       _g_counter, %ecx
              movl       $17, %eax
              movl       %edx, 8(%esp)
              movl       %ecx, 4(%esp)
              movl       %eax, 12(%esp)
              call  _printf
 
       调用函数set_counter_g_counter赋值,这次访问的也应该是在static_test_b.c定义_g_counte
              movl       $200, %eax
              movl       %eax, _g_counter
       最后打印_g_counter.0的值,这次访问的应该是在static_test_a.c定义_g_counte.0
              movl       $LC1, (%esp)
              movl       $200, %eax
              movl       $11, %edx
              movl       %eax, _g_counter
              movl       $LC2, %ecx
              movl       %edx, 12(%esp)
              movl       %ecx, 8(%esp)
              movl       %eax, 4(%esp)
              call  _printf
 
由此可以总结出,当我们在定义一个全局变量的时候一定要考虑清楚,这个全局变量是否需要在别的文件中访问,
1) 如果需要,则应该定义为如下风格:
int g_counter = 0;
当别的文件引用时,应该采用如下方式:
              extern int g_counter;
 
2) 如果不需要,则应该定义为如下风格:
       static int g_counter = 0;
 
static修饰局部变量的情况:
#include
 
int get_counter()
{
       static int g_counter;
      
       return g_counter++;
}
 
int main(int args, char** argv)
{
       int i;
      
       for (i = 0; i < 10; i++)
       {
              printf("g_counter = %d\n", get_counter());
       }
             
       return 0; 
}
 
编译器生成的汇编代码如下:
       .file  "static_test_a.c"
.lcomm g_counter.0,16
       .text
       .p2align 4,,15
.globl _get_counter
       .def  _get_counter;  .scl  2;    .type       32;   .endef
_get_counter:
       pushl       %ebp
       movl       g_counter.0, %eax
       movl       %esp, %ebp
       popl %ebp
       leal   1(%eax), %edx
       movl       %edx, g_counter.0
       ret
       .def  ___main; .scl  2;    .type       32;   .endef
       .section .rdata,"dr"
LC0:
       .ascii "g_counter = %d\12\0"
       .text
       .p2align 4,,15
.globl _main
       .def  _main;     .scl  2;    .type       32;   .endef
_main:
       pushl       %ebp
       movl       $16, %eax
       movl       %esp, %ebp
       pushl       %ebx
       subl $20, %esp
       andl $-16, %esp
       call  __alloca
       call  ___main
       movl       $9, %ebx
       .p2align 4,,15
L7:
       movl       $LC0, (%esp)
       movl       g_counter.0, %edx
       leal   1(%edx), %ecx
       movl       %ecx, g_counter.0
       movl       %edx, 4(%esp)
       call  _printf
       decl %ebx
       jns   L7
       movl       -4(%ebp), %ebx
       xorl  %eax, %eax
       leave
       ret
       .def  _printf;    .scl  3;    .type       32;   .endef
 
编译器定义g_counter如下:
.lcomm g_counter.0,16
 
这是一个只能在本地访问的全局变量,虽然我们在C代码中定义的g_counter是一个局部变量,但是编译器却将g_counter定义成了本地访问的全局变量,这就是说,我们可以将static修饰的局部变量视为static修饰的全局变量,但是这个本地的全局变量进一步受限——只能在定义的函数中访问,也就是说static修饰的局部变量的生命周期是全局。
 
再来看看static修饰函数的情况:
在文件static_test_a.c中:
#include
#include "static_test_b.h"
 
int g_counter = 0;
 
int main(int args, char** argv)
{
       g_counter = 100;
       print();
       set_counter(200);
       printf("g_counter = %d at %s:%d\n", g_counter, __FILE__, __LINE__);
       return 0; 
}
 
在文件static_test_b.c中:
#include
 
int g_counter;
 
int get_counter()
{
       return g_counter;
}
 
static void set_counter(int c)
{
       g_counter = c;
}
 
void print()
{
       printf("g_counter = %d at %s:%d\n", g_counter, __FILE__, __LINE__);
}
 
在文件static_test_b.h中:
#ifndef _STATIC_TEST_B_H_
#define _STATIC_TEST_B_H_
 
extern int get_counter();
extern void set_counter(int c);
extern void print();
 
#endif
 
来看看不添加优化选项原汁原味的汇编代码
              .file  "static_test_a.c"
.globl _g_counter
       .bss
       .align 4
_g_counter:
       .space 4
       .def  ___main; .scl  2;    .type       32;   .endef
       .section .rdata,"dr"
LC0:
       .ascii "static_test_a.c\0"
LC1:
       .ascii "g_counter = %d at %s:%d\12\0"
       .text
.globl _main
       .def  _main;     .scl  2;    .type       32;   .endef
_main:
       pushl       %ebp
       movl       %esp, %ebp
       subl $24, %esp
       andl $-16, %esp
       movl       $0, %eax
       addl $15, %eax
       addl $15, %eax
       shrl  $4, %eax
       sall   $4, %eax
       movl       %eax, -4(%ebp)
       movl       -4(%ebp), %eax
       call  __alloca
       call  ___main
       movl       $100, _g_counter
       call  _print
       movl       $200, (%esp)
       call  _set_counter
       movl       $11, 12(%esp)
       movl       $LC0, 8(%esp)
       movl       _g_counter, %eax
       movl       %eax, 4(%esp)
       movl       $LC1, (%esp)
       call  _printf
       movl       $0, %eax
       leave
       ret
.globl _get_counter
       .def  _get_counter;  .scl  2;    .type       32;   .endef
_get_counter:
       pushl       %ebp
       movl       %esp, %ebp
       movl       _g_counter, %eax
       popl %ebp
       ret
       .def  _set_counter.0;      .scl  3;    .type       32;   .endef
_set_counter.0:
       pushl       %ebp
       movl       %esp, %ebp
       movl       8(%ebp), %eax
       movl       %eax, _g_counter
       popl %ebp
       ret
       .section .rdata,"dr"
LC2:
       .ascii "static_test_b.c\0"
       .text
.globl _print
       .def  _print;     .scl  2;    .type       32;   .endef
_print:
       pushl       %ebp
       movl       %esp, %ebp
       subl $24, %esp
       movl       $17, 12(%esp)
       movl       $LC2, 8(%esp)
       movl       _g_counter, %eax
       movl       %eax, 4(%esp)
       movl       $LC1, (%esp)
       call  _printf
       leave
       ret
.globl _g_counter
       .bss
       .align 4
_g_counter:
       .space 4
       .def  _printf;    .scl  3;    .type       32;   .endef
       .def  _set_counter;  .scl  3;    .type       32;   .endef
       .def  _print;     .scl  3;    .type       32;   .endef
 
与上次编译器生成的汇编代码比较,可以看到,_set_counter.0没有被定义为globl函数,也就是说不能跨文件访问函数_set_counter.0
 
这次我们只看对set_counter的调用部分:
movl       $200, (%esp)
       call  _set_counter
 
还记得上次编译器生成对set_counter调用的汇编代码吗?列举如下:
       movl       $200, %ecx
 
       这是编译器优化后的代码,将函数调用直接转化成赋值。
 
由于这次编译器将函数_set_counter.0定义为局部变量,不能跨文件访问,没办法进行优化,所以只好老老实实的调用函数了。
movl       $200, (%esp)
call  _set_counter
 
这又造成了一个问题,在编译器链接的时候,由于找不到_set_counter的定义,无法进行链接,只好报告错误:
由此可以看到,在协作开发时,当你不打算把你的代码给别人调用时,可以采用static进行“隐藏”。
总之,对static修饰符的运用,还需要读者在实际运用中去体会。
 
阅读(385) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~