一个同事给了我一个难题,我无法弄清楚这个C程序实际上是如何编译和运行的。这个>>>=运算符和奇怪的1P1字面值是什么?我在Clang和GCC中进行了测试。没有警告,输出为“??”

#include <stdio.h>

int main()
{
    int a[2]={ 10, 1 };

    while( a[ 0xFULL?'\0':-1:>>>=a<:!!0X.1P1 ] )
        printf("?");

    return 0;
}

这是一些相当晦涩的代码,涉及到有向图,即<:和:>,它们分别是[和]的替代标记。还有一些条件运算符的用法。还有一个位移位操作符,右移位赋值>>=。

下面是一个可读性更好的版本:

while( a[ 0xFULL ? '\0' : -1 ] >>= a[ !!0X.1P1 ] )

还有一个更可读的版本,替换[]中的表达式为它们解析的值:

while( a[0] >>= a[1] )

替换[0]和[1]的值应该可以很容易地找出循环在做什么,即等价于:

int i = 10;
while( i >>= 1)

它只是在每次迭代中执行(整数)除2,产生序列5,2,1。


线:

while( a[ 0xFULL?'\0':-1:>>>=a<:!!0X.1P1 ] )

包含有向图:>和<:,它们分别转换为]和[,因此它等价于:

while( a[ 0xFULL?'\0':-1 ] >>= a[ !!0X.1P1 ] )

文字0xFULL与0xF相同(它是十六进制的15);ULL只是指定它是一个无符号的长长文字。在任何情况下,作为一个布尔值,它是真的,所以0xFULL ?'\0': -1的值为'\0',这是一个字符字面值,其数值为0。

与此同时,0 x。1P1是一个十六进制浮点字面值,等于2/16 = 0.125。在任何情况下,它是非零的,作为一个布尔值也是正确的,所以用!!结果还是1。因此,整个事情可以简化为:

while( a[0] >>= a[1] )

操作符>>=是一个复合赋值,它将左操作数按右操作数给出的位数向右移位,并返回结果。在这种情况下,右操作数[1]的值总是1,所以它等价于:

while( a[0] >>= 1 )

或者,相当于:

while( a[0] /= 2 )

[0]的初始值为10。向右移动一次后,它变成5,然后(四舍五入)2,然后是1,最后是0,在这一点上循环结束。因此,循环体被执行了三次。


让我们从左到右看看这个表达式:

a[ 0xFULL?'\0':-1:>>>=a<:!!0X.1P1 ]

我注意到的第一件事是我们使用的是三元运算符。那么子表达式是:

0xFULL ? '\0' : -1

如果0xFULL非零,返回'\0',否则返回-1。0xFULL是一个带unsigned long-long后缀的十六进制字面值——这意味着它是unsigned long-long类型的十六进制字面值。不过这并不重要,因为0xF可以放入一个常规整数中。

此外,三元运算符将第二项和第三项的类型转换为它们的公共类型。然后'\0'被转换为int,也就是0。

0xF的值远大于0,所以它通过了。表达式现在变成:

a[ 0 :>>>=a<:!!0X.1P1 ]

接下来,:>是一个有向图。它是一个扩展为]的结构:

a[0 ]>>=a<:!!0X.1P1 ]

>>=是带符号的右移运算符,我们可以把它和a分开,这样更清楚。

此外,<:是展开为[:

a[0] >>= a[!!0X.1P1 ]

0 x。1P1是一个带指数的十六进制字面值。但无论价值如何,这个!!任何非零的都是真的。0 x。1P1是非零的0.125,所以它变成:

a[0] >>= a[true]
-> a[0] >>= a[1]

>>=是有符号右移运算符。它通过将左操作数的位前移到操作符右侧的值来改变其左操作数的值。10的二进制是1010。以下是步骤:

01010 >> 1 == 00101
00101 >> 1 == 00010
00010 >> 1 == 00001
00001 >> 1 == 00000

>>=返回其操作的结果,因此只要每次将[0]的位右移1时,它的移位仍然是非零,循环将继续。第四次尝试时,[0]变为0,因此永远不会进入循环。

因此,?打印三次。