[转]: 初探编译器static、const之实现原理

 文章作者:evilknight
信息来源:邪恶八进制信息安全团队(www.eviloctal.com
编译环境: WinXP sp2 + VC6.0 SP 6

对于许多C/C++初学者,往往知道static变量只是被初始化一次,对于const变量,只知道他的值是不能被修改的,但是对于其实现却不知所有然。这里我以VC6.0 SP6为平台,揭开其编译器实现原理。
下面看一段程序:

C++代码
  1. #include <iostream.h>   
  2. void fun(int i)   
  3. {   
  4.     static int n = i ;   
  5.     int *p = &n ;   
  6.     cout << n << endl ;   
  7.     ++n ;   
  8.     //   
  9.     // 等下我们要在这写代码,让static int n   
  10.     // 每次进这个函数都初始化一次   
  11.     //   
  12. }   
  13. int main(void)   
  14. {   
  15.     for (int i(10); i > 0; --i)   
  16.     {   
  17.         fun(i) ;   
  18.     }   
  19.     return 0;   
  20. }  
程序的输出结果是:
10
11
12
13
14
15
16
17
18
19

下面我们调试一下,看下编译器如何实现:
我们在fun函数的第一行设一个断点。static int n = i ;所在行,按F5。
按Alt+6打开Memory。按F10单步执行,当p有值的时候,我们将他的值拖到Memory窗口,这时就会转到n所在的内存地址,可是这时static已经初始化了,我们不知道编译器对他做了什么操作了。这时我们重新开始调试,一般n的内存地址不会变的,还是在那里。
我这里以我这边的地址为例:

0042E058  00 00 00 00  ....
0042E05C  00 00 00 00  .... // 中间这个为n的内存地址
0042E060  00 00 00 00  ....

我们按F10单步执行一下一条语句(static int n = i ;)

0042E058  01 00 00 00  ....
0042E05C  0A 00 00 00  ....// n
0042E060  00 00 00 00  ....

执行完这条语句之后,除了n有了初值,上面有内存空间也有了变化。
我们接着按F5直接执行到那个断点处,再单步执行一下,发现这次只是n的值有变化,所以我们猜测上面的那个位可能是static的标志位,如果是0的话,说明没有初始化,如果是1的话,说明已经初始化了,下次再进来的时候就不用初始化了,为了验证我们的猜测,我们现在在函数里面加几句语言,修改那个值。

C++代码
  1. void fun(int i)   
  2. {   
  3.     static int n = i ;   
  4.     int *p = &n ;   
  5.     cout << n << endl ;   
  6.     ++n ;   
  7.     //   
  8.     // 等下我们要在这写代码,让static int n   
  9.     // 每次进这个函数都初始化一次   
  10.     --p ;   
  11.     *p = 0 ;   
  12.     //   
  13. }  
写完上面二句,我们执行一下,是不是发现执行结果已经和上面的不同了,每次进函数都会对static int n进行赋初值操作。

下面我们再来看2个static类型的情况,在上面的代码中,我们再加一个 static变量;
C++代码
  1. void fun(int i)   
  2. {   
  3.     static int n1 = i ;   
  4.     static int n2 = i ;   
  5.     int *p = &n1 ;   
  6.     cout << n1 << endl ;   
  7.     ++n1 ;   
  8.     //   
  9.     // 等下我们要在这写代码,让static int n   
  10.     // 每次进这个函数都初始化一次   
  11.     --p ;   
  12.     *p = 0 ;   
  13.     //   
  14. }  
还是继续调戏。
二个static变量初始化之前内存里面的值
0042E050  00 00 00 00  ....
0042E054  00 00 00 00  ....
0042E058  00 00 00 00  ....
0042E05C  00 00 00 00  .... // n1
0042E060  00 00 00 00  .... // n2
0042E064  00 00 00 00  ....

当执行完static int n1 = i ;语句之后,内存的值变成这样了

0042E058  01 00 00 00  ....
0042E05C  0A 00 00 00  ....
0042E060  00 00 00 00  ....

接着我们再单步执行
内存的值变成这样。

0042E058  03 00 00 00  ....
0042E05C  0A 00 00 00  ....
0042E060  0A 00 00 00  ....

这样就很明显了,编译器分别用一位来表示一个static变量是否已经始化。

上面是对于用变量对 static进行初始化,对于用常量初始化的情况是怎么样的呢?
我们将上面的代码改成:

 

C++代码
  1. #include <iostream.h>   
  2. void fun(int i)   
  3. {   
  4.     static int n1 = 0x12345678 ;    
  5.     int *p = &n1 ;   
  6.     cout << *p << endl ;   
  7. }   
  8. int main(void)   
  9. {   
  10.     for (int i(10); i > 0; --i)   
  11.     {   
  12.         fun(i) ;   
  13.     }   
  14.     return 0;   
  15. }  
当指针取到值之后,我们结束调试。我这里的地址值是0x0042ad64。
好了,我们结束调戏,用winhex打开生成的可执行文件,按Alt+g跳到n的地址,这里要减去0x400000,也就是2ad64。是不是看到我们的初值了。
因为intel使用的是小端法,所以我们看到的值是反过来的。


下面我们再来探索一下const的原理;
下面看一个程序段
C++代码
  1. #include <iostream.h>   
  2. int main(void)   
  3. {   
  4.     const int n = 1 ;   
  5.     int *p = (int *)&n ;   
  6.     *p = 0 ;   
  7.     cout << n << endl ;   
  8.     cout << *p << endl ;   
  9.     return 0;   
  10. }  
我们执行一下,结果是不是和我们所期望的不同呢,我们在第一行下断点,一条一条的执行。
确认每一步操作是否正确。
当执行到*p = 0的时候我们发现n内存所在的值已经变成0了,但是为什么执行结果令我们大失所望呢?
我们按Alt +8打开汇编窗口。
7:        cout << n << endl ;
0041161E   push        offset @ILT+40(endl) (0040102d)
00411623   push        1
00411625   mov         ecx,offset cout (0042e070)
0041162A   call        ostream::operator<< (004012a0)
0041162F   mov         ecx,eax
00411631   call        @ILT+30(ostream::operator<<) (00401023)
8:        cout << *p << endl ;
00411636   push        offset @ILT+40(endl) (0040102d)
0041163B   mov         edx,dword ptr [ebp-8]
0041163E   mov         eax,dword ptr [edx]
00411640   push        eax
00411641   mov         ecx,offset cout (0042e070)
00411646   call        ostream::operator<< (004012a0)
0041164B   mov         ecx,eax
0041164D   call        @ILT+30(ostream::operator<<) (00401023)

原来编译器将我们的const变量直接用常量给替换掉了!
可能有人会想,那这样为什么还要给const变量分配空间呢,这个留给大家思考吧,或者给你们设计编译器的话,你们也会这样实现的!


很不华丽的分割线


 

By xiaofengsheng

总结一下,

static变量只会被初始化一次, 是一个被放在全局静态区的一个变量. 它会用一个bit位来表示它是否已经被初始化.

 

const定义的变量一般会被分配一个空间;

但是对于这个const变量的引用(如const int const_i = 2; int i = const_i;), 编译器会在编译期间, 将他们换成常数.

By xiaofengsheng

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值