Linux上程式執行的入口 Main

2021-09-06 23:59:35 字數 3285 閱讀 3823

main()函式,想必大家都不陌生了,從剛開始寫程式的時候,大家便開始寫main(),我們都知道main是程式的入口。那main作為乙個函式,又是誰呼叫的它,它是怎麼被呼叫的,返回給誰,返回的又是什麼?這次我們來**一下這個問題。

1. main()函式的形式

先來說說main函式的定義,較早開始寫c程式的肯定都用過這樣的定義void main(){},其實翻翻c/c++標準,從來沒有定義過void main()。

在c標準中main的定義只有兩種:

int main(void)

int main(int argc, char *argv)

在c++標準中main的定義也只有兩種:

int main( )

int main(int argc, char *argv)

換句話說:當你的程式不需要命令列引數的時候用int main(), 當需要命令列引數的時候請使用int main(int argc, char *argv)

不過標準歸標準,在不同的平台上,不同的編譯器中對main()的定義方式總有自己的實現,比如早期編譯器對void main()的支援(現在gcc也支援,不過會給出乙個warning)。特別的,因為歷史的原因,在unix-like平台上,大多還支援

int main(int argc, char *argv, char *envp)

其使用方式我們稍後再談。

2. main()函式的返回    

int main(...) 意味著需要return乙個int值,如果不寫,有的編譯器會自動幫你新增乙個return 0;,而有的則會返回乙個隨機值。為了避免不必要的問題,建議寫的時候還是加上乙個return 0;,浪費不了你多少時間,不是嗎?

所以乙個完整的test.c檔案應該為:

int main(int argc, char *argv)

當然我們也可以嘗試著讓main返回乙個long, double甚至是struct,更改main函式中的形參定義。這在有些編譯器上是能編譯通過的,不過可能會有一些警告(如gcc)。但是執行的時候如果編譯器能做轉換的還好,如返回long,float. 如果不能的話(如返回struct,或者main(int argc, char *argv0,char *argv1,char *argv2))會造成segmentation fault。

3. main()的呼叫和返回

在了解了main()函式的定義和返回形式後,我們再來看看main函式是怎麼被呼叫的,它又"return"給了誰。在"gcc的編譯過程"一中,我們回顧了程式從原始碼到可執行程式的過程,在"應用程式在linux上是如何被執行的"一文中,我們回顧了可執行檔案怎麼被作業系統載入的,今天我們繼續這個過程。

上文提到不管是在load_elf_binary()中或者使用了動態鏈結庫,最後都執行到了應用程式的入口。不過這個入口不是main.而是_start()

執行gcc -o test test.c

readelf -a test

可以看到test檔案的entry point address是0x80482e0,在往後看,這個位址是.text的位址(**段的開始),也是_start()的位址。在_start()中又會呼叫__libc_start_main(),主要做一些程式的初始化工作,感興趣的同學可以讀讀glibc中的原始碼,注釋很清楚。然後主角登場了,在__libc_start_main()中最後會呼叫

int result = main (argc, argv, __environ main_auxvec_param);//這是unix-like下main函式的呼叫方式,這下大家明白main函式中形參的由來了吧。

result中放著main函式的返回值,然後帶著這個值退出。

exit (result);

注意:雖然main函式是乙個特殊的函式,是程式執行的入口,但它畢竟也是乙個函式,是可以被呼叫的。如:

int   main()  

不過要小心呼叫方式,和退出條件,避免無窮遞迴。

4. shell中執行程式

通過前幾次和上面的分析,我們終於基本弄清了應用程式的執行過程,再回顧一遍: 在某個互動式shell中敲入./test, 此shell fork()/clone()出乙個子程序,這個子程序執行

execve("./test",char * const argv, char * const envp)

execve載入./test,並把引數argv,envp一步一步傳遞下去。載入了./test之後,從./test的入口開始執行,即elf檔案中的_start(),_start()呼叫__libc_start_main(),最後到了main。

int main(int argc, char *argv, char *envp)

看著這個main的定義和execve相似吧,沒錯main中的引數都是execve一步步傳遞下來的。argc是命令列引數個數,argv儲存著各個引數的指標(注意argv[0]通常是程式名,argv[1]開始才是命令列引數。這是由shell設定的),envp儲存著環境變數表。然而在標準c中只定義了int main(int argc, char *argv),所以unix-like平台也提供了全域性變數environ指向環境變數表。

extern char **environ;

當然也可以用getenv和putenv來訪問特定的環境變數。

對了,父shell還在wait()./test的結束呢,不錯,test中main函式return的值,在被__libc_start_main() exit之後,終於被父shell抓住了,可以用$?訪問。

如$> ./test

$> echo $?

可以得到test返回的值。這樣,你就知道main()函式中return的意義,以及如何在shell中使用了吧。儘管可以return任何值,也建議用return 0來表示程式正常結束。這樣別人用shell指令碼呼叫你寫的程式的時候,就可以$?等於0來判斷你的程式是否正常執行了。

最後小結一下:

1. 避免使用void main(),盡量使用int main() 或者 int main(int argc, char *argv)。

2.在main的結尾記得 return int;, 最好用return 0;表示程式的正常結束。

3. main函式和普通函式一樣也是能被呼叫的。

4. main return的值最終會返回給其呼叫者,如shell中執行的程式,可以在shell中用$?得到其返回值。

5. 在unix-like環境中,可以使用int main(int argc, char *argv, char *envp), extern char **environ; , getenv()等方式來得到環境變數。

MFC 程式入口和執行流程

一 mfc程式執行過程剖析 相信大家有點暈點了吧,實際程式設計中沒有必要深刻理解這麼多,這些大都是由mfc內部自動幫我們完成的。實際mfc程式設計過程中,其實懂得mfc程式中各個函式的執行流程即可。有時候過於追究mfc細節會白白浪費我們的精力,應該將主要精力放在使用mfc解決實際問題上。二 vc6中...

MFC 程式入口和執行流程

一 mfc程式執行過程剖析 6 在訊息執行結束,使用者按下關閉按鈕後,作業系統向程式傳送wm close訊息,預設狀況下程式呼叫destorywindow並且傳送wm destory訊息,應用程式接受到這個訊息以後的預設操作是呼叫postquitmessage函式,由這個函式傳送wm quit訊息。...

MFC 程式入口和執行流程

一 mfc程式執行過程剖析 相信大家有點暈點了吧,實際程式設計中沒有必要深刻理解這麼多,這些大都是由mfc內部自動幫我們完成的。實際mfc程式設計過程中,其實懂得mfc程式中各個函式的執行流程即可。有時候過於追究mfc細節會白白浪費我們的精力,應該將主要精力放在使用mfc解決實際問題上。二 vc6中...