一个同事给了我一个难题,我无法弄清楚这个C程序实际上是如何编译和运行的。这个>>>=操作符和奇怪的1P1字面值是什么?我在Clang和GCC中进行了测试。没有警告,输出为“??”
>>>=
1P1
#include <stdio.h> int main() { int a[2]={ 10, 1 }; while( a[ 0xFULL?'\0':-1:>>>=a<:!!0X.1P1 ] ) printf("?"); return 0; }
这是一些相当晦涩的代码,涉及的有向图< em > < / em >,即<:和:>,它们分别是[和]的替代令牌。还有一些< em >条件操作符< / em >的用法。还有一个位移位运算符,右移赋值>>=。
<:
:>
[
]
>>=
下面是一个可读性更好的版本:
while( a[ 0xFULL ? '\0' : -1 ] >>= a[ !!0X.1P1 ] )
和一个更可读的版本,替换[]中的表达式为它们解析的值:
[]
while( a[0] >>= a[1] )
替换a[0]和a[1]的值应该可以很容易地找出循环在做什么,即相当于:
a[0]
a[1]
int i = 10; while( i >>= 1)
它只是在每次迭代中执行(整数)除2,产生序列5, 2, 1。
5, 2, 1
线:
while( a[ 0xFULL?'\0':-1:>>>=a<:!!0X.1P1 ] )
包含有向图 :>和<:,它们分别转换为]和[,因此它等价于:
while( a[ 0xFULL?'\0':-1 ] >>= a[ !!0X.1P1 ] )
字面量0xFULL与0xF相同(它是15的十六进制);ULL只是指定了它是unsigned long long字面量。在任何情况下,作为一个布尔值为真,因此0xFULL ? '\0' : -1的值为'\0',这是一个字符文字,其数值为0。
0xFULL
0xF
15
ULL
unsigned long long
0xFULL ? '\0' : -1
'\0'
0
同时,0X.1P1是一个十六进制浮点字面值,等于2/16 = 0.125。在任何情况下,它是非零的,作为一个布尔值也是正确的,所以用!!对它进行两次否定仍然会生成1。因此,整个事情可以简化为:
0X.1P1
!!
1
操作符>>=是一个复合赋值,它将其左操作数按右操作数给出的位数向右移位,并返回结果。在这种情况下,右操作数a[1]的值总是1,因此它等价于:
while( a[0] >>= 1 )
或者,相当于:
while( a[0] /= 2 )
a[0]的初始值是10。向右移动一次后,它变成5,然后(四舍五入)2,然后是1,最后是0,在这一点上循环结束。因此,循环体被执行了三次。
让我们从左到右看看这个表达式:
a[ 0xFULL?'\0':-1:>>>=a<:!!0X.1P1 ]
我注意到的第一件事是,我们使用了来自?的三元操作符。那么子表达式是:
?
表示“如果0xFULL非零,则返回'\0',否则返回-1。”0xFULL是一个带有无符号long-long后缀的十六进制字面值——这意味着它是一个类型为unsigned long long的十六进制字面值。不过这并不重要,因为0xF可以放入一个常规整数中。
-1
此外,三元运算符将第二项和第三项的类型转换为它们的公共类型。然后'\0'被转换为int,也就是0。
int
0xF的值远大于0,所以它通过。表达式现在变成:
a[ 0 :>>>=a<:!!0X.1P1 ]
接下来,:>是一个有向图。它是一个扩展到]的结构:
a[0 ]>>=a<:!!0X.1P1 ]
>>=是有符号的右移操作符,我们可以将其与a分隔开来,使其更清楚。
a
而且,<:是一个扩展到[的有向图:
a[0] >>= a[!!0X.1P1 ]
0X.1P1是一个带指数的十六进制字面值。但无论值是什么,任何非零的!!都是真。0X.1P1是0.125,它是非零的,所以它变成:
0.125
a[0] >>= a[true] -> a[0] >>= a[1]
>>=是有符号右移操作符。它通过将左操作数的位前移到操作符右侧的值来改变其左操作数的值。10的二进制格式是1010。以下是步骤:
10
1010
01010 >> 1 == 00101 00101 >> 1 == 00010 00010 >> 1 == 00001 00001 >> 1 == 00000
>>=返回其操作的结果,因此只要每次将a[0]的位右移1时,移位a[0]仍是非零,循环将继续。第四次尝试是a[0]变成0,因此永远不会进入循环。
结果,?被打印了三次。