溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

c++程序真正的入口函數(shù)

發(fā)布時(shí)間:2020-06-16 08:09:09 來(lái)源:網(wǎng)絡(luò) 閱讀:354 作者:cxvdsdd 欄目:系統(tǒng)運(yùn)維

今天終于有時(shí)間來(lái)研究一下一個(gè)很大很大的工程編譯成一個(gè)exe和若干dll后,程序是如果執(zhí)行它的第一條指令的?操作系統(tǒng)以什么規(guī)則來(lái)找到應(yīng)該執(zhí)行的第一條指令(或說(shuō)如何找到第一個(gè)入口函數(shù)的)?

           我們以前寫windows控制臺(tái)程序時(shí),都是先寫個(gè)main()函數(shù),寫windows窗口程序時(shí),首先要寫winmain()函數(shù),然后再寫自己的邏輯;然后編譯,然后點(diǎn)擊exe就能運(yùn)行我們的程序了;并且認(rèn)為main或winmain是程序中第一個(gè)運(yùn)行的程序,也是必須存在的函數(shù),但深入了解window的編程就會(huì)發(fā)現(xiàn),main或winmain函數(shù)不是第一個(gè)運(yùn)行的函數(shù),在他們之前首先會(huì)運(yùn)行另一個(gè)函數(shù),會(huì)對(duì)全局變量進(jìn)行初始化和資源的分配,以及程序結(jié)束后會(huì)對(duì)資源的釋放。

           我們以前寫的程序在編譯器編譯成為一個(gè)模塊(可能是obj文件或其他形式),然后連接器會(huì)將一些所需要的庫(kù)文件和剛才編譯器生成的文件進(jìn)行連接,最終生成一個(gè)exe文件,在所連接的庫(kù)文件中就包含CRT運(yùn)行時(shí)庫(kù),這就是我們今天談?wù)摰闹鹘?。在運(yùn)行時(shí)庫(kù)里面有好一個(gè)已經(jīng)定義如下的函數(shù)函數(shù):

(1)mainCRTStartup(或 wmainCRTStartup) //使用 /SUBSYSTEM:CONSOLE 的應(yīng)用程序

(2)WinMainCRTStartup(或 wWinMainCRTStartup)//使用 /SUBSYSTEM:WINDOWS 的應(yīng)用程序

(3)_DllMainCRTStartup //調(diào)用 DllMain(如果存在),DllMain 必須用 __stdcall 來(lái)定義

其中w開頭的函數(shù)時(shí)unicode版本的,分割符‘//’后面的是入口點(diǎn)函數(shù)匹配的subsystem屬性設(shè)置。

如果未指定 /DLL 或 /SUBSYSTEM (也就是subsystem選項(xiàng))選項(xiàng),則鏈接器將根據(jù)是否定義了 main 或 WinMain 來(lái)選擇子系統(tǒng)和入口點(diǎn)。 函數(shù) main、WinMain 和 DllMain 是三種用戶定義的入口點(diǎn)形式。

在默認(rèn)情況下,如果你的程序中使用的是main()或_main()函數(shù),這連接器會(huì)將你的使用(1)中的函數(shù)連接到你的exe中;如果你的函數(shù)是以WinWain()函數(shù)開始的則連接器使用(2)中的函數(shù)連接進(jìn)exe中;如果我們寫的是DLL程序這連接進(jìn)DLL的是(3)中的函數(shù)。

        用我們寫的程序最終生成的exe執(zhí)行時(shí),一開始執(zhí)行的就是上面的函數(shù)之一,而不是我們程序所寫的main或WinMain等。那么連接器為什么要這樣做呢?這就是因?yàn)槲覀儗懙某绦虮仨氁褂玫礁鞣N各樣的運(yùn)行時(shí)庫(kù)函數(shù)才能正常工作,所有在執(zhí)行我們自己寫程序之前必須要先準(zhǔn)備好所需要的一切庫(kù),噢,明白了吧,之所以要連接它們是因?yàn)樗麄兗缲?fù)著很重要的使命,就是初始化好運(yùn)行時(shí)庫(kù),準(zhǔn)備我們的程序執(zhí)行時(shí)調(diào)用。

       那么這些函數(shù)具體做了什么呢?通過(guò)MSDN我們可以知道---它們會(huì)去進(jìn)一步調(diào)用其他函數(shù),使得C/C++ 運(yùn)行時(shí)庫(kù)代碼在靜態(tài)非局部變量上調(diào)用構(gòu)造函數(shù)和析構(gòu)函數(shù)。

設(shè)有一個(gè)Win32下的可執(zhí)行文件MyApp.exe,這是一個(gè)Win32應(yīng)用程序,符合標(biāo)準(zhǔn)的PE格式。MyApp.exe的主要執(zhí)行代碼都集中在其源文件MyApp.cpp中,該文件第一個(gè)被執(zhí)行的函數(shù)是WinMain。初學(xué)者會(huì)認(rèn)為程序就是首先從這個(gè)WinMain函數(shù)開始執(zhí)行,其實(shí)不然。

在WinMain函數(shù)被執(zhí)行之前,有一系列復(fù)雜的加載動(dòng)作,還要執(zhí)行一大段啟動(dòng)代碼。運(yùn)行程序MyApp.exe時(shí),操作系統(tǒng)的加載程序首先為進(jìn)程分配一個(gè)4GB的虛擬地址空間,然后把程序MyApp.exe所占用的磁盤空間作為虛擬內(nèi)存映射到這個(gè)4GB的虛擬地址空間中。一般情況下,會(huì)映射到虛擬地址空間中0X00400000的位置。加載一個(gè)應(yīng)用程序的時(shí)間比一般人所設(shè)想的要少,因?yàn)榧虞d一個(gè)PE文件并不是把這個(gè)文件整個(gè)一次性的從磁盤讀到內(nèi)存中,而是簡(jiǎn)單的做一個(gè)內(nèi)存映射,映射一個(gè)大文件和映射一個(gè)小文件所花費(fèi)的時(shí)間相差無(wú)幾。當(dāng)然,真正執(zhí)行文件中的代碼時(shí),操作系統(tǒng)還是要把存在于磁盤上的虛擬內(nèi)存中的代碼交換到物理內(nèi)存(RAM)中。但是,這種交換也不是把整個(gè)文件所占用的虛擬地址空間一次性的全部從磁盤交換到物理內(nèi)存中,操作系統(tǒng)會(huì)根據(jù)需要和內(nèi)存占用情況交換一頁(yè)或多頁(yè)。當(dāng)然,這種交換是雙向的,即存在于物理內(nèi)存中的一部分當(dāng)前沒(méi)有被使用的頁(yè)也可能被交換到磁盤中。

接著,系統(tǒng)在內(nèi)核中創(chuàng)建進(jìn)程對(duì)象和主線程對(duì)象以及其它內(nèi)容。

然后操作系統(tǒng)的加載程序搜索PE文件中的引入表,加載所有應(yīng)用程序所使用的動(dòng)態(tài)鏈接庫(kù)。對(duì)動(dòng)態(tài)鏈接庫(kù)的加載與對(duì)應(yīng)用程序的加載完全類似。

再接著,操作系統(tǒng)執(zhí)行PE文件首部所指定地址處的代碼,開始應(yīng)用程序主線程的執(zhí)行。首先被執(zhí)行的代碼并不是MyApp中的WinMain函數(shù),而是被稱為C Runtime startup code的WinMainCRTStartup函數(shù),該函數(shù)是連接時(shí)由連接程序附加到文件MyApp.exe中的。該函數(shù)得到新進(jìn)程的全部命令行指針和環(huán)境變量的指針,完成一些C運(yùn)行時(shí)全局變量以及C運(yùn)行時(shí)內(nèi)存分配函數(shù)的初始化工作。如果使用C++編程,還要執(zhí)行全局類對(duì)象的構(gòu)造函數(shù)。最后,WinMainCRTStartup函數(shù)調(diào)用WinMain函數(shù)。

WinMainCRTStartup函數(shù)傳給WinMain函數(shù)的4個(gè)參數(shù)分別為:hInstance、hPrevInstance、lpCmdline、nCmdShow。

hInstance:該進(jìn)程所對(duì)應(yīng)的應(yīng)用程序當(dāng)前實(shí)例的句柄。WinMainCRTStartup函數(shù)通過(guò)調(diào)用GetStartupInfo函數(shù)獲得該參數(shù)的值。該參數(shù)實(shí)際上是應(yīng)用程序被加載到進(jìn)程虛擬地址空間的地址,通常情況下,對(duì)于大多數(shù)進(jìn)程,該參數(shù)總是0X00400000。

hPrevInstance:應(yīng)用程序前一實(shí)例的句柄。由于Win32應(yīng)用程序的每一個(gè)實(shí)例總是運(yùn)行在自己的獨(dú)立的進(jìn)程地址空間中,因此,對(duì)于Win32應(yīng)用程序,WinMainCRTStartup函數(shù)傳給該參數(shù)的值總是NULL。如果應(yīng)用程序希望知道是否有另一個(gè)實(shí)例在運(yùn)行,可以通過(guò)線程同步技術(shù),創(chuàng)建一個(gè)具有唯一名稱的互斥量,通過(guò)檢測(cè)這個(gè)互斥量是否存在可以知道是否有另一個(gè)實(shí)例在運(yùn)行。

lpCmdline:命令行參數(shù)的指針。該指針指向一個(gè)以0結(jié)尾的字符串,該字符串不包括應(yīng)用程序名。

nCmdShow:指定如何顯示應(yīng)用程序窗口。如果該程序通過(guò)在資源管理器中雙擊圖標(biāo)運(yùn)行,WinMainCRTStartup函數(shù)傳給該參數(shù)的值為SW_SHOWNORMAL。如果通過(guò)在另一個(gè)應(yīng)用程序中調(diào)用CreatProcess函數(shù)運(yùn)行,該參數(shù)由CreatProcess函數(shù)的參數(shù)lpStartupInfo(STARTUPINFO.wShowWindow)指定。

操作系統(tǒng)裝載應(yīng)用程序后,做完初始化工作就轉(zhuǎn)到程序的入口點(diǎn)執(zhí)行。程序的默認(rèn)入口點(diǎn)由連接程序設(shè)置, 不同的連接器選擇的入口函數(shù)也不盡相同。在VC++下,連接器對(duì)控制臺(tái)程序設(shè)置的入口函數(shù)是 mainCRTStartup,mainCRTStartup 再調(diào)用main 函數(shù);對(duì)圖形用戶界面(GUI)程序設(shè)置的入口函數(shù)是 WinMainCRTStartup,WinMainCRTStartup 調(diào)用你自己寫的 WinMain 函數(shù)。具體設(shè)置哪個(gè)入口點(diǎn)是由連接器的“/subsystem:”選項(xiàng)確定的,它告訴操作系統(tǒng)如何運(yùn)行編譯生成的.EXE文件??梢灾付ㄋ姆N方式:CONSOLE|WINDOWS|NATIVE|POSIX。如果這個(gè)選項(xiàng)參數(shù)的值為 WINDOWS,則表示該應(yīng)用程序運(yùn)行時(shí)不需要控制臺(tái),有關(guān)連接器參數(shù)選項(xiàng)的詳細(xì)說(shuō)明請(qǐng)參考 MSDN 庫(kù)。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI