程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> C語言陷阱和缺陷概述

C語言陷阱和缺陷概述

編輯:關於C語言
 

那些自認為已經“學完”C語言的人,請你們仔細讀閱讀這篇文章吧。路還長,很多東西要學。我也是……

[概述]

C語言像一把雕刻刀,鋒利,並且在技師手中非常有用。和任何鋒利的工具一樣,C會傷到那些不能掌握它的人。本文介紹C語言傷害粗心的人的方法,以及如何避免傷害。

[內容]

0 簡介
1 詞法缺陷
1.1 = 不是 ==
1.2 & 和 | 不是 && 和 ||
1.3 多字符記號
1.4 例外
1.5 字符串和字符
2 句法缺陷
2.1 理解聲明
2.2 運算符並不總是具有你所想象的優先級
2.3 看看這些分號!
2.4 switch語句
2.5 函數調用
2.6 懸掛else問題
3 鏈接
3.1 你必須自己檢查外部類型
4 語義缺陷
4.1 表達式求值順序
4.2 &&、||和!運算符
4.3 下標從零開始
4.4 C並不總是轉換實參
4.5 指針不是數組
4.6 避免提喻法
4.7 空指針不是空字符串
4.8 整數溢出
4.9 移位運算符
5 庫函數
5.1 getc()返回整數
5.2 緩沖輸出和內存分配
6 預處理器
6.1 宏不是函數
6.2 宏不是類型定義
7 可移植性缺陷
7.1 一個名字中都有什麼?
7.2 一個整數有多大?
7.3 字符是帶符號的還是無符號的?
7.4 右移位是帶符號的還是無符號的?
7.5 除法如何捨入?
7.6 一個隨機數有多大?
7.7 大小寫轉換
7.8 先釋放,再重新分配
7.9 可移植性問題的一個實例
8 這裡是空閒空間
參考
腳注

0 簡介
C語言及其典型實現被設計為能被專家們容易地使用。這門語言簡潔並附有表達力。但有一些限制可以保護那些浮躁的人。一個浮躁的人可以從這些條款中獲得一些幫助。

在本文中,我們將會看一看這些未可知的益處。這是由於它的未可知,我們無法為其進行完全的分類。不過,我們仍然通過研究為了一個C程序的運行所需要做的事來做到這些。我們假設讀者對C語言至少有個粗淺的了解。

第一部分研究了當程序被劃分為記號時會發生的問題。第二部分繼續研究了當程序的記號被編譯器組合為聲明、表達式和語句時會出現的問題。第三部分研究了由多個部分組成、分別編譯並綁定到一起的C程序。第四部分處理了概念上的誤解:當一個程序具體執行時會發生的事情。第五部分研究了我們的程序和它們所使用的常用庫之間的關系。在第六部分中,我們注意到了我們所寫的程序也不並不是我們所運行的程序;預處理器將首先運行。最後,第七部分討論了可移植性問題:一個能在一個實現中運行的程序無法在另一個實現中運行的原因。

1 詞法缺陷
編譯器的第一個部分常被稱為詞法分析器(lexical analyzer)。詞法分析器檢查組成程序的字符序列,並將它們劃分為記號(token)一個記號是一個有一個或多個字符的序列,它在語言被編譯時具有一個(相關地)統一的意義。在C中, 例如,記號->的意義和組成它的每個獨立的字符具有明顯的區別,而且其意義獨立於->出現的上下文環境。

另外一個例子,考慮下面的語句:

if(x > big) big = x;

該語句中的每一個分離的字符都被劃分為一個記號,除了關鍵字if和標識符big的兩個實例。

事實上,C程序被兩次劃分為記號。首先是預處理器讀取程序。它必須對程序進行記號劃分以發現標識宏的標識符。它必須通過對每個宏進行求值來替換宏調用。最後,經過宏替換的程序又被匯集成字符流送給編譯器。編譯器再第二次將這個流劃分為記號。

在這一節中,我們將探索對記號的意義的普遍的誤解以及記號和組成它們的字符之間的關系。稍後我們將談到預處理器。

1.1 = 不是 ==
從Algol派生出來的語言,如Pascal和Ada,用:=表示賦值而用=表示比較。而C語言則是用=表示賦值而用==表示比較。這是因為賦值的頻率要高於比較,因此為其分配更短的符號。

此外,C還將賦值視為一個運算符,因此可以很容易地寫出多重賦值(如a = b = c),並且可以將賦值嵌入到一個大的表達式中。

這種便捷導致了一個潛在的問題:可能將需要比較的地方寫成賦值。因此,下面的語句好像看起來是要檢查x是否等於y:

if(x = y)
foo();

而實際上是將x設置為y的值並檢查結果是否非零。在考慮下面的一個希望跳過空格、制表符和換行符的循環:

while(c == ' ' || c = '\t' || c == '\n')
c = getc(f);

在與'\t'進行比較的地方程序員錯誤地使用=代替了==。這個“比較”實際上是將'\t'賦給c,然後判斷c的(新的)值是否為零。因為'\t'不為零,這個“比較”將一直為真,因此這個循環會吃盡整個文件。這之後會發生什麼取決於特定的實現是否允許一個程序讀取超過文件尾部的部分。如果允許,這個循環會一直運行。

一些C編譯器會對形如e1 = e2的條件給出一個警告以提醒用戶。當你趨勢需要先對一個變量進行賦值之後再檢查變量是否非零時,為了在這種編譯器中避免警告信息,應考慮顯式給出比較符。換句話說,將:

if(x = y)
foo();

改寫為:

if((x = y) != 0)
foo();

這樣可以清晰地表示你的意圖。

1.2 & 和 | 不是 && 和 ||
容易將==錯寫為=是因為很多其他語言使用=表示比較運算。 其他容易寫錯的運算符還有&和&&,或|和||,這主要是因為C語言中的&和|運算符於其他語言中具有類似功能的運算符大為不同。我們將在第4節中貼近地觀察這些運算符。

1.3 多字符記號
一些C記號,如/、*和=只有一個字符。而其他一些C記號,如/*和==,以及標識符,具有多個字符。當C編譯器遇到緊連在一起的/和*時,它必須能夠決定是將這兩個字符識別為兩個分離的記號還是一個單獨的記號。C語言參考手冊說明了如何決定:“如果輸入流到一個給定的字符串為止已經被識別為記號,則應該包含下一個字符以組成能夠構成記號的最長的字符串”。因此,如果/是一個記號的第一個字符,並且/後面緊隨了一個*,則這兩個字符構成了注釋的開始,不管其他上下文環境。  

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