**前言:**
前日,在一次C++課程上,劉老師在舉例說明構造函數和析構函數的功能時,提到了全局變量初始化時的構造函數的行為。構造函數在main函數之前初始化全局變量。當然在C++下我是深信不疑的。但隨后老師聲稱C語言下的全局變量也是如此,因為C沒有構造和析構函數,所以我們無法看到這一過程,在C++下可以在構造和析構函數中向屏幕打印信息,進而可以觀察全局變量的初始化和生存期。
這個觀點無疑使我心頭一震,作為C的癡迷者,長期以來在我頭腦中的印象是,全局變量在編譯期就完成初始化了。難道我的觀念是錯誤的?!難道C真的也是在main函數之前,在程序運行初期才初始化?!
于是我翻看了《C語言參考手冊》這本書上沒有明確的答案,再翻看著名的K&R的《C程序設計語言》中只有括號里面的一句話“在概念上.......”也是含糊其辭。(現在想想這個問題可能和編譯器有關,所以丹爺爺也沒說明太多)
在網上查詢了一下,關于這個問題,持什么觀點的都有,沒有一個權威的答案。
只能靠自己了,動手實驗!
**先給出我的結論**:
C和C++中的一般全局變量(不包括類class)是在編譯期確定的初始值,而不是在程序運行時,在main函數之前初始化的。
C++中的類的全局變量是在程序運行時,在main函數之前初始化的。
預熱知識:
C或者C++語言,明面上的入口函數是main(argc,argv),或者tmain、wmain、WinMain等等。但實際上,是C Runtime的startup代碼中的void?mainCRTStartup(void)函數,調用了編程者寫的main函數。這個函數定義在VisualC++安裝目錄的crt\src\目錄下的某個.c文件中(視VC++的版本不同,存放的文件也不同)。它在執行一些初始化操作,如獲取命令行參數、獲取環境變量值、初始化全局變量、初始化io的所需各項準備之后,調用main(argc,argv)。main函數返回后,mainCRTStartup還需要調用全局變量的析構函數或者atexit()所登記的一些函數。往深里說,是在鏈接生成可執行文件時,告訴鏈接器這個可執行文件的entry就是mainCRTStartup。當然,我們也可以對編譯器進行設置,使其不插入mainCRTStartup函數代碼
以VC++6.0為例設置:Project->Settings->Link 在Category中選擇Output,在Entry-point symbol中填上main 即可。
-------------------------
**實驗一:**
1,?????C語言環境下:
**實驗準備:**
~~~
int a ;
int main(void)
{
return a+3 ;
}
~~~
在編譯器中設置入口函數為main(具體方法見上面)
這樣,我們讓編譯器生成的程序,直接從main函數中進入,而不是先執行mainCRTStartup函數做一些準備工作。
**結果預測:**
這樣,如果函數返回的是3,則說明此全局變量是在編譯期就被初始化為0了,如果函數返回的是其它數字,則說明此全局變量是在程序運行時,main函數運行前進行的初始化。
**實驗結果:**
進入控制臺(運行cmd命令),運行編譯后的程序(因為程序沒有向屏幕輸出結果,我們看不到任何現象),繼續輸入命令:echo? %ERRORLEVEL% 則顯示3,此即為函數的返回值。
(echo是顯示其后的值,系統把前面運行的程序的返回值放在%ERRORLEVEL%中,故我們可以通過此方法獲得主函數的返回值)
同理:對于結構體全局變量
~~~
struct A
{
int a ;
} sTest;
int main(void)
{
return sTest.a+3 ;
}
~~~
函數也返回3.
**實驗結論:**
**在C語言中,全局變量是在編譯期完成初始化的。**
(在本實驗中我們沒有使用I/O函數把結果打印出來,因為I/O函數的調用之前必須要初始化內存中的某堆空間,而這個工作是由main函數之前的mainCRTStartup函數來做的。而我們設置讓編譯器跳過這個函數,故會在運行時出錯。)
**實驗二:**
C++語言環境下
**實驗準備:**
~~~
class A
{
public:
int a ;
A(){a=10;}
~A(){}
} ;
A cTest ;
int main(void)
{
return cTest.a ;
}
~~~
**結果預測:**
這樣,如果函數返回的是0,則說明此全局變量是在編譯期就被初始化為0了,如果函數返回的是其它數字,則說明此全局變量是在程序運行時,main函數運行前進行的初始化。
**實驗結果:**
在編譯器中設置入口函數為main,主函數返回一個其他值
在編譯器中設置入口函數為默認,主函數返回值為10
**實驗結論:**
**在C++中,類(class)的全局變量是在程序運行期,main函數開始之前,調用類的構造函數完成初始化的。**
同理:
把C中的代碼放到C++下實驗
~~~
int a ;
int main(void)
{
return a+3 ;
}
~~~
結果與C的結果相同。
說明:**在C++中一般全局變量的初始化(類除外),是在編譯期完成的,而不是在運行期完成。(與C語言規則相同)**
mainCRTStartup函數不管一般全局變量的初始化,它管理類(class)的全局變量的初始化,調用類的析構函數。
編譯器會在編譯時,初始化一般全局變量為0.
**另:**具有全局生命期的局部靜態變量的初始化,與局部變量相同都是在運行時,執行到該初始化語句完成初始化的,只是局部靜態變量只初始化一次。
**后記:**
1、程序不是從主函數開始執行的,而是先要執行一些啟動代碼。(現在明白為什么要在在嵌入式軟件編程時要在工程中添加類似于75x_init.s和75x_vect.s這兩個匯編文件了吧)
2、你應該給主函數以返回值。實際上標準C只規定了兩種形式的main函數:
int main( void ) 和 int main(int argc, char *argv[])
main返回0,告訴系統程序正常終止,返回非零值告訴系統程序異常關閉.
其作用:**我們可以利用程序的返回值,控制要不要執行下一個程序。**
例:程序名&&DOS命令
前面的程序正常執行后才執行后面的DOS命令。當然我們也可以用其它的邏輯符把程序和命令組織起來,來實現復雜的功能。
(UNIX中的shell命令也有類似功能)