同学看完我的一段有趣的程序后,给了我一段bt的程序:
|
|
俺就又本着人不bt枉少年的精神,又bt的研究了一下。程序贴到vc里,编译出现三个错误,一个是printf未定义,一个是F_00未定义。改动后如下,同时将程序结构改的可读性更强:
|
|
首先,这个程序bt到用00和OO近似来混淆视听!OO是两个大写字母o,可以作为变量名,而00单独写只是数字0,不能作为变量名。
程序首先定义了一段宏_
(md,又是这个下划线),之后定义了两个变量F和OO,并赋予初值0。这里要注意,宏里使用到的就是这两个变量。
之后定义了一个函数F_00(),大量调用了这个宏,来改变变量F和OO的值。虽然程序看起来bt,但实际上归结起来只有两种语句“_
”和“-_
”两种。这两语句在进行宏替换后分别为“-F<00||--F-OO--;
”和“--F<00||--F-OO--;
”
语句“-F<00||--F-OO--;
”根据优先级判断,先执行||
左边的部分,由于F=0,所以左部分为假(0<0为假),在执行右半部分,先对F自减1,执行F-OO
,再对OO自减1。最后的结果为-1,再进行||
,舍弃最后结果。折腾半天,只有对F和OO的自减使变量发生了变化,最终的运算结果并不care。
语句“--F<00||--F-OO--;
”根据优先级判断,先执行||
左边部分,对F自减1后判断是否F<0。经过上个语句后,F值为-1,因此F<0为真,不再执行右半部分。
但是注意!*在单步跟踪“-_
”语句时,变量的值没有发生任何变化!*也就是说刚才的分析并不是实际情况。由于变量值没有任何改变,可以肯定||
右边的部分根本没有被运行,也就是说||
左边的部分结果为真。如果左边的语句解释为“- -F<00
”(注意两个减号中间的空格),则F的值没有被改变,且其值为真。因此,这里的“-_
”语句实际是被解释成“- -F<00||--F-OO--;
”,也就是“(-(-F))<00||--F-OO--;
”。
由于“-_
”没有对变量的值进行任何操作,因此函数F_00()
里看似唬人的大球,实际每行只有行首的“_
”语句起作用,是对两个变量的值自减1。因此,整个高度为16的球,实际对两个变量进行16次自减1。真是无聊!!!!!
主程序就简单多了。首先运行F_00()
改变两个变量的值,最后运行printf("%1.3f\n",4.*-F/OO/OO)
语句输出。这个printf语句就简单了,输出一个长度为1,3位小数宽度的浮点数并换行。因为浮点的总长度肯定大于1,所以相当于输出一个3位小数宽的浮点并换行。输出的浮点内容为“4.*-F/OO/OO
”,也就是“(4.)*(-F)/OO/OO
”,此时F和OO的值都是“-16”,结果很显然,没啥大意思了。
前面所讲到的问题,应该涉及到c语言编译器实现的细节问题。c语言编译器指对宏进行简单替换,而不进行任何语法判断,因此可以肯定,对宏的替换工作是先于语法判断和编译过程的。c编译器在进行语法判断时,使用的是大嘴原则,也就是尽可能将更多的字符解释为运算符等关键字,而实际中并没有将“-_
”解释为“--F<00||--F-OO--;
”,可以认为,编译器在替换前,会对宏的前后进行标识,将宏本身与前后的语句分开,这样,宏本身的运算符就不会与程序前后的运算符关联在一起,改变宏语句本身的运算符的意义。因此,这个程序中的宏最终解释为“- -F<00||--F-OO--;
”。
另一个值得注意的问题是,“-_
”在解释为“- -F<00||--F-OO--;
”之后,实际宏本身的运算顺序已经改变。这里也是为什么在《c陷阱与缺陷》里,作者特别提到,宏的定义应该加括号,格式为“#define NN (…)”,以免程序中出现很隐蔽的,改变宏本身运算顺序的逻辑错误。
为了验证编译器对宏的替换过程,编写了下面的程序:
实际程序运行通过,证明“a=a+++A
”被解释为“a=a+++ ++a
”即“a=(a++)+(++a)
”,而不是“a=a+++++a
”。其中后一种译法会因为大嘴原则译为“a=((a++) ++)+a
”第二层的“++
”会因为左边不是一个有效变量,而引起编译错误:error C2105: '++' needs l-value
。