程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 你所未必了解的main()函數的事情

你所未必了解的main()函數的事情

編輯:關於C語言

在各種C語言書上,能看到各式各樣main()函數的寫法,簡直令人無所適從,這是這麼回事?原因主要有兩個:一個是隨著C語言的發展和演化,main()函數的寫法也在不斷變化;另外,某些書籍寫法不規范或誤導的現象也同時存在。

最初main()函數的寫法非常簡潔,那個時候的C程序員哪怕一個字符似乎都不肯多寫。不知道是因為當時鍵盤質量不好還是因為編輯器太糟糕的緣故,那個時代的C程序員似乎驚人地一致崇尚“簡約”——甚至可以說是“至簡”。

main()
{
	printf("hello,world\n");
}

這就是main()函數最古老的寫法,K&R在他們的經典名著《The C Programming Language》中的第一個C語言源程序(1978)。這種寫法是那個時代的主流。

簡直和裸體差不多,連#include<stdio.h>也沒有麼?在《The C Programming Language》的第一版中確實沒有。那個時代的C語言,返回值類型為int的函數不用聲明。不過在該書的第二版(1988)中這個程序被改成了:

#include <stdio.h>
main()
{
	printf("hello,world\n");
}

返回值類型為int的函數不用聲明的規則改變了嗎?規則沒有改變。改變了的是觀念,人們已經不再傾向於代碼的“至簡”,而開始傾向於在代碼中交代清楚每一個標識符的來龍去脈。從C89開始倡導在函數調用之前一定要有函數聲明,但並沒有強求,而在C99這已經是強制性的要求了。由於《The C Programming Language》第二版正值ANSI C標准頒布(1989)前夕出版,所以這種變化也應該視為ANSI C標准的傾向性以及K&R對新標准的認同。盡管這個例子沒有完全反映出來這種認同。

為什麼說沒有完全反映出來這種認同呢?因為這個main()的定義並沒有按照函數原型(Function prototype)的方式來寫,C90中規定不帶參數的main()函數應該這樣寫:

int main(void) { /*. . .*/}

但同時規定那個int可以省略。C90把()內不寫任何內容視為過時的寫法,盡管C90無奈地容忍了它(The use of function declarators with empty parentheses (not prototype-format parameter type declarators) is an obsolescent feature.)。

為什麼要容忍?因為有許多老式的代碼還在用。

如果以C99的標准看這個main()寫得如何呢?C99不容許省略int。但同樣只把()內不寫任何內容視為過時,而沒有完全禁止,可見習慣力量的頑固。

那又為什麼說K&R對新標准的認同呢?《The C Programming Language》第二版中的其他函數定義和函數類型聲明基本上都改成了函數原型風格。比如,在講解main()函數的參數時,K&R把原來的main()函數

#include <stdio.h>
main(argc,argv)
int argc;char *argv[];
{
	/*…… */
	return 0;
}

改成了:

#include <stdio.h>
main(int argc, char *argv[])
{
	/*…… */
	return 0;
}

前一個寫法今天已經差不多絕跡,後一個main()以今天的眼光來看有些奇怪,main()的參數是用函數原型風格寫的,但卻沒有寫main()返回值的類型,給人有點半新半舊的感覺。盡管不能說它違背C90(因為C90容許不寫main()前面的int),但如果寫上了返回值的類型int,就同時滿足現代C99標准的要求了。

這裡出現的“return 0;”是怎麼回事?這在現代C語言中已經是司空見慣了,它返回給操作系統一個值以表明程序是在何種狀態下結束的。但在另一段代碼中,K&R似乎又走得太遠:

#include <stdio.h>
main(int argc,char *argv[])
{
	int found = 0 ;
	/*……計算found的值 */
	return found;
}

這個實在有些“標新立異”,居然把計算結果返回給了操作系統,頗有突破常規之嫌。

那前面幾個沒有“return 0;”的main()函數會怎麼樣?按照C90標准,會返回一個不確定的int類型的值,如果確實不關心這個返回值是多少,不寫確實可以。但C99卻要求編譯器在編譯的時候幫忙給補上這個“return 0;”,C99在必須寫int這個問題上沒有遷就懶人,但在這裡卻對偷懶的做法給予了遷就。 問:如果確實不關心main()函數的返回值,把main()的返回值定義為void類型如何?我看到許多書上都這樣寫的。

#include <stdio.h>
void main()
{
	printf("This is a C program.\n");
}

這在C99之前是一種野路子寫法,究竟從哪裡冒出來的,無據可考。但前幾年的主流教科書中這種寫法很常見。K&R(C語言的發明者)沒有這樣寫過,C90國際標准也不承認這種寫法。Bjarne Stroustrup(C++語言的創始人)在他的關於C++的FAQ中,在回答是否可以寫“void main()”時憤怒地回答說這種寫法在C++和C中都不曾有過。事實上,很多C語言專家都認為“void main()”非常邪惡。

因此,在C99之前,這是不符合標准的寫法。盡管這段代碼的功能似乎是輸出“This is a C program.”,但其實卻不是一個“C program”。

但是有時這樣寫並沒有產生錯誤啊?首先,C語言的錯誤不一定反應在編譯、鏈接或運行過程中。你輸出一個垃圾值也可能一路通過編譯、鏈接或運行,但這不說明你的代碼沒有錯誤,更不能說明這樣的代碼正確、有意義。其次,這樣的寫法在有些編譯器下程序會產生崩潰或得到警告。這說明這種寫法至少不普遍性適用的。可以說,如果不是C99標准,這種寫法根本沒有立錐之地。

C99給了這種寫法以立足之地麼?從某種意義上也許可以這樣理解。因為K&R沒承認過這種寫法,C90根本不承認這種寫法,C99雖然沒有正式承認這種寫法,但為這種寫法留了一個後門:“It shall be defined ……or in some other implementation-defined manner”。這意思就是說,如果編譯器明確聲稱允許void main()這種寫法的話,那麼C99不再象C90那樣簡單認為這種寫法違背C標准。

但是不管怎麼說,這種寫法最多是某些編譯器的一種“方言土語”,如果沒有特殊理由,比如僅僅是工作在某個特殊環境,且僅僅使用特定的編譯器而根本不考慮程序的可移植性,為什麼不寫普遍適用的形式呢?

既然很多C語言專家都認為“void main()”非常邪惡,C99為什麼包容這種寫法呢?很難確定C99是否就是打算專門想把這種寫法也“收容”在標准之列。因為除了void main(),還有另外一些main()函數的寫法被C90排除在標准之外了。而現在,這些寫法在理論上也具備了符合C99標准的可能性。

還有什麼樣的main()函數?很多編譯器都支持下面的main()的寫法:

int main(int argc, char *argv[], char *env[])
{
	/* */
	return 0;
}

居然有3個形參,那個env是做什麼用的?那個參數可以使程序獲得環境變量。

什麼叫環境變量?簡單地講可以理解為操作系統記錄的一些數據,比如計算機的名字,操作系統放在哪裡等等。應用程序在運行時可能要用到這些信息,這時可以通過env這個參數來獲得。

如果編譯器不支持main()的第三個參數怎麼辦?標准庫函數也可以達到同樣的目的。

#include <stdlib.h>
char *getenv(const char *name);

是否可以說void main()和int main(int argc, char *argv[], char *env[])也符合C99標准呢?恐怕還不能這麼說,現在只是不能說這兩種寫法一定不符合C99標准。但這兩種寫法不符合C90標准是確定的,這兩種寫法的可移植性很差也是確定無疑的。C99標准在這裡寫的很模糊,沒有進一步界定“implementation-defined manner”的含義。除非編譯器聲明遵守C99標准,且容許這兩種寫法,否則斷言這兩種寫法符合C99標准屬於空穴來風。

有人說“C99建議把main函數指定為int型”,這種說法對嗎?顯然不對。因為C99並非絕對不包容返回值非int類型的main()。正確的說法是,C90要求main()函數的返回值一定得是int型。但C90容許不寫那個int,而C99則要求必須寫上這個“int”。

下面這種風格如何?

#include <stdio.h>
int main()
{
	printf("This is a C program.\n");
	return 0;
}

這個寫法有點不倫不類。返回值的類型int寫了,這個和C89的倡導或C99的要求一致,但是()裡面什麼都不寫,又與標准的所倡導的風格不符,所以說不倫不類。這種寫法目前的標准依然容許,但屬於標准目前尚能容忍的但即將過時的(obsolescent)寫法,被拋棄只是早晚的問題。這種寫法就如同古代的函數形參的寫法一樣:

main(argc,argv)
int argc;char *argv[];
{
	/*…… */
	return 0;
}

都屬於歷史的垃圾。

見過在main()的函數體的“}”之前前寫一句getch();,這個是怎麼回事?這個是時代的產物。在PC從DOS時代轉變為Windows時代的過程中,DOS時代開發的IDE(主要是TC)無法在運行程序後顯示輸出結果,為了在運行後從容仔細地觀察一下運行結果再返回IED界面,加上了這麼一句,人為地延長程序運行時間(因為getch()會等待用戶輸入一個字符)。但這與main()本身的結構無關。這條語句不具備普遍意義,只是將就過時的IDE的一種權宜之計而已。所謂不具備普遍意義是指,第一,真正的程序往往不需要這條語句,就是說這條語句與程序功能無關;第二,getch()這個函數並不是標准函數,只有個別的編譯器才支持它,在其他編譯器上寫這條語句,很可能行不通。

為什麼不用getchar()這個標准庫函數呢?getchar()的功能和getch()有點區別,前者會在標准輸出設備上顯示用戶鍵入的字符,這顯得很不利索,而後者則不會顯示用戶所鍵入的字符,更接近“Press Any Key to continue……”的效果。

有的代碼在main()函數結束前寫system("PAUSE");,是否也是這個意思?是的。這也是一種人工制造的“請按任意鍵繼續. . .”,與程序功能結構無關,只是為了方便地觀察輸出結果。但是這種寫法比調用getch()要好,因為system()函數是標准庫函數,各個編譯器都提供支持。

有一種說法,“在最新的C99標准中,只有以下兩種定義方式是正確的:”

int main( void )
{ 
	/*  */
	return 0;
}

int main( int argc, char *argv[] )
{
	/*  */
    return 0;
}

這種說法對嗎?

這種說法顯然不對。但可以確認的是這兩種定義方式一定正確。不但在C99來說是正確的,以C89來說也是正確的。

還有一種寫法:

int main( void )
{
	return EXIT_SUCCESS;
}

那個EXIT_SUCCESS是怎麼回事?

return EXIT_SUCCESS;是與return 0;等價的一種文雅的寫法。EXIT_SUCCESS是在stdlib.h中定義了的符號常量,返回這個值表示程序任務完成後程序退出。在stdlib.h定義的另一個符號常量EXIT_FAILURE,通常用於程序無法完成任務而退出。

實在太眼花缭亂了,需要記住這麼多嗎?顯然沒必要。很多東西都是歷史原因遺留下的垃圾。

如果學習C語言,應該記住或使用哪種呢?顯然是:

int main( void )
{ 
	/*  */
    return 0;
}

int main( int argc, char *argv[] )
{
	/*  */
    return 0;
}

第一,他們普遍適用,不存在可移植性的問題;

第二,就目前看,他們不存在任何過時或即將過時的成分。當然,如果喜歡文雅,不寫return 0;而寫EXIT_SUCCESS也可以。 順便說一句,有的學習者記不住帶參數main()函數兩個形參的名字。其實這兩個形參的名字也可以自己取,不一定用那兩個名字,只要記住類型就可以了。第二個參數的類型也可以是char **,這和原來的是等價的。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved