本節書摘來自異步社群《c專家程式設計》一書中的第1章,第1.9節,作者 【美】perter van der linde,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視
有時候必須非常專注地閱讀ansi c标準才能找到某個問題的答案。一位銷售工程師把下面這段代碼作為測試例發給sun的編譯器小組。
如果編譯這段代碼,編譯器會發出一條警告資訊:
(第5行:警告:參數與原型不比對)。
送出代碼的工程師想知道為什麼會産生這條警告資訊,也想知道ansi c标準的哪一部分講述了這方面的内容。他認為,實參char s與形參const char p應該是相容的,标準庫中所有的字元串處理函數都是這樣的。那麼,為什麼實參char argv與形參const char p實際上不能相容呢?
答案是肯定的,它們并不相容。要回答這個問題頗費心機,如果研究一下獲得這個答案的整個過程,會比僅僅知道結論更有意義。對這個問題的分析是由sun的其中一位“語言律師”[6]進行的,其過程如下:
在ansi c标準第6.3.2.2節中講述限制條件的小節中有這麼一句話:
每個實參都應該具有自己的類型,這樣它的值就可以指派給與它所對應的形參類型的對象(該對象的類型不能含有限定符)。
這就是說參數傳遞過程類似于指派。
是以,除非一個類型為char 的值可以指派給一個const char 類型的對象,否則肯定會産生一條診斷資訊。要想知道這個指派是否合法,就請回顧标準中有關簡單指派的部分,它位于第6.3.16.1節,描述了下列限制條件:
要使上述的指派形式合法,必須滿足下列條件之一:
兩個操作數都是指向有限定符或無限定符的相容類型的指針,左邊指針所指向的類型必須具有右邊指針所指向類型的全部限定符。
正是這個條件,使得函數調用中實參char能夠與形參const char比對(在c标準庫中,所有的字元串處理函數就是這樣的)。它之是以合法,是因為在下面的代碼中:
左操作數是一個指向有const限定符的char的指針。
右操作數是一個指向沒有限定符的char的指針。
char類型與char類型是相容的,左操作數所指向的類型具有右操作數所指向類型的限定符(無),再加上自身的限定符(const)。
注意,反過來就不能進行指派。如果不信,試試下面的代碼:
标準第6.3.16.1節有沒有說char 實參與const char 形參是相容的?沒有。
标準第6.1.2.5節中講述執行個體的部分聲稱:
const float *類型并不是一個有限定符的類型——它的類型是“指向一個具有const限定符的float類型的指針”,也就是說const限定符是修飾指針所指向的類型,而不是指針本身。
類似地,const char **也是一個沒有限定符的指針類型。它的類型是“指向有const限定符的char類型的指針的指針”。
由于char 和const char 都是沒有限定符的指針類型,但它們所指向的類型不一樣(前者指向char ,後者指向const char ),是以它們是不相容的。是以,類型為char的實參與類型為const char的形參是不相容的,違反了标準第6.3.2.2節所規定的限制條件,編譯器必然會産生一條診斷資訊。
用這種方式了解這個要點有一定困難。可以用下面這個方法進行了解:
左操作數的類型是foo2,它是一個指向foo的指針,而foo是一個沒有限定符的指針,它指向一個帶有const限定符的char類型,而且……
右操作數的類型是baz2,它是一個指向baz的指針,而baz是一個沒有限定符的指針,它指向一個沒有限定符的字元類型。
foo和baz所指向的類型是相容的,而且它們本身都沒有限定符,是以符合标準的限制條件,兩者之間進行指派是合法的。但foo2和baz2之間的關系又有不同,由于相容性是不能傳遞的,foo和baz所指向的類型相容并不表示foo2和baz2所指向的類型也相容,是以雖然foo2和baz2都沒有限定符,但它們之間不能進行指派。也就是說,它們都是不帶限定符的指針,但它們所指向的對象是不同的,是以它們之間不能進行指派,也就不能分别作為函數的形參和實參。但是,這個限制條件很令人惱火,也很容易讓使用者混淆。是以,這種指派方法目前在基于cfront的c++翻譯器中是合法的(雖然這在将來可能會改變)。

容易混淆的const
關鍵字const并不能把變量變成常量!在一個符号前加上const限定符隻是表示這個符号不能被指派。也就是它的值對于這個符号來說是隻讀的,但它并不能防止通過程式的内部(甚至是外部)的方法來修改這個值。const最有用之處就是用它來限定函數的形參,這樣該函數将不會修改實參指針所指的資料,但其他的函數卻可能會修改它。這也許就是c和c++中const最一般的用法。
const可以用在資料上,如:
這和其他語言差不多,但當你在等式兩邊加上指針,就有一定難度了:
這段代碼表示limitp是一個指向常量整型的指針。這個指針不能用于修改這個整型數,但是在任何時候,這個指針本身的值卻可以改變。這樣,它就指向了不同的位址,對它進行解除引用(dereference)操作時會得到一個不同的值!
const和的組合通常隻用于在數組形式的參數中模拟傳值調用。它聲稱“我給你一個指向它的指針,但你不能修改它。”這個約定類似于極為常見的void 的用法,盡管在理論上它可以用于任何情形,但通常被限制于把指針從一種類型轉換為另一種類型。
類似地,你可以取一個const變量的位址,并且可以...(唔,我最好不要往大家的腦袋裡灌輸這種思想)。正如ken thompson所指出的那樣,“const關鍵字可能引發一些罕見的錯誤,隻會混淆函數庫的接口。”回首往事,const關鍵字原先如果命名為readonly就好多了。
确實,整個标準好像是由一位蹩腳的翻譯把它從烏爾都語轉譯成丹麥語,再轉譯成英語而來。标準委員會似乎自我感覺良好,是以雖然人們希望語言的規則更簡單一些、更清楚一些,但他們覺得這樣做會破壞他們的良好感覺,是以拒不采納。
我感覺,将來還會有許多人産生類似的疑問,而且并不是他們中的每一個人都會仔細揣摩前面詳述的推理過程。是以,我們修改了sun的ansi c編譯器,當它發現不相容的情況時,會列印出更多的警告資訊。原先那個例子将會産生的完整資訊如下:
(第6行:警告:#1實參與原型不相容:
即使程式員不明白為什麼會這樣,他至少應該明白什麼是不相容。