《C陷阱與缺陷》中有一個(gè)有意思的問題:“某些C編譯器允許嵌套注釋。請寫一個(gè)測試程序,要求:無論是對(duì)允許嵌套注釋的編譯器,還是對(duì)不允許嵌套注釋的編譯器,該程序都能正常通過編譯(無錯(cuò)誤消息出現(xiàn)),但是這兩種情況下程序執(zhí)行的結(jié)果卻不相同。
(提示:在用引號(hào)括起來的字符串中,注釋符I*屬于字符串的一部分,而在注釋中出現(xiàn)的雙引號(hào)””又屬于注釋的一部分。)”
我覺得這對(duì)C的詞法分析挺有意思的。要是聰明的您,有什么辦法呢?
以下是作者給出的答案,確實(shí)很精妙,尤其第二個(gè)。
——為了判斷編譯器是否允許嵌套注釋,必須找到這樣一組符號(hào)序列,無論是對(duì)于允許嵌套注釋的編譯器,還是不允許嵌套注釋的編譯器,它都是合法的;但是,對(duì)于兩類不同的編譯器,它卻意味著不同的事物。這樣一組符號(hào)序列不可避免地要涉及嵌套注釋,讓我們從這里開始討論:
/*/**/
對(duì)于一個(gè)允許嵌套注釋的C編譯器,無論上面的符號(hào)序列后面跟什么,都屬于注釋的一部分;而對(duì)于不允許嵌套注釋的C編譯器,后面跟的就是實(shí)實(shí)在在的代碼內(nèi)容。也許有人因此想到可以在后面再跟一個(gè)用一對(duì)引號(hào)引起的注釋結(jié)束符:
/*/**/ "*/"
如果允許嵌套注釋,上面的符號(hào)序列就等效于一個(gè)引號(hào);如果不允許,那么就等效于一個(gè)字符串"*I"。因此,我們可以接著在后面跟一個(gè)注釋開始符以及一個(gè)引號(hào):
/*/**/ "*/"/*"
如果允許嵌套注釋,上面就等效于用一對(duì)引號(hào)引起的注釋開始符"/*";如果不允許,那么就等效于一個(gè)用引號(hào)括起的注釋結(jié)束符,后跟一段未結(jié)束的注釋。我們可以簡單地讓最后的注釋結(jié)束:
/*/**/ "*/"/*" /**/
這樣,如果允許嵌套注釋,上面的表達(dá)式就等效于"/*",:如果不允許,那么就等效于,"*/"。
在我用基本上類似于上面的形式解決這個(gè)問題之后,Doug McIlroy發(fā)現(xiàn)了下面這個(gè)讓人拍案叫絕的解法:
/*/*/0*/**/1
這個(gè)解法主要利用了編譯器作詞法分析時(shí)的“大嘴法”規(guī)則。如果編譯器允許嵌套注釋,則上式將被解釋為:
/*/*/0*/**/1
兩個(gè)/*符號(hào)與兩個(gè)*/符號(hào)正好匹配,所以上式的值就是1。如果不允許嵌套注釋,注釋中的/*將被忽略。因此,即使卿出現(xiàn)在注釋中也沒有特殊的含義:上面的表達(dá)式因此將被這樣解釋:
/*/*/0*/**/1
它的值就是0*1,也就是0o
上面利用一個(gè)特殊構(gòu)造的字符串就完成了這個(gè)任務(wù),果然精妙!
而當(dāng)時(shí)我對(duì)這個(gè)特殊字符串推導(dǎo)時(shí)沒有看到希望,用了宏來幫忙,設(shè)計(jì)了下面的程序:
#define A /* aaa /* a*/ a
#define B */

bool CanNesting()


{
#ifdef B
return false;
#else
return true;
#endif
}
上面程序中,如果支持嵌套,B的宏定義屬于注釋的一部分,所以B應(yīng)該沒有被定義,函數(shù)返回true。否則,就不支持嵌套,函數(shù)返回false。