精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

深度剖析C語言的main函數

璟琰乀 ? 來源:csdn ? 作者:z_ryan ? 2020-12-18 17:07 ? 次閱讀

main的返回值

main函數的返回值用于說明程序的退出狀態。如果返回0,則代表程序正常退出。返回其它數字的含義則由系統決定。通常,返回非零代表程序異常退出。

void main()

有一些書上的,都使用了void main( ) ,其實這是錯誤的。C/C++ 中從來沒有定義過void main( ) 。

C++ 之父 Bjarne Stroustrup 在他的主頁上的 FAQ 中明確地寫著 “The definition void main( ) { /* … */ } is not and never has been C++, nor has it even been C.” 這可能是因為 在 C 和 C++ 中,不接收任何參數也不返回任何信息的函數原型為“void foo(void);”。

可能正是因為這個,所以很多人都誤認為如果不需要程序返回值時可以把main函數定義成void main(void) 。然而這是錯誤的!main 函數的返回值應該定義為 int 類型,C 和 C++ 標準中都是這樣規定的。

雖然在一些編譯器中,void main() 可以通過編譯,但并非所有編譯器都支持 void main() ,因為標準中從來沒有定義過 void main 。

g++3.2 中如果 main 函數的返回值不是 int 類型,就根本通不過編譯。而 gcc3.2 則會發出警告。所以,為了程序擁有很好的可移植性,一定要用 int main ()。測試如下:

#include 《stdio.h》void main(){ printf(“Hello world”); return;}

運行結果:g++ test.c

main()

那既然main函數只有一種返回值類型,那么是不是可以不寫?規定:不明確標明返回值的,默認返回值為int,也就是說 main()等同于int main(),而不是等同于void main()。

在C99中,標準要求編譯器至少給 main() 這種用法來個警告,而在c89中這種寫法是被允許的。但為了程序的規范性和可讀性,還是應該明確的指出返回值的類型。測試代碼:

#include 《stdio.h》main(){ printf(“Hello world”); return 0;}

運行結果:

C和C++的標準

在 C99 標準中,只有以下兩種定義方式是正確的:

int main( void ) int main( int argc, char *argv[] )

若不需要從命令行中獲取參數,就使用int main(void) ;否則的話,就用int main( int argc, char *argv[] )。當然參數的傳遞還可以有其他的方式,在下一節中,會單獨來講。

main 函數的返回值類型必須是 int ,這樣返回值才能傳遞給程序的調用者(如操作系統),等同于 exit(0),來判斷函數的執行結果。

C++89中定義了如下兩種 main 函數的定義方式:

int main( ) int main( int argc, char *argv[] )

int main( ) 等同于 C99 中的 int main( void ) ;int main( int argc, char*argv[] ) 的用法也和C99 中定義的一樣。同樣,main函數的返回值類型也必須是int。

return 語句

如果 main 函數的最后沒有寫 return 語句的話,C99 和c++89都規定編譯器要自動在生成的目標文件中加入return 0,表示程序正常退出。

不過,建議你最好在main函數的最后加上return語句,雖然沒有這個必要,但這是一個好的習慣。在linux下我們可以使用shell命令:echo $? 查看函數的返回值。

#include 《stdio.h》int main(){ printf(“Hello world”);}

運行結果:

同時,需要說明的是return的返回值會進行 類型轉換,比如:若return 1.2 ;會將其強制轉換為1,即真正的返回值是1,同理,return ‘a’ ;的話,真正的返回值就是97,;但是若return “abc”;便會報警告,因為無法進行隱式類型轉換。

測試main函數返回值的意義

前文說到,main函數如果返回0,則代表程序正常退出。通常,返回非零代表程序異常退出。在本文的最后,測試一下: test.c:

#include 《stdio.h》int main(){ printf(“c 語言”); return 11.1; }

在終端執行如下:

testSigpipe git:(master) vim test.c testSigpipe git:(master) gcc test.c testSigpipe git:(master) 。/a.out && echo “hello world” #&&與運算,前面為真,才會執行后邊的c 語言

可以看出,操作系統認為main函數執行失敗,因為main函數的返回值是11

testSigpipe git:(master) 。/a.out testSigpipe git:(master) echo $?11

若將main函數中返回值該為0的話:

testSigpipe git:(master) vim test.c testSigpipe git:(master) gcc test.c testSigpipe git:(master) 。/a.out && echo “hello world” #helloc 語言hello world

可以看出,正如我們所期望的一樣,main函數返回0,代表函數正常退出,執行成功;返回非0,代表函數出先異常,執行失敗。

main函數傳參

首先說明的是,可能有些人認為main函數是不可傳入參數的,但是實際上這是錯誤的。main函數可以從命令行獲取參數,從而提高代碼的復用性。

函數原形

為main函數傳參時,可選的main函數原形為:

int main(int argc , char* argv[],char* envp[]);

參數說明:

①、第一個參數argc表示的是傳入參數的個數 。

②、第二個參數char* argv[],是字符串數組,用來存放指向的字符串參數的指針數組,每一個元素指向一個參數。各成員含義如下:

argv[0]:指向程序運行的全路徑名。

argv[1]:指向執行程序名后的第一個字符串 ,表示真正傳入的第一個參數。

argv[2]:指向執行程序名后的第二個字符串 ,表示傳入的第二個參數。

…… argv[n]:指向執行程序名后的第n個字符串 ,表示傳入的第n個參數。

規定:argv[argc]為NULL ,表示參數的結尾。

③、第三個參數char* envp[],也是一個字符串數組,主要是保存這用戶環境中的變量字符串,以NULL結束。envp[]的每一個元素都包含ENVVAR=value形式的字符串,其中ENVVAR為環境變量,value為其對應的值。

envp一旦傳入,它就只是單純的字符串數組而已,不會隨著程序動態設置發生改變。可以使用putenv函數實時修改環境變量,也能使用getenv實時查看環境變量,但是envp本身不會發生改變;平時使用到的比較少。

注意:main函數的參數char* argv[]和char* envp[]表示的是字符串數組,書寫形式不止char* argv[]這一種,相應的argv[][]和 char** argv均可。

char* envp[]

寫個小測試程序,測試main函數的第三個參數:

#include 《stdio.h》int main(int argc ,char* argv[] ,char* envp[]){ int i = 0; while(envp[i++]) { printf(“%s”, envp[i]); } return 0;}

運行結果:部分截圖

envp[] 獲得的信息等同于Linux下env命令的結果。

常用版本

在使用main函數的帶參版本的時,最常用的就是:**int main(int argc , char* argv[]);**變量名稱argc和argv是常規的名稱,當然也可以換成其他名稱。

命令行執行的形式為:可執行文件名 參數1 參數2 … … 參數n。可執行文件名稱和參數、參數之間均使用空格隔開。

示例程序

#include 《stdio.h》int main(int argc, char* argv[]){ int i; printf(“Total %d arguments”,argc); for(i = 0; i 《 argc; i++) { printf(“Argument argv[%d] = %s ”,i, argv[i]); } return 0;}

運行結果:

cpp_workspace git:(master) vim testmain.c cpp_workspace git:(master) gcc testmain.c cpp_workspace git:(master) 。/a.out 1 2 3 #./a.out為程序名 1為第一個參數 , 2 為第二個參數, 3 為第三個參數Total 4 argumentsArgument argv[0] = 。/a.out Argument argv[1] = 1 Argument argv[2] = 2 Argument argv[3] = 3 Argument argv[4] = (null) #默認argv[argc]為null

main的執行順序

可能有的人會說,這還用說,main函數肯定是程序執行的第一個函數。那么,事實果然如此嗎?相信在看了本節之后,會有不一樣的認識。

為什么說main()是程序的入口

linux系統下程序的入口是”_start”,這個函數是linux系統庫(Glibc)的一部分,當我們的程序和Glibc鏈接在一起形成最終的可執行文件的之后,這個函數就是程序執行初始化的入口函數。通過一個測試程序來說明:

#include 《stdio.h》int main(){ printf(“Hello world”); return 0;}

編譯:

gcc testmain.c -nostdlib # -nostdlib (不鏈接標準庫)

程序執行會引發錯誤:/usr/bin/ld: warning: cannot find entry symbol _start;未找到這個符號

所以說:

編譯器缺省是找 __start 符號,而不是 main

__start 這個符號是程序的起始

main 是被標準庫調用的一個符號

那么,這個_start和main函數有什么關系呢?下面我們來進行進一步探究。

_start函數的實現該入口是由ld鏈接器默認的鏈接腳本指定的,當然用戶也可以通過參數進行設定。_start由匯編代碼實現。大致用如下偽代碼表示:

void _start(){ %ebp = 0; int argc = pop from stack char ** argv = top of stack; __libc_start_main(main, argc, argv, __libc_csu_init, __linc_csu_fini, edx, top of stack);}

對應的匯編代碼如下:

_start: xor ebp, ebp //清空ebp pop esi //保存argc,esi = argc mov esp, ecx //保存argv, ecx = argv push esp //參數7保存當前棧頂 push edx //參數6 push __libc_csu_fini//參數5 push __libc_csu_init//參數4 push ecx //參數3 push esi //參數2 push main//參數1 call _libc_start_mainhlt

可以看出,在調用_start之前,裝載器就會將用戶的參數和環境變量壓入棧中。

main函數運行之前的工作

從_start的實現可以看出,main函數執行之前還要做一系列的工作。主要就是初始化系統相關資源:

Some of the stuff that has to happen before main():set up initial stack pointer initialize static and global data zero out uninitialized data run global constructorsSome of this comes with the runtime library‘s crt0.o file or its __start() function. Some of it you need to do yourself.Crt0 is a synonym for the C runtime library.

1.設置棧指針

2.初始化static靜態和global全局變量,即data段的內容

3.將未初始化部分的賦初值:數值型short,int,long等為0,bool為FALSE,指針為NULL,等等,即.bss段的內容

4.運行全局構造器,類似c++中全局構造函數

5.將main函數的參數,argc,argv等傳遞給main函數,然后才真正運行main函數

main之前運行的代碼

下面,我們就來說說在mian函數執行之前到底會運行哪些代碼:(1)全局對象的構造函數會在main 函數之前執行。

(2)一些全局變量、對象和靜態變量、對象的空間分配和賦初值就是在執行main函數之前,而main函數執行完后,還要去執行一些諸如釋放空間、釋放資源使用權等操作

(3)進程啟動后,要執行一些初始化代碼(如設置環境變量等),然后跳轉到main執行。全局對象的構造也在main之前。

(4)通過關鍵字attribute,讓一個函數在主函數之前運行,進行一些數據初始化、模塊加載驗證等。

示例代碼

①、通過關鍵字attribute

#include 《stdio.h》__attribute__((constructor)) void before_main_to_run() { printf(“Hi~,i am called before the main function!”); printf(“%s”,__FUNCTION__); } __attribute__((destructor)) void after_main_to_run() { printf(“%s”,__FUNCTION__); printf(“Hi~,i am called after the main function!”);} int main( int argc, char ** argv ) { printf(“i am main function, and i can get my name(%s) by this way.”,__FUNCTION__); return 0; }

②、全局變量的初始化

#include 《iostream》using namespace std;inline int startup_1(){ cout《《“startup_1 run”《《endl; return 0;}int static no_use_variable_startup_1 = startup_1();int main(int argc, const char * argv[]) { cout《《“this is main”《《endl; return 0;}

至此,我們就聊完了main函數執行之前的事情,那么,你是否還以為main函數也是程序運行的最后一個函數呢?

結果當然不是,在main函數運行之后還有其他函數可以執行,main函數執行完畢之后,返回到入口函數,入口函數進行清理工作,包括全局變量析構、堆銷毀、關閉I/O等,然后進行系統調用結束進程。

main函數之后執行的函數

1、全局對象的析構函數會在main函數之后執行;2、用atexit注冊的函數也會在main之后執行。

atexit函數

原形:

int atexit(void (*func)(void));

atexit 函數可以“注冊”一個函數,使這個函數將在main函數正常終止時被調用,當程序異常終止時,通過它注冊的函數并不會被調用。

編譯器必須至少允許程序員注冊32個函數。如果注冊成功,atexit 返回0,否則返回非零值,沒有辦法取消一個函數的注冊。

在 exit 所執行的任何標準清理操作之前,被注冊的函數按照與注冊順序相反的順序被依次調用。每個被調用的函數不接受任何參數,并且返回類型是 void。被注冊的函數不應該試圖引用任何存儲類別為 auto 或 register 的對象(例如通過指針),除非是它自己所定義的。

多次注冊同一個函數將導致這個函數被多次調用。函數調用的最后的操作就是出棧過程。main()同樣也是一個函數,在結束時,按出棧的順序調用使用atexit函數注冊的,所以說,函數atexit是注冊的函數和函數入棧出棧一樣,是先進后出的,先注冊的后執行。通過atexit可以注冊回調清理函數。可以在這些函數中加入一些清理工作,比如內存釋放、關閉打開的文件、關閉socket描述符、釋放鎖等等。

#include《stdio.h》#include《stdlib.h》void fn0( void ), fn1( void ), fn2( void ), fn3( void ), fn4( void );int main( void ){ //注意使用atexit注冊的函數的執行順序:先注冊的后執行 atexit( fn0 ); atexit( fn1 ); atexit( fn2 ); atexit( fn3 ); atexit( fn4 ); printf( “This is executed first.” ); printf(“main will quit now!”); return 0;}void fn0(){ printf( “first register ,last call” );}void fn1({ printf( “next.” );}void fn2(){ printf( “executed ” );}void fn3(){ printf( “is ” );}void fn4(){ printf( “This ” );}

責任編輯:haq

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • C語言
    +關注

    關注

    180

    文章

    7601

    瀏覽量

    136251
  • 編程
    +關注

    關注

    88

    文章

    3596

    瀏覽量

    93610
  • 函數
    +關注

    關注

    3

    文章

    4308

    瀏覽量

    62445
收藏 人收藏

    評論

    相關推薦

    使用C語言實現函數模板

      用C語言能不能實現一個通用的函數,既能完成整數的相加,又能完成浮點數的相加?
    的頭像 發表于 11-09 11:38 ?299次閱讀

    C語言中的socket編程基礎

    數據 步驟6:關閉socket 創建socket 在C語言中,創建socket需要使用socket()函數。這個函數需要兩個參數:域
    的頭像 發表于 11-01 16:51 ?248次閱讀

    MEMS 可編程振蕩器的卓越代表:SiT9121 系列(1 to 220 MHZ)深度剖析

    MEMS 可編程振蕩器的卓越代表:SiT9121 系列(1 to 220 MHZ)深度剖析
    的頭像 發表于 08-13 10:56 ?437次閱讀
    MEMS 可編程振蕩器的卓越代表:SiT9121 系列(1 to 220 MHZ)<b class='flag-5'>深度</b><b class='flag-5'>剖析</b>

    探索巔峰性能 | 迅為RK3588開發板深度剖析

    探索巔峰性能 | 迅為RK3588開發板深度剖析
    的頭像 發表于 08-12 14:07 ?776次閱讀
    探索巔峰性能 | 迅為RK3588開發板<b class='flag-5'>深度</b><b class='flag-5'>剖析</b>

    表面貼裝低相位噪音晶體振蕩器 DSO531SHH 深度剖析

    表面貼裝低相位噪音晶體振蕩器 DSO531SHH 深度剖析
    的頭像 發表于 07-26 14:12 ?344次閱讀
    表面貼裝低相位噪音晶體振蕩器 DSO531SHH <b class='flag-5'>深度</b><b class='flag-5'>剖析</b>

    請問為什么非main.c的其他文件能調用庫函數

    main.c中調用庫函數我可能理解: 是因為在main.c中引用了#include \"sys.h\"頭文件,而這個頭文件包含了各庫函數的定義聲明。 但其他文件諸如這樣
    發表于 04-25 06:49

    C語言數據類型有哪些

    C 語言中,數據類型指的是用于聲明不同類型的變量或函數的一個廣泛的系統。變量的類型決定了變量存儲占用的空間,以及如何解釋存儲的位模式。
    發表于 03-20 10:56 ?444次閱讀
    <b class='flag-5'>C</b><b class='flag-5'>語言</b>數據類型有哪些

    C語言內存泄漏問題原理

    內存泄漏問題只有在使用堆內存的時候才會出現,棧內存不存在內存泄漏問題,因為棧內存會自動分配和釋放。C語言代碼中堆內存的申請函數是malloc。
    發表于 03-19 11:38 ?496次閱讀
    <b class='flag-5'>C</b><b class='flag-5'>語言</b>內存泄漏問題原理

    淺談C語言中的函數定義

    如果函數要使用參數,則必須聲明接受參數值的變量。這些變量稱為函數的形式參數。 形式參數就像函數內的其他局部變量,在進入函數時被創建,退出函數
    發表于 03-11 10:09 ?336次閱讀

    C語言中的可變參數介紹

    C 語言為這種情況提供了一個解決方案,它允許您定義一個函數,能根據具體的需求接受可變數量的參數
    發表于 02-28 14:00 ?275次閱讀
    <b class='flag-5'>C</b><b class='flag-5'>語言</b>中的可變參數介紹

    C語言中的動態內存管理講解

    本章將講解 C 中的動態內存管理。C 語言為內存的分配和管理提供了幾個函數。這些函數可以在 頭文件中找到。
    的頭像 發表于 02-23 14:03 ?375次閱讀
    <b class='flag-5'>C</b><b class='flag-5'>語言</b>中的動態內存管理講解

    c語言,c++,java,python區別

    C語言C++、Java和Python是四種常見的編程語言,各有優點和特點。 C語言
    的頭像 發表于 02-05 14:11 ?2262次閱讀

    GD32 MCU啟動后如何運行到main函數

    GD32 MCU啟動后如何運行到main函數入口?你是否也有這樣的疑慮。在執行到main函數之前MCU干了哪些事情呢?下面為大家解答。
    的頭像 發表于 01-15 10:00 ?1030次閱讀
    GD32 MCU啟動后如何運行到<b class='flag-5'>main</b><b class='flag-5'>函數</b>

    如何解決C語言中的“訪問權限沖突”異常?C語言引發異常原因分析

    如何解決C語言中的“訪問權限沖突”異常?C語言引發異常原因分析? 在C語言中,訪問權限沖突異常通
    的頭像 發表于 01-12 16:03 ?5192次閱讀

    如何給C語言中的函數定義兩個不同的名字?

    最近有位哥問我,如何給C語言中的函數定義兩個不同的名字?就是這兩個名字都是指向同一個函數,同一個地址,而且兩個名字都可以當做函數來用的那種。
    的頭像 發表于 12-19 16:21 ?767次閱讀