多玩YY語音的面試題:C++中如何在main()函數之前執行操作?
第一反應main()函數是所有函數執行的開始。但是問題是main()函數執行之前如何執行呢?
聯想到MFC裡面的 C**App類的theApp對象,其執行順序就在main函數之前。道理相通,順理推下,能夠想到:如果在main函數之前聲明一個類的全域的對象。那麼其執行順序,根據全域對象的生存期和範圍,肯定先於main函數。
樣本如下:
class simpleClass{public: simpleClass( ) { cout << "simpleClass constructor.." << endl; //step2 }}; simpleClass g_objectSimple; //step1全域對象int _tmain(int argc, _TCHAR* argv[]) //step3{ return 0;}可單步調試查看執行順序為step1、step2、step3。
考慮到全域對象,同理會進一步思考靜態對象的範圍。將上述樣本進一步擴充如下:
class simpleClass{public: simpleClass( ) { cout << "simpleClass constructor.." << endl; //step2 }};class simpleClassTwo{public: static simpleClass m_sSimpleClass;};simpleClass simpleClassTwo::m_sSimpleClass = simpleClass(); //step1 靜態對象int _tmain(int argc, _TCHAR* argv[]) //step3{ return 0;}可單步調試查看執行順序為step1、step2、step3。
至此,我們可以總結出:定義在main( )函數之前的全域對象、靜態對象的建構函式在main( )函數之前執行。
再進一步思考,既然可以在main( )函數之前執行全域、靜態對象的建構函式。那麼有沒有函數在main( )函數之後執行呢?
有的,onexit函數。原型如下:
_onexit_t _onexit(
_onexit_t function
);
_onexit_t_m _onexit_m(
_onexit_t_m function
);
解釋:The _onexit function is passed the address of a function (function) to be called when the program terminates normally. Successive calls to _onexit create a register of functions that are executed in LIFO (last-in-first-out)
order. The functions passed to _onexit cannot take parameters.
核心點:
1) 執行期——程式執行終止的時候;
2) 傳遞參數——函數的地址,即函數指標;
3) 執行順序——後進先出。
_onexit is a Microsoft extension. For ANSI portability, use atexit. The _onexit_m version of the function is for mixed mode use.
onexit是微軟的擴充版本,標準C++裡面應用的是atexit。
【MSDN】樣本:
#include <stdlib.h>#include <stdio.h>/* Prototypes */int fn1(void), fn2(void), fn3(void), fn4 (void);int main( void ){ _onexit( fn1 ); _onexit( fn2 ); _onexit( fn3 ); _onexit( fn4 ); printf( "This is executed first.\n" );} int fn1(){ printf( "next.\n" ); return 0;} int fn2(){ printf( "executed " ); return 0;} int fn3(){ printf( "is " ); return 0;} int fn4(){ printf( "This " ); return 0;}
執行結果如下:
顯然,讀程式可以看出main( )函數執行完畢後又執行了onexit( )函數。
還有沒有其他特殊的情況呢?持續探討、更新中……
2013-4-23更新
補充:控制台介面應用程式是如何啟動的?
以Windows平台為例,用MicrosoftVisual Studio 來建立一個應用程式項目時,整合式開發環境會設定各種串連開關,使連結器將子系統的正確類型嵌入最終產生的可執行檔(executable)中。對於控制台介面應用程式,這個連結器的開關是/SUBSYSTEM:CONSOLE。
使用者啟動應用程式時,作業系統的載入程式(loader)會檢查可執行檔映像的檔案頭,並擷取這個子系統值。如果如所示,子系統值為/SUBSYSTEM:CONSOLE,載入程式會自動確保命令符啟動程式的時候有一個可用的文本控制台視窗。另外,如有必要,如從資源管理員啟動CUI程式的時候,會建立一個新視窗。
在串連可執行檔時,連結器將選擇正確的C/C++運行庫啟動函數。如果指定了/SUBSYSTEM:CONSOLE,會執行如下步驟:
所有C/C++運行庫啟動函數所做的事情基本都是一樣的,區別1在於它們處理字串的類型(ANSI字串或者是Unicode字串),區別2在於它們調用的是哪個進入點函數。
Visual C++內建C++運行庫的原始碼啟動函數的用途簡單概括如下:
1) 擷取新進程完整命令列的一個指標;
2) 擷取指向新進程的環境變數的一個指標;
3) 初始化C/C++運行庫的全域變數。
4) 初始化C運行庫記憶體配置函數(malloc和calloc)和其他底層的I/O常式使用的堆。
5) 調用所有全域和靜態C++類對象的建構函式。
第5條也就解釋了為什麼在main()函數前運行全域、靜態變數的建構函式了。
進入點函數返回後,啟動函數將調用C運行庫函數exit,向其傳遞傳回值(nMainRetVal)。exit函數執行以下任務:
1) 調用_onexit函數所有調用所註冊的任何一個函數;
2) 調用所有全域和靜態C++類對象的解構函式;
3) 在DEBUG產生中,如果設定了_CRTDBG_LEAK_CHECK_DF標誌,就通過調用_CrtDumpMemoryLeaks函數產生記憶體泄露報告。
4) 叫用作業系統的ExitProcess函數,向其傳入nMainRetVal。這會導致作業系統殺死我們的進程,並設定它的結束代碼。
exit( )函數的執行的先後順序為:1)、2)、3)、4)。
如下上述程式的合體,驗證了exit函數的執行順序。
先全域物件建構函數,然後執行main函數列印語句,再執行_onexit註冊函數;最後執行全域對象解構函式。
#include <stdlib.h>#include <stdio.h> class simpleClass{public: simpleClass() { cout<< "simpleClass constructor.." << endl; } ~simpleClass() { cout<< "~SimpleClass Destructor.." << endl; }}; simpleClass g_objectSimple; //1全域對象 /* Prototypes */int fn1(void), fn2(void), fn3(void), fn4(void); int main( void ){ _onexit(fn1 ); _onexit(fn2 ); _onexit(fn3 ); _onexit(fn4 ); printf("This is executed first.\n" );} int fn1(){ printf("next.\n" ); return0;} int fn2(){ printf("executed " ); return0;} int fn3(){ printf("is " ); return0;} int fn4(){ printf("This " ); return0;}
2013-4-18