星期一, 4月 13, 2009

Complex C Declarations

因為 olv 丟了一個複雜的 C 語言宣告給我:
 void (*glXGetProcAddressARB(const GLubyte *procName))( void )
勾起學生時代的回憶,所以把這篇 1991 年出現在 comp.lang.c 的文章挖出來,這裡可以下載完整的內容。

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.

利用 "right-left rule" 可以幫助我們正確地解讀 C 語言的宣告,不論是多麼複雜,即便是不合法的宣告。在解讀的過程中,若是遇到了上述表格裡的 * [ ] ( ) 符號,則將它們視為其後相對應的解釋。

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

步驟一、先把識別子(變數名或函式名稱)找出來,這時候你可以說:xxx 是一個 ...,例如 int *p[ ]

識別子是 p,翻成「p 是一個...」

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.)

步驟二、檢查識別子右邊出現的符號,如果有的話,例如看到 [ ] 我們可以說「xxx 是一個陣列,它的陣列元素是 ...」,若是看到 ( ) 則翻成「xxx 是一個函式,回傳值是 ...」,接著繼續地往識別子的右邊尋找其他符號,直到識別子右邊已經沒有符號或是遇到了 ")",跳到步驟三。

int *p[ ],往識別子 p 右邊遇到了符號 [ ],右邊也沒有其他符號,翻成「p 是一個陣列,其陣列元素是...」

另外,如果遇到的是 [N],也就是帶有陣列大小的陣列宣告,我們可以這樣翻「 xxx 是一個大小為 N 的陣列,它的陣列元素是...」,若是遇到的是 (yyy),我們可以翻成「xxx 是一個參數為 yyy 的函式,其回傳值為...」

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 "(".

步驟三、檢查識別子左邊的符號,如果出現的是 * [ ] ( ) 就照表翻譯,若出現的並不屬於上述的符號,那麼看到甚麼就說甚麼,不需要額外的翻譯,例如看到 int 就直接翻成 int,繼續地往識別子的左邊尋找,直到識別子左邊已經沒有其他符號或是遇到了 "(",接著回到步驟二(若是識別子左右兩邊已經沒有其他符號)

int *p[ ],往識別子左邊先找到了 *,所以翻成「p 是一個陣列,其陣列元素是指標,指向...」,繼續往左邊看,遇到了 int,由於識別子左右兩邊都沒有符號了,所以完整的解讀是「p 是一個陣列,其陣列元素是指標,指向 int」

「p 是一個陣列,其陣列元素是指向 int 的指標」

Now repeat steps 2 and 3 until you've formed your declaration.

重複地套用步驟二、三,直到完成整個宣告的解讀。

我相信剛接觸 C 語言的人很難分辨上述的 int *p[ ] 與 int (*p)[10] 的差別,試著套用 ”right-left-rule” 解讀:

1. int (*p)[10],識別子是 p,翻成「p 是一個」

2. 往p 的右邊看到的符號是 ”)”,根據步驟二的說明,我們跳到步驟三檢查識別子 p 的左邊

3. 往 p 的左邊看到的符號是 ”*”,翻成「p 是一個指標,指向...」

4. 繼續往 p 的左邊檢查,遇到的符號是 ”(”,根據步驟三的說明,我們回到步驟二檢查識別子 p 右邊的其他符號

5. 繼續往 p 右邊我們遇到了 [10],翻成「p 是一個指標,指向大小為10的陣列,其陣列元素是...」

6. 繼續往 p 右邊檢查,已經沒有其他符號了,根據步驟二的說明,接著繼續講查識別子 p 左邊的符號

7. 接著往 p 左邊我們遇到了 int,因為不屬於 * [ ] ( ) 中的一個,所以翻成「p 是一個指標,指向大小為10的陣列,其陣列元素是 int」

8. 識別子左右兩邊已經沒有符號,結束

知道了 p 的宣告,在寫程式的時候,就可以避免 assign 錯誤型態的變數給它的錯誤
int main(void) {
int array[10];
int (*p)[10] = &array;
array[2] = 111;
(*p)[2] = 222;
printf("%d\n", array[2]);
return 0;
}

回頭看 void (*glXGetProcAddressARB(const GLubyte *procName))( void ),套用 ”right-left-rule”:

1. 先把識別子找出來, glXGetProcAddressARB 是識別子,翻成「glXGetProcAddressARB 是一個」

2. 往識別子右邊看到的符號是 (const GLubyte *procName),翻成「glXGetProcAddressARB 是一個參數為 const GLubyte *procName 的函式,其回傳值為...」

3. 再往右邊看下去,我們遇到了 ”)”,根據步驟二的說明,我們跳到步驟三

4. 往識別子的左邊看到的符號是 ”*”,翻成「glXGetProcAddressARB 是一個參數為 const GLubyte *procName 的函式,其回傳值為指標,指向...」

5. 繼續往左邊看,我們遇到了符號 ”(“,根據步驟三,我們跳回步驟二

6. 回到步驟二,我們繼續往識別子右邊找其他的符號,這時出現的是 “( void )”,所以翻成「glXGetProcAddressARB 是一個參數為 const GLubyte *procName 的函式,其回傳值為指標,指向一個參數為 void的函式,其回傳值為...」

7. 再往右邊找,已經沒有任何符號,跳到步驟三

8. 往左邊找到了 void,直接翻,「glXGetProcAddressARB 是一個參數為 const GLubyte *procName 的函式,其回傳值為指標,指向一個參數為 void的函式,其回傳值為 void」

9. 識別子左右兩邊已經沒有符號了,完成
「glXGetProcAddressARB 是一個參數為 const GLubyte *procName 的函式,其回傳值為指標,指向一個參數為 void的函式,其回傳值為 void」再稍微修飾一下,改成「glXGetProcAddressARB 是一個參數為 const GLubyte *procName 的函式,其回傳值為函式指標,指向一個參數為 void的函式,其回傳值為 void」

void helloworld(void) { return; }

void (*glXGetProcAddressARB(char *procName))( void ) {
return helloworld;
}

int main(void) {
glXGetProcAddressARB("ARB");
return 0;
}

沒有留言: