2011年9月28日 星期三

C 的型別宣告

直到看了 Dan Saks 的 const T vs T const, 才(比較)明白 C 的型別宣告, 知道 int const *a 和 int * const a 的差別。原本雖然知道 int (*f[10])(int )、int (*f)(int) 和 int *f(int) 的差別, 但沒有很清楚細部原因, 看了這篇後才懂得更徹底。

C 的宣告分幾個部份, 以 static unsigned long int *x[N] 來說, *x[N] 是 declarator, 剩下的是 declaration specifier。declarator 的部份用運算子優先順序來解讀, 語意清楚許多。即 () 最優先, 其次為 [], 再來為 *。所以:

  • int (*f)(float) 表示 f 是函式指標, 指向一個函式其傳回值為 int, 參數為 float
  • int *f(float) 表示 f 是函式, 傳回 int*, 參數為 float
  • int (*f[10])(float) 表示 f 是一個陣列, 每個元素是一函式指標

令人混淆的部份在於, declaration specifier 的順序其實沒有影響。所以, static unsigned int n 和 int unsigned static n 是一樣的。再加上 const 和 volatile 不同於其它 declaration specifier, 它們可以同時寫在 declarator, 於是產生不易理解的結果。

我還在參透 volatile 的效果, 這裡先以 const 為例:

  • int const n 和 const int n 都表示 n 是指向唯讀的 int
  • int const *n 表示 n 是指標, 指標指向的型別為唯讀的 int
  • int * const n 表示 n 是唯讀的指標, 指向 int
  • int const * const n 表示 n 是唯讀的指標, 且 n 指向唯讀的 int

簡單的判別方式是以 * 為分界點, 由右往左看, 包含 * 的右邊是 declarator, 表示 declarator 的型別, 所以這裡有 const 表示是唯讀的指標; * 的左邊是 declaration specifier, 這裡有 const 表示指到的型別為唯讀。附帶一提, 用英文來解讀的話似乎比較清楚, 不知是自己受程式語言的影響, 還是英文天性比較有邏輯一點, 總覺得用英文思考比較有利於理解數學、程式之類的事。

比方說:

  • int *p: p is a pointer to a int
  • int *p[N]: p is an array of pointer to int (用用運算子的優先順序看)

善用 const 產生 immutable 的效果可以減少 bug、易於維護, 學好這些觀念滿實用的。作者建議將 * 貼近 declarator 並且將 const 寫在最右側, 避免誤會。 * 貼近 declarator 是因為 compiler 本來就是這麼解讀的, 將 * 放在 int 旁會不易分辨 declaration specifier 和 declarator。而將 const 寫在最右側則可避免誤會, 像文中的例子:

typedef void *VP;
const VP vectorTable[]

原以為表示 vectorTable 的內容都為 const, 但 compiler 會解讀成

VP const vectorTable[];
// 等同於
void * const vectorTable[];

變成 vectorTable 是一個陣列, 每一個元素是唯讀的指標, 指標指向的型別為 void (即沒指定特定型別)。若希望宣告 vectorTable 的指標不為唯讀, 是指到的型別唯讀, 有兩種作法:

// 1.
void const * vectorTable[];
// 2.
typedef void const *VCP;
VCP vectorTable[];

寫完這篇後讓我想到 int **p 的情況, * 和 * 之間可以插三個 const, 這才體會到作者強調的觀念:

Although C and C++ read mostly from top-to-bottom and left-to-right, pointer declarations read, in a sense, backward.

用這種方式解讀 int const * const * const p 的確比較直覺, p is a read-only pointer to a read-only pointer to a read-only int。

ps. const-correctness 提到更完整的 const 語意, 包含函式的部份。

1 則留言:

在 Fedora 下裝 id-utils

Fedora 似乎因為執行檔撞名,而沒有提供 id-utils 的套件 ,但這是使用 gj 的必要套件,只好自己編。從官網抓好 tarball ,解開來編譯 (./configure && make)就是了。 但編譯後會遇到錯誤: ./stdio.h:10...