本文來自網站 ieng9.ucsd.edu (http://ieng9.ucsd.edu/~cs30x/rt_lt.rule.html) 加以重新排版.

呃… 它搬家了, 新網址如後: cseweb.ucsd.edu (http://cseweb.ucsd.edu/~ricko/rt_lt.rule.html)

本文開始: (修訂二版)


The "right-left" rule is a completely regular rule for deciphering C declarations. It can also be useful in creating them.

First, symbols. Read

*as "pointer to"- always on the left side
[]as "array of"- always on the right side
()as "function returning"- always on the right side

as you encounter them in the declaration.

"右-左" 法則 (先右後左) 是一套用於解讀 C 語言宣告式的完整的正規法則. 它在撰寫 C 語言的宣告式上, 也同樣有用.

首先是符號 (即: 運算子, 以下我統一使用運算子)(註一). (當你在解讀宣告式時遇到下列的運算子時, 它們的) 讀法是(註二):

*讀作 "指標 (其/此指標)指向的是…"- 只會在 (辨識字的) 左邊出現
[]讀作 "陣列 (其/此陣列的)元素是…"- 只會在 (辨識字的) 右邊出現
()讀作 "函式 (其/此函式的)回傳值是…"- 只會在 (辨識字的) 右邊出現

註一: 指的是宣告式內, 在辨識字前/後的運算子 (它們和這個辨識字的資料型態相關), 總共有三種: * (指標), [] (陣列), () (函式). 另外還會有改變運算優先次序的括號運算子 (()). 函式運算子和括號運算子雖然都是用(), 二者之間依然可以明確的分辨, 並不會造成語義不清的現象 (ambiguous ). 其差異在於: 在宣告式中, 函式運算子的(只會出現在辨識字的右邊; 而括號運算子的(只會出現在辨識字的左邊.

註二: 中文比較習慣的是說法是 指向…的指標, 元素內容為…的陣列, 回傳值為…的函式, 但是這樣的語法會使得我們相對的容易把修飾對象搞混, 尤其是宣告式中的運算子數量很多的時候. 因此在這裡我比照英文的次序翻譯, 最後再進一步的轉換成我們比較習慣的說法. 同時, 請注意後面的 修飾的對象就是它前面的 指標, 陣列或函式.

STEP 1

Find the identifier. This is your starting point. Then say to yourself, "identifier is". You've started your declaration.

STEP 2

Look at the symbols on the right of the identifier. If, say, you find "()" there, then you know that this is the declaration for a function. So you would then have "identifier is function returning". Or if you found a "[]" there, you would say "identifier is array of". Continue right until you run out of symbols *OR* hit a *right* parenthesis ")". (If you hit a left parenthesis, that's the beginning of a () symbol, even if there is stuff in between the parentheses. More on that below.)

STEP 3

Look at the symbols to the left of the identifier. If it is not one of our symbols above (say, something like "int"), just say it. Otherwise, translate it into English using that table above. Keep going left until you run out of symbols *OR* hit a *left* parenthesis "(".

Now repeat steps 2 and 3 until you've formed your declaration. Here are some examples:

步驟一
先要找到辨識字 (變數名或者函式名). 這是你的起點. 然後你把它讀作 "某某某是…".

步驟二
再來看辨識字右手邊的運算子. 例如: 你找到的是 "()", 那你知道這是一個函式的宣告式. 因此你把它讀成 "某某某是函式, 其回傳值是…". 又或者你找到的是 "[]", 則要把它讀成 "某某某是陣列, 其元素是…". 如此繼續向右推進, 直到沒有運算子了, 或者你遇到一個單獨的右括號 ")"(註三). (如果你遇到一個左括號, 那它只是 "()" 的開頭, 即便是左右括號中有其他的東西. 關於這個部份後面會再詳細說明)

步驟三
接著看辨識字左手邊的運算子. 如果它不是我們上面列的那三種運算子 (例如像是 "int") 那就直接讀出來. 否則 (是這三種運算子的情況) 就依照上表列的把它轉譯出來. 如此繼續向左推進, 直到沒有運算子了, 或者你遇到一個單獨的左括號 "("(註四).

重複步驟二和步驟三, 直到讀完整個宣告式. 下面來看幾個例子:

註三: 此處的右括號就是括號運算子中的右括號.

註四: 此處的左括號就是括號運算子中的左括號.

int *p[];

1) Find identifier.
    找出辨識字.
int *p[];
     ^
"p is"
p 是…
2) Move right until out of symbols or right parenthesis hit.
    向右移動及解讀直到沒有運算子了, 或者你遇到右括號.
int *p[];
      ^^
"p is array of"
p 是陣列, 其元素是…
3) Can't move right anymore (out of symbols), so move left and find:
    無法繼續右移 (沒有運算子)了, 所以向左移動並發現了:
int *p[];
    ^
"p is array of pointer to"
p 是陣列, 其元素是指標, 其指向的是…
4) Keep going left and find:
    繼續左移並發現了:
int *p[];
^^^
"p is array of pointer to int".
p 是陣列, 其元素是指標, 其指向的是 int(註五).
(or "p is an array where each element is of type pointer to int")

註五: 重新順一下, 轉成我們平常慣用的中文說法: p 是一個陣列, 此陣列的元素是指向 (int) 的指標. (抱歉, 功力不佳, 一對一翻真的沒辦法翻得非常順.)

Another example:

int *(*func())();

1) Find identifier.
    找出辨識字.
int *(*func())();
       ^^^^
"func is"
func 是…
2) Move right.
    向右移動.
int *(*func())();
           ^^
"func is function returning"
func 是函式, 其回傳值是…
3) Can't move right anymore because of the right parenthesis, so move left.
    由於遇到了右括號, 無法繼續右移. 故向左移動.
int *(*func())();
      ^
"func is function returning pointer to"
func 是函式, 其回傳值是指標, 其指向的是…
4) Can't move left anymore because of the left parenthesis, so keep going right.
    由於遇到了左括號, 無法繼續左移. 故改為向右繼續.
int *(*func())();
              ^^
"func is function returning pointer to function returning"
func 是函式, 其回傳值是指標, 其指向的是函式, 其回傳值是…
5) Can't move right anymore because we're out of symbols, so go left.
    由於沒有運算子無法繼續右移了. 所以向左.
int *(*func())();
    ^
"func is function returning pointer to function returning pointer to"
func 是函式, 其回傳值是指標, 其指向的是函式, 其回傳值是指標, 其指向的是…
6) And finally, keep going left, because there's nothing left on the right.
    最後, 繼續左移, 因為右邊空空如也.
int *(*func())();
^^^
"func is function returning pointer to function returning pointer to int".
func 是函式, 其回傳值是指標, 其指向的是函式, 其回傳值是指標, 其指向的是 int(註六).

註六: 轉成我們平常慣用的中文說法是: func 是一個函式, 此函式的回傳值是指向函式的指標, 而該函式的回傳值是指向 int 的指標.

As you can see, this rule can be quite useful. You can also use it to sanity check yourself while you are creating declarations, and to give you a hint about where to put the next symbol and whether parentheses are required.

如同你見到的, 此一法則相當有用. (即便是) 在你撰寫宣告式的時候, 你自己也可以用它來檢查完整性, 提示你下一組運算子該放在哪裡? 和是不是需要附加上一組括號?

Some declarations look much more complicated than they are due to array sizes and argument lists in prototype form. If you see "[3]", that's read as "array (size 3) of...". If you see "(char *,int)" that's read as "function expecting (char *,int) and returning...". Here's a fun one:

int (*(*fun_one)(char *,double))[9][20];

I won't go through each of the steps to decipher this one.

Ok. It's:

"fun_one is pointer to function expecting (char *,double) and returning pointer to array (size 9) of array (size 20) of int."

有些宣告式看上去比我們前面說的要複雜許多, 原因是他們附加了陣列的元素個數或者宣告函式原型的參數表列. 如果你看到了 [3], 它應該讀成 陣列 (含有 3 個元素), 其元素是… 如果你看到了 (char *, int), 它應該讀作函式, 其傳入的參數是 (char *, int), 其回傳值是… 下面是 fun one 的例子: (雙關語, 一個有趣的例子)

int (*(*fun_one)(char *,double))[9][20];

這個例子我就不再逐步進行解讀了.

OK. 結果是:
fun_one 是指標, 其指向的是函式, 其參數是 (char *, double), 其回傳值是指標, 其指向的是陣列 (含有 9 個元素), 其元素是陣列 (含有 20 個元素), 其元素是 int(註七).

註七: 轉成我們平常慣用的中文說法是: fun_one 是一個指向函式的指標, 此函式的傳入參數是 (char *, double), 回傳值是指向陣列的指標, 而該陣列含有 9 個元素, 元素的內容是 20 個 int 的陣列.

As you can see, it's not as complicated if you get rid of the array sizes and argument lists:

int (*(*fun_one)())[][];

You can decipher it that way, and then put in the array sizes and argument lists later.

如同下一行所展示的, 如果你先甩掉陣列的元素個數以及函式的參數表列, 就沒那麼的複雜:

int (*(*fun_one)())[][];

你可以先這樣解讀完宣告式, 之後再將陣列的元素個數以及函式的參數表列加回去.

Some final words:

It is quite possible to make illegal declarations using this rule, so some knowledge of what's legal in C is necessary. For instance, if the above had been:

int *((*fun_one)())[][];

it would have been "fun_one is pointer to function returning array of array of pointer to int". Since a function cannot return an array, but only a pointer to an array, that declaration is illegal.

Illegal combinations include:

  • []() - cannot have an array of functions
  • ()() - cannot have a function that returns a function
  • ()[] - cannot have a function that returns an array

In all the above cases, you would need a set of parens to bind a * symbol on the left between these () and [] right-side symbols in order for the declaration to be legal.

最後

用這個法則有非常大的可能會製造出一些不合 C 的語法的宣告式. 因此, 具備一些關於什麼是合乎 C 的語法規則的知識是必要的. 譬如把上面的例子改成:

int *((*fun_one)())[][];

把它解讀出來會是 "fun_one 是一個指標, 其指向的是函式, 其回傳值是陣列, 其元素是陣列, 其元素是指標, 其指向的是 int"(註八). 由於函式無法回傳一整個陣列, 取而代之只能回傳一個指向陣列的指標, 而因此這個宣告式是不合語法的.

不合語法的組合包括:

  • []() - 沒有這種東西: 陣列, 其元素內容是函式.
  • ()() - 沒有這種東西: 函式, 其回傳值是函式.
  • ()[] - 沒有這種東西: 函式, 其回傳值是陣列.

以上的例子, 如果想要把它轉換成合於語法的宣告式, 你需要用一組括號運算子來括住: 一個在左邊的 * (指標運算子) 與 ()[] 之間的右邊運算子(註九).

註八: 轉成我們平常慣用的中文說法是: fun_one 是一個指向函式的指標, 此函式的回傳值是一個陣列, 此一陣列的元素內容是另一個陣列, 該陣列的元素是指向 int 的指標. 其中紅色加底線者即為語法錯誤的部份.

註九: 到底是在練什麼肖話! 換句話說, 重來一次: 其實就是在前面 (辨識字及已經處理的部份) 補一個左括號*, 然後這一組 ()[] 運算子之間插入一個右括號. 例如:
af[]() --> (*apf[])()
ff()() --> (*fpf())()
fa()[] --> (*fpa())[]

以上三個之所以要這麼改是因為:

  1. C 無法以函式為陣列元素, 只能用 (指向該函式的) 指標來替代.
  2. C 無法回傳一個函式, 只能用 (指向該函式的) 指標來替代.
  3. C 無法回傳一整個陣列, 只能用 (指向該陣列的) 指標來替代.

因此, 我們在左側多加一個 * (指標運算子). 但是緊接在右側的第二組 () (或 []) 它的優先權高於我們附加在左側的 *. 因此我們需要再多加一組括號將左側的 * 與第一組[] (或 ()) 括住, 用來覆蓋緊接在右側的第二組 () (或 []) 的優先權.

另外, 有二組以上緊鄰時, 處理原則是: 每次只需注意右側緊鄰的運算子即可. 處理好之後再繼續處理下一組右側緊鄰的運算子.

Here are some legal and illegal examples:
以下是一些合於語法和不合語法的例子:

Item Declare Description OK/NA
1. int i; an int OK
2. int *p; an int pointer (ptr to an int) OK
3. int a[]; an array of ints OK
4. int f(); a function returning an int OK
5. int **pp; a pointer to an int pointer (ptr to a ptr to an int) OK
6. int (*pa)[]; a pointer to an array of ints OK
7. int (*pf)(); a pointer to a function returning an int OK
8. int *ap[]; an array of int pointers (array of ptrs to ints) OK
9. int aa[][]; an array of arrays of ints OK
10. int af[](); an array of functions returning an int NA
11. int *fp(); a function returning an int pointer OK
12. int fa()[]; a function returning an array of ints NA
13. int ff()(); a function returning a function returning an int NA
14. int ***ppp; a pointer to a pointer to an int pointer OK
15. int (**ppa)[]; a pointer to a pointer to an array of ints OK
16. int (**ppf)(); a pointer to a pointer to a function returning an int OK
17. int *(*pap)[]; a pointer to an array of int pointers OK
18. int (*paa)[][]; a pointer to an array of arrays of ints OK
19. int (*paf)[](); a pointer to an array of functions returning an int NA
20. int *(*pfp)(); a pointer to a function returning an int pointer OK
21. int (*pfa)()[]; a pointer to a function returning an array of ints NA
22. int (*pff)()(); a pointer to a function returning a function returning an int NA
23. int **app[]; an array of pointers to int pointers OK
24. int (*apa[])[]; an array of pointers to arrays of ints OK
25. int (*apf[])(); an array of pointers to functions returning an int OK
26. int *aap[][]; an array of arrays of int pointers OK
27. int aaa[][][]; an array of arrays of arrays of ints OK
28. int aaf[][](); an array of arrays of functions returning an int NA
29. int *afp[](); an array of functions returning int pointers NA
30. int afa[]()[]; an array of functions returning an array of ints NA
31. int aff[]()(); an array of functions returning functions returning an int NA
32. int **fpp(); a function returning a pointer to an int pointer OK
33. int (*fpa())[]; a function returning a pointer to an array of ints OK
35. int (*fpf())(); a function returning a pointer to a function returning an int OK
36. int *fap()[]; a function returning an array of int pointers NA
37. int faa()[][]; a function returning an array of arrays of ints NA
38. int faf()[](); a function returning an array of functions returning an int NA
39. int *ffp()(); a function returning a function returning an int pointer NA

註十:

  • item 10 int af[]() 應改用 item 25 int (*apf[])().
  • item 12 int fa()[] 應改用 item 33 int (*fpa[])().
  • item 13 int ff()() 應改用 item 35 int (*fpf[])().
  • item 19 int (*paf)[]() 應改為 int (*(*papf)[])().
  • item 21 int (*pfa)()[] 應改為 int (*(*pfpa)())[].
  • item 22 int (*pff)()() 應改為 int (*(*pfpf)())().
  • item 28 int aaf[][]() 應改為 int (*aapf[][])().
  • item 29 int *afp[]() 應改為 int *(*apfp[])().
    注意: 辡識字前的*運算子, 它的優先權低於[]右側緊鄰的()運算子. 因此, (*是加在*的右側而不是左側.
  • item 30 int afa[]()[] 應改為 int (*(*apfpa[])())[].
  • item 31 int aff[]()() 應改為 int (*(*apfpf[])())().
  • item 36 int *fap()[] 應改為 int *(*fpap())[]. 同 item 29 的注意事項.
  • item 37 int faa()[][] 應改為 int (*fpaa())[][].
  • item 38 int faf()[]() 應改為 int (*(*fpapf())[])().
  • item 39 int *ffp()() 應改為 int *(*fpfp())(). 同 item 29 的注意事項.


本文結束.


2023/01/06


我重新 review 了一遍譯文, 並做了一些詞彙上的修訂和附加註解, 好讓它讀起來順暢一些. 同時也修正一處原文的錯誤 (來自舊原文站; 新原文站已修正).

arrow
arrow

    MagicJackTing 發表在 痞客邦 留言(6) 人氣()