关于混淆:C中的>>>=运算符是什么?

What is the >>>= operator in C?

一位同事把它当成一个谜,我不知道这个C程序是如何编译和运行的。这个>>>=运算符和奇怪的1P1字是什么?我在Clang和GCC进行过测试。没有警告,输出为""。??"

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>

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

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

    return 0;
}


线:

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

包含有向图:><:,分别转换为][,因此相当于:

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

文字0xFULL0xF相同(对于15为十六进制);ULL只是指明它是unsigned long long文字。在任何情况下,作为一个布尔值,它都是真的,所以0xFULL ? '\0' : -1的计算结果是'\0',它是一个字符文字,其数值仅为0

同时,0X.1P1是十六进制浮点文字,等于2/16=0.125。在任何情况下,非零的情况下,作为一个布尔值也是正确的,因此用!!否定它两次会再次产生1。因此,整个过程简化为:

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

运算符>>=是一个复合赋值,它按右操作数给定的位数将左操作数右移,并返回结果。在这种情况下,右操作数a[1]的值总是1的值,因此它相当于:

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

或者,等价地:

1
while( a[0] /= 2 )

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


这是一些相当模糊的有向图代码,即<::>,分别是[]的替代标记。还有一些条件运算符的用法。还有一个位移位运算符,即右移位指定>>=

这是一个更易读的版本:

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

以及更可读的版本,将[]中的表达式替换为它们解析的值:

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

a[0]a[1]替换为它们的值应该可以很容易地了解循环在做什么,即等效于:

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

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


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

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

我注意到的第一件事是,我们使用的是来自?的三元运算符。所以子表达式:

1
0xFULL ? '\0' : -1

表示"如果0xFULL为非零,则返回'\0',否则返回-10xFULL是一个带有无符号长后缀的十六进制文字,也就是说它是unsigned long long类型的十六进制文字。但这并不重要,因为0xF可以放入一个正整数中。

另外,三元运算符将第二个和第三个术语的类型转换为它们的公共类型。然后将'\0'转换为int,即0

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

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

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

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

>>=是有符号的右移位运算符,我们可以把它从a中去掉,使其更清晰。

此外,<:是一个有向图,它扩展到[上:

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

0X.1P1是一个十六进制的带指数的文字。但无论价值如何,任何非零的东西的!!都是正确的。0X.1P10.125非零,故:

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

>>=是签名的右移位运算符。它通过按运算符右侧的值向前移动其位来更改左操作数的值。二进制中的101010。下面是步骤:

1
2
3
4
01010 >> 1 == 00101
00101 >> 1 == 00010
00010 >> 1 == 00001
00001 >> 1 == 00000

>>=返回其操作的结果,只要移位a[0]保持非零,每次其位右移一位,循环将继续。第四次尝试是在a[0]变成0的地方,所以循环从未进入。

因此,?打印了三次。