天天看點

const與static原理

作者:evilknight摘自邪惡八進制

編譯環境: 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變量的标志位,并把标志位的值改為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)    //此處0042e070直接替換了n

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

第一篇原文連結,感謝原作者

常量有沒有存儲空間,或者隻是編譯時的符号而已?

不一定。

在C中,define常量是預處理階段的工作,其不占據記憶體。但是const常量總是占據記憶體

在C++中,const常量是否占據存儲空間取決于是否有引用該常量位址的代碼。C++對于const預設為内部連結,是放置在符号表中的,是以const常量定義通常都放在頭檔案中,即使配置設定記憶體也不會出現連結錯誤。

若不引用常量對應的位址,則不會為其配置設定空間。

(c++ : 對于基本資料類型的常量,編譯器會把它放到符号表中而不配置設定存儲空間,

而ADT/UDT的const對象則需要配置設定存儲空間(大對象)。還有一些情況下也需要配置設定存儲空間,

例如強制聲明為extern的符号常量或取符号常量的位址等操作)

Const是用來替換define的,是以其必須能夠放在頭檔案中,在C++中const變量是預設為内部連結的,即在其他檔案中看不到本檔案定義的const變量,是以連結不會出現問題。Const變量在定義的時候必須初始化,除非顯式的指定其為extern的。通常C++中會盡量避免為const變量配置設定記憶體storage的,而是在編譯階段将其儲存在符号表symbol table中。當用extern修飾const變量或引用其位址時,将強制為其配置設定記憶體,因為extern表示采用外部連結,是以其必須有某個位址儲存其值。

#include <iostream.h>

const int i=100;   //無法找到i的符号,因為沒有為其配置設定存儲空間。

const int j=i+100;   //強迫編譯器為常量配置設定存儲空間

long address=(long)&j;

char buf[j+10];

int main(int argc, char* argv[])

{

 const char c=cin.get();

 const char d=3;  // 局部變量棧區

 char test1[d+10];

 //char test2[c+10];  error const char c必須到運作時刻動态擷取其初值。char test2[c+10]編譯無法通過,因為無法确定c就無法确定數組長度。

 const char c2=c-'a'+'A';

 cout<<c<<" "<<c2<<endl;

 return 0;

}

第二篇原文連結,感謝原作者

定義const 隻讀變量,具有不可變性。

例如:

const intMax=100;

intArray[Max];

這裡請在Visual C++6.0 裡分别建立.c 檔案和.cpp 檔案測試一下。你會發現在.c 檔案中,

編譯器會提示出錯,而在.cpp 檔案中則順利運作。為什麼呢?我們知道定義一個數組必須指

定其元素的個數。這也從側面證明在C 語言中,const 修飾的Max 仍然是變量,隻不過是隻

讀屬性罷了;而在C++裡,擴充了const 的含義,這裡就不讨論了。

編譯器通常不為普通const 隻讀變量配置設定存儲空間,而是将它們儲存在符号表中,這使

得它成為一個編譯期間的值,沒有了存儲與讀記憶體的操作,使得它的效率也很高。

例如:

#define M 3 //宏常量

const int N=5; //此時并未将N 放入記憶體中

......

int i=N; //此時為N 配置設定記憶體,以後不再配置設定!

int I=M; //預編譯期間進行宏替換,配置設定記憶體

int j=N; //沒有記憶體配置設定

int J=M; //再進行宏替換,又一次配置設定記憶體!

const 定義的隻讀變量從彙編的角度來看,隻是給出了對應的記憶體位址,而不是象#define

一樣給出的是立即數,是以,const 定義的隻讀變量在程式運作過程中隻有一份拷貝(因為

它是全局的隻讀變量,存放在靜态區),而#define 定義的宏常量在記憶體中有若幹個拷貝。

#define 宏是在預編譯階段進行替換,而const 修飾的隻讀變量是在編譯的時候确定其值。

第三段原文連結,感謝原作者