初探编译器static、const之实现原理(转)

转一篇不错的文章,以作学习。。。。。

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

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

引用:
#include <iostream.h>
void fun(int i)
{
static int n = i ;
int *p = &n ;
cout << n << endl ;
++n ;
//
// 等下我们要在这写代码,让static int n
// 每次进这个函数都初始化一次
//
}
int main(void)
{
for (int i(10); i > 0; --i)
{
       fun(i) ;
}
return 0;
}

程序的输出结果是:

引用:
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的话,说明已经初始化了,下次再进来的时候就不用初始化了,为了验证我们的猜测,我们现在在函数里面加几句语言,修改那个值。

引用:
void fun(int i)
{
static int n = i ;
int *p = &n ;
cout << n << endl ;
++n ;
//
// 等下我们要在这写代码,让static int n
// 每次进这个函数都初始化一次
--p ;
*p = 0 ;
//
}

写完上面二句,我们执行一下,是不是发现执行结果已经和上面的不同了,每次进函数都会对static int n进行赋初值操作。

下面我们再来看2个static类型的情况,在上面的代码中,我们再加一个 static变量;

引用:
void fun(int i)
{
static int n1 = i ;
static int n2 = i ;
int *p = &n1 ;
cout << n1 << endl ;
++n1 ;
//
// 等下我们要在这写代码,让static int n
// 每次进这个函数都初始化一次
--p ;
*p = 0 ;
//
}

还是继续调戏。
二个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进行初始化,对于用常量初始化的情况是怎么样的呢?
我们将上面的代码改成:

引用:
#include <iostream.h>
void fun(int i)
{
static int n1 = 0x12345678 ;  
int *p = &n1 ;
cout << *p << endl ;
}
int main(void)
{
for (int i(10); i > 0; --i)
{
       fun(i) ;
}
return 0;
}

当指针取到值之后,我们结束调试。我这里的地址值是0x0042ad64。
好了,我们结束调戏,用winhex打开生成的可执行文件,按Alt+g跳到n的地址,这里要减去0x400000,也就是2ad64。是不是看到我们的初值了。
因为intel使用的是小端法,所以我们看到的值是反过来的。


下面我们再来探索一下const的原理;
下面看一个程序段

引用:
#include <iostream.h>
int main(void)
{
const int n = 1 ;
int *p = (int *)&n ;
*p = 0 ;
cout << n << endl ;
cout << *p << endl ;
return 0;
}

我们执行一下,结果是不是和我们所期望的不同呢,我们在第一行下断点,一条一条的执行。
确认每一步操作是否正确。
当执行到*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变量分配空间呢,这个留给大家思考吧,或者给你们设计编译器的话,你们也会这样实现的!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值