標籤:c 缺陷 陷阱
1.大多數C語言的實現都通過函數main的返回值來告訴作業系統該函數的執行是成功還是失敗。典型的處理方案是,返回值為0代表程式執行成功,返回值非0則表示程式執行失敗。如果一個程式的main函數並不返回任何值,那麼有可能看上去執行失敗。所以建議我們的C程式的main函數應該如下編寫:
int main()
{
return 0;
}
當然如果main函數需要接受參數的話將參數聲明加上更加完美。
2.一個C程式可能是由多個分別編譯的部分組成,這些不同的部分通過一個通常叫做連接器的程式合并為一個整體。連接器的輸入是一組目標模組和庫檔案,連接器的輸出是一個載入模組。
連接器通常把目標模組看成是由一組外部對象組成的,每個外部對象代表著機器記憶體中的某個部分,並通過一個外部名稱來識別。因此,程式中的每個函數和每個外部變數,如果沒有被聲明為static,就是一個外部對象。某些C編譯器會對靜態函數和靜態變數的名稱做一定改變,將它們也作為外部對象。
大多數連接器都禁止同一個載入模組中的兩個不同外部對象擁有相同的名稱。然而在多個目標模組整合成一個載入模組時,這些目標模組可能就包含了同名的外部對象,連接器的一個重要工作就是處理這類命名衝突。
除了外部對象之外,目標模組中還可能包含了對其他模組中的外部對象的引用。例如,一個調用了函數printf的C程式所產生的目標模組,就包括了一個對函數printf的引用。所以在連接器產生載入模組的過程中,它必須同時記錄這些外部對象的引用。
3.extern int a;這條語句是對外部變數a的聲明,即使其出現在一個函數的內部,也仍然具有相同的含義,因為這種形式的聲明是對一個外部對象的顯式引用。
如果語句int a = 7和語句int a = 9出現在同一個源檔案中,將會出現什麼樣的情形呢?這個問題的答案與系統有關,不同的系統可能有不同的處理方式。嚴格的規則是,每個外部變數只能夠定義一次。如果上述這種情況出現的話,大多數系統都會拒絕接受該程式。但是如果一個外部變數在多個源檔案中定義卻並沒有指定初始值,那麼某些系統會接受這個程式,而另外一些系統則不會接受。
4.兩個具有相同名稱的外部對象實際上代表的是同一個對象,即使編程者的本意並非如此,但系統卻會如此處理。因此,如果在兩個不同的源檔案中都包含了定義:
int a;
那麼,它或者表示程式錯誤,或者在兩個源檔案中共用a的同一個執行個體。static可以很好的解決這個問題。例如,如下聲明語句:
static int a;
a的範圍將被限制在一個源檔案內,對於其他源檔案,a是不可見的。因此,如果若干個函數想要共用一組外部對象,可以將這些函數放到一個源檔案中,把它們需要用到的對象也都在同一個源檔案中以static修飾符聲明。需要注意的是static同樣可以適用於函數。
5.如果一個函數在被定義或聲明之前被調用,那麼它的傳回型別就預設為整型。C語言中的規則是,如果一個未聲明的標識符後跟一個開括弧,那麼它將被視為一個返回整型的函數。當然這在很多時候會造成錯誤,所以在使用函數的時候一定要保證在函數調用之前已經有過函數的聲明。
6.如下的這段代碼有什麼問題?
int main()
{
char c;
while((c = getchar()) != EOF)
putchar(c);
return 0;
}
分析:這段代碼的問題在於隱性的假設了EOF的值是在char的表示範圍內。然而很多實際情況下,EOF的值char類型是無法容納的,所以如果想改正這個程式,需要將c定義為int類型。
7.許多作業系統的標準輸入/輸出庫都允許程式開啟一個檔案,同時進行寫入和讀出的操作:
FILE *fp;
fp = fopen(file, "r+");
編程者也許認為,程式一旦執行上述操作完畢,就可以自由地交錯進行讀出和寫入的操作。遺憾的是,為了保持與過去不能同時進行讀寫操作的程式向下相容性,一個輸入操作不能隨後直接緊跟一個輸出操作,反之亦然。如果要同時進行輸入和輸出操作,必須在其中插入fseek函數的調用。推薦如下的寫法:
while(...)
{
fread(...);
fseek(...);
fwrite(...);
fseek(...);
}
8.程式輸出有兩種方式:一種即時處理方式,另一種是先暫存起來,然後再大塊寫入的方式,前者往往造成系統較高的負擔。因此,C語言實現通常都允許程式員進行實際的寫操作之前控制產生的輸出資料量。這種控制能力一般都是通過庫函數setbuf實現的,如果buf是一個大小適當的字元數組,那麼
setbuf(stdout, buf);
語句將通知輸入/輸出庫,所有寫入到stdout的輸出都應該使用buf作為輸出緩衝區。
注意:這裡的buf如果被定義為局部變數將可能導致錯誤,因為系統最後一次清理buf將會是在main函數執行完成後,這個時候如果buf都已經被系統釋放,就有可能出現錯誤,所以一般將buf設定為static或者設定為全域變數再或者使用malloc分配,這三種方法都是延長buf的生存期。
9.在調用庫函數時,我們應該首先檢測作為錯誤指示的返回值,確定程式執行已經失敗,然後在檢查errno(當很多庫函數程式執行失敗的時候,往往會通過這個外部變數通知程式該函數調用失敗),來具體查清楚出錯的原因,而不是直接檢測errno的值來判斷是否出現某種錯誤。
10.當一個程式異常終止時,程式輸出的最後幾行常常會丟失,原因是什嗎?我們能夠採取怎麼樣的措施來解決這個問題?
分析:一個異常終止的程式可能沒有機會來清空其輸出緩衝區,因此,該程式產生的輸出可能位於記憶體的某個位置,但卻永遠不會被寫出了。在某些系統上,這些無法寫出的資料可能長達好幾頁。
對於試圖調試這類程式的編程者來說,這種丟失的情況經常會誤導他們,因為它會造成這樣一種假象,程式發生失敗的時刻比實際上運行失敗的真正時刻要早得多。解決方案就是在調試時強制不允許對輸出進行緩衝。要做到這一點,不同的系統有不同的作法,這些作法雖然存在細微差別,但大致如下:
setbuf(sedout, (char *)0);
這個語句必須在任何輸出被寫入到stdout(包括任何對printf函數的調用)之前執行。該語句的最恰當的位置就是作為main函數的第一個語句。
C陷阱與缺陷整理三