摘取:
(提示:在用引号括起来的字符串中,注释符I*属于字符串的一部分,而在注释中出现的双引号””又属于注释的一部分。)”
我觉得这对C的词法分析挺有意思的。要是聪明的您,有什么办法呢?
以下是作者给出的答案,确实很精妙,尤其第二个。
——为了判断编译器是否允许嵌套注释,必须找到这样一组符号序列,无论是对于允许嵌套注释的编译器,还是不允许嵌套注释的编译器,它都是合法的;但是,对于两类不同的编译器,它却意味着不同的事物。这样一组符号序列不可避免地要涉及嵌套注释,让我们从这里开始讨论:
/*/**/
对于一个允许嵌套注释的C编译器,无论上面的符号序列后面跟什么,都属于注释的一部分;而对于不允许嵌套注释的C编译器,后面跟的就是实实在在的代码内容。也许有人因此想到可以在后面再跟一个用一对引号引起的注释结束符:
/*/**/ "*/"
如果允许嵌套注释,上面的符号序列就等效于一个引号;如果不允许,那么就等效于一个字符串"*I"。因此,我们可以接着在后面跟一个注释开始符以及一个引号:
/*/**/
"*/"/*"
如果允许嵌套注释,上面就等效于用一对引号引起的注释开始符"/*";如果不允许,那么就等效于一个用引号括起的注释结束符,后跟一段未结束的注释。我们可以简单地让最后的注释结束:
/*/**/ "*/"/*" /**/
这样,如果允许嵌套注释,上面的表达式就等效于"/*",:如果不允许,那么就等效于,"*/"。
在我用基本上类似于上面的形式解决这个问题之后,Doug McIlroy发现了下面这个让人拍案叫绝的解法:
/*/*/0*/**/1
这个解法主要利用了编译器作词法分析时的“大嘴法”规则。如果编译器允许嵌套注释,则上式将被解释为:
/*/*/0*/**/1
两个/*符号与两个*/符号正好匹配,所以上式的值就是1。如果不允许嵌套注释,注释中的/*将被忽略。因此,即使卿出现在注释中也没有特殊的含义:上面的表达式因此将被这样解释:
/*/*/0*/**/1
它的值就是0*1,也就是0o
上面利用一个特殊构造的字符串就完成了这个任务,果然精妙!
而当时我对这个特殊字符串推导时没有看到希望,用了宏来帮忙,设计了下面的程序:
1 #define A /* aaa /* a*/ a 2 #define B */ 3 4 bool CanNesting() 5 { 6 #ifdef B 7 return false; 8 #else 9 return true; 10 #endif 11 }
上面程序中,如果支持嵌套,B的宏定义属于注释的一部分,所以B应该没有被定义,函数返回true。否则,就不支持嵌套,函数返回false。
问题来源:《C陷阱与缺陷》
练习1-1. 某些C编译器允许嵌套注释。请写一个测试程序,要求:无论是对允许嵌套注释的编译器,还是对不允许嵌套注释的编译器,该程序都能正常通过编译(无错误消息出现),但是这两种情况下程序执行的结果却不相同。
提示:被双引号括起来的字符串中,注释符/*属于字符串的一部分,而在注释中出现的双引号又属于注释的一部分。
我的解答如下:
首先我们必须找一串符号序列,它满足在不同的编译器下都是合法的,但意义不同。而要形成嵌套注释,则这串序列必须至少含有这样的字符:/*/**/*/。
我们先从/*/**/开始讨论。对允许嵌套注释的编译器,/*/**/后面的字符都是注释的一部分,而对不允许注释的编译器,这就是一个完整的注释了。
然后我们在后面加上"*/",变成/*/**/"*/"。对允许嵌套注释的编译器,/*/**/"*/"等效于一个引号;对不允许注释的编译器/*/**/"*/"等效于字符串"*/"。此时对允许嵌套注释的编译器无法通过编译,即剩下一个引号,我的想法是把这个引号给注释掉,由于是在C++的编译器,我直接就在引号前面添加了两干,变成/*/**/"*///",有点投机取巧的意味。
但用C的注释符也是可以的,在/*/**/"*/"后面加上/*"变成/*/**/"*/"/*"。对允许嵌套注释的编译器,/*/**/"*/"/*"等效于字符串"/*";对不允许注释的编译器,/*/**/"*/"/*"等效于字符串和一个不完整的注释"*/"/*"。
最后可以在后面加上/**/完成注释。故最终的字符串为/*/**/"*/"/*"/**/。
在作者的解答里面有一个很神奇神奇的答案:/*/*/0*/**/1
大家就自己分析下啦!挺有趣的!
以下是最终写的程序的代码,包含以上提到的几种方案,很简单
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 int main() 5 { 6 char* str=("hello world"); 7 /*/**/str="*///hello world"; 8 printf("%s\n",str); 9 str=/*/**/"*/"/*"/**/; 10 printf("%s\n",str); 11 int i = /*/*/0*/**/1; 12 printf("%d\n",i); 13 system("pause"); 14 return 0; 15 } 16