Linux上程式執行的入口--Main

來源:互聯網
上載者:User

標籤:

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[])
    {
        return 0;
    }
    當然我們也可以嘗試著讓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()  
    {  
        if(...)  
            return   0;  
        main();  
        return   0;  
    }  
    不過要小心調用方式,和允出準則,避免無窮遞迴。

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()等方式來得到環境變數。

Linux上程式執行的入口--Main

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.