程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 初探編譯器static、const之實現原理

初探編譯器static、const之實現原理

編輯:關於C語言

文章作者: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變量分配空間呢,這個留給大家思考吧,或者給你們設計編譯器的話,你們也會這樣實現的!


End

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved