原文下載:http://uploadingit.com/file/n7xuzf5mu71qqvkm/Dan_Saks_const_T_vs_T_const.pdf
參考文檔:http://blog.csdn.net/night_elf_1020/archive/2008/12/06/3460715.aspx
“我們在使用typedef和const的時候遇到了一個非常有趣的問題。我希望您評論下這種問題。我在想我們是不是碰巧遇到了一些我們不知道的C語言規則”。
“我們正在Hitachi SH-2 32位RISC微控制器上使用Hitachi C編譯器。我們認爲下面的代碼:
typedef void *VP;
const VP vectorTable[]
={..<data>..}; (1)
應該等同於:
const void*vectorTable[]
={..<data>..}; (2)”
“然而,在(1)中連接器把vectorTable放在了CONSTANT區,但是在(2)中卻放在了DATA區。”
“這是編譯器的正常行爲還是BUG?”
這種情況是正常的,不是BUG。你確實碰巧遇到了一些你顯然不知道的C語言規則。不要沮喪,我相信很多其他的C和C++程序員對這些規則也都很困惑,這也正是爲什麼我要在這期專欄裏對這類問題予以解答。
我在早期專欄裏提到過這類規則,但是現在回顧起來,我感覺那篇文章並沒有充分的解釋清楚你所困惑的根源,所以讓我再解釋一次。
聲明符
這裏是第一個要點:
每一條C/C++聲明語句都有兩個基本部分組成:零個或多個聲明說明符(declaration specifier)序列(零個應該是顯式的不寫返回值的函數,譯者注);以及一個或多個聲明符(declarator)序列,中間用逗號隔開。
例如:static unsigned long int *x[N];
一個聲明符就是一個被聲明的名字,可能還會被若干個操作符修飾,如*, [], (),和&(C++中的引用)。正如你已經知道的,符號“*”代表“指向...的指針”而“[]”意思是“...的數組”。這樣*x[N]這個聲明符就表示x是一個N個指向...的指針的數組,這裏的“...”指聲明說明符中的類型。例如:
static unsigned long int *x[N];
表示聲明變量對象x是一個含有N個指向unsigned long int型數據的指針的數組(在後面將解釋關鍵詞static並不對類型起作用)。
我是怎麼知道*x[N]是一個指針數組而不是數組指針的呢?這裏遵循以下規則:在聲明語句中的操作符的優先級和在表達式裏的操作符的優先級是一樣的。
例如,如果你參照下最新的C/C++的優先級列表,你會看到[]比*優先級高,這樣在聲明符*x[N]中x被解析成數組,而不是指針。
圓括號在聲明符中有兩種角色:函數調用操作符和分組標誌。作爲函數調用操作符,()與[]具有相同的優先級;作爲分組標誌,()擁有最高的優先級。例如*f(int)中f是一個返回指針類型的函數,而在(*f)(int)中f是一個指向函數的指針。
一個聲明符可能會包含不止一個標識符(ID)。聲明符*x[N]包含兩個標識符,x和N。其中僅有一個是在本聲明語句中被聲明的,稱作聲明符ID,其它的必須在前面已經聲明過。也就是說x[N]聲明的聲明符ID是x。
一個聲明符當然也可以不包含操作符。像一個極其簡單的聲明語句:
int n;
聲明符就是標識符n。
聲明說明符
聲明說明符可以指定聲明符爲int、unsigned等內置類型,或者是typedef定義的類型(根據個人理解譯,譯者注),也可以是存儲類說明符,像extern或者static。在C++中還可以是inline或者virtual這類的函數說明符。
這裏引出另一個要點:
類型說明符指明瞭聲明符ID的類型,其他的說明符對聲明符ID的類型沒有直接影響。
例如:
static unsigned long int*x[N];
聲明符ID x是一個含有N個指向unsigned long int數據的指針的數組,關鍵詞static表明x靜態分配存儲。
在你信中提到的例子讓我懷疑你可能誤認爲關鍵字const和volatile不是類型說明符。而事實上const和volatile都是類型說明符。(在C參考手冊中const和volatile是type qualifier,而不是type specifier.這裏不知爲什麼作者不加以區分.譯者注)
例如,在(2)中的const並不是直接修飾vectorTable,而是直接修飾void。
const void*vectorTable[]
={..<data>..}; (2)
上面語句聲明vectorTable爲一個指向const void類型的指針的數組,而你卻希望它是一個指向void的只讀指針的數組。
這裏還有一個要點:
在聲明語句中的聲明說明符的順序無關緊要。
例如:
const VP vectorTable[]
等價於
VP const vectorTable[]
而
const void *vectorTable[]
等價於
void const *vectorTable[]。
我們大都傾向於把存儲器類聲明符(比如static)放在最左邊,但這僅僅是一個常見的約定,C語言並沒有要求這麼做。
和其他聲明說明符不同,const和volatile是唯一的兩個可以出現在聲明符中的聲明說明符。
例如
void *const vectorTable[]
中的const就出現在聲明符中,這種情況下不能改變關鍵詞的順序,例如
*const void vectorTable[]
就是個錯誤的例子。
一種清晰的編碼風格
正如我前面所解釋的,聲明說明符的順序對編譯器來說無關緊要,因此下面的兩條聲明等價
const void *vectorTable[] (3)
void const *vectorTable[] (4)
幾乎所有的C和C++程序員都傾向於把const和volatile放在其他類型說明符的左邊,像(3)一樣,而我更喜歡寫在右邊,類似(4),並且強烈推薦這種方式。
儘管C和C++大都是按照從上自下和從左自右的順序(結合),但指針的聲明某種意義上卻是逆着來。也就是說指針聲明符是從右向左的,把const放在其他聲明說明符的右邊就可以嚴格的從右往左分析了。例如
T const *p;
聲明瞭一個指向const T類型的指針,而
T *const p;
聲明瞭一個只讀指針,指向T類型。
在同時使用const和typedef定義的類型的聲明語句中,這種方法會降低分析的難度。使用最初的信中的例子:
typedef void *VP;
const VP vectorTable[]。
一種解釋是在原處替換VP:
const VP vectorTable[]
const void*vectorTable[]
這似乎使得vectorTable是一個存放若干個指向const void類型的指針的數組,這是錯誤的!
正確的解釋是替換VP爲
const VP vectorTable[]
void *const vectorTable[]
這樣,vectorTable是存放若干個指向void的只讀指針的數組,但這並不是那麼容易就可以看出來的。
把const寫在最右邊便能更容易的分析出正確結果:
VP const vectorTable[]
void *const vectorTable[]
現在,我意識到我在推薦一種極少人採用的風格。幾乎所有的人都把const放在最左邊,但是考慮到很少有人真正理解他們在使用const時究竟在做什麼,“別人都這麼做”並不能作爲支持這種流行的風格的論據。我們爲何不抵制這種趨勢,去嘗試一種更加清晰的代碼書寫風格呢?
只要我還在從事這個行業,我還要反對另外一種類似的風格(這兒不好翻譯,不過不影響理解)。雖然C程序員好像都沒有這種傾向,但是許多c++程序員卻不幸的養成了這樣一個書寫習慣:
const int* p;
而不是
const int *p;
也就是說,他們使用空格將*和聲明說明符連在一起,而不是和聲明符。我真的認爲C++程序員這麼做是在給自己和別人製造麻煩。的確,空格的位置對編譯器來說沒有任何區別,但是把空格放在*後面會使人對聲明的結構產生一種錯誤的印象。認清最後一個聲明說明符和聲明符的界限是理解聲明的一個關鍵點。像上面那樣用空格把聲明符分開只會使情況變的更加複雜。
我希望我解答了你的疑問並澄清了相關問題。
The End.
本來想一點點翻譯,但在CSDN上看到night_elf_1020已經翻譯過這篇文章(在參考文檔中列出)。但是譯文多處不通順,而且個人認爲還有幾處錯誤,所以在Ta的基礎上做了修改。因此嚴格意義上來說這並不是原創翻譯。在這裏對night_elf_1020表示感謝。
這篇文章是關於const的有名的文章,有人說它講出了const所有的知識點,未免有點誇張,但不得不說這確實是一篇好文章。
declaration specifier更多的是翻譯成"聲明修飾符",如果你感覺不習慣可以將文中的"聲明說明符"改爲"聲明修飾符".
龍西村原創,轉載請註明出處.