什么是C中的>>> =运算符?

Cus*_*alc 291 c obfuscation bit-shift literals digraphs

由同事作为一个谜题,我无法弄清楚这个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;
}
Run Code Online (Sandbox Code Playgroud)

Ilm*_*nen 465

这条线:

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

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

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

字面值0xFULL0xF(为十六进制15)相同; 在ULL刚刚规定,这是一个unsigned long long文字.在任何情况下,作为一个布尔值,它是真的,所以0xFULL ? '\0' : -1求值为'\0',这是一个字符文字,其数值很简单0.

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

while( a[0] >>= a[1] )
Run Code Online (Sandbox Code Playgroud)

运算符>>=是一个复合赋值,它将左操作数右移位右操作数给出的位数,并返回结果.在这种情况下,右操作数a[1]始终具有值1,因此它等效于:

while( a[0] >>= 1 )
Run Code Online (Sandbox Code Playgroud)

或者,等效地:

while( a[0] /= 2 )
Run Code Online (Sandbox Code Playgroud)

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

  • @Kay:它与`10e5`中的`e`相同,除了你必须使用`p`表示十六进制文字,因为`e`是十六进制数字. (76认同)
  • 能否详细说明`0X.1P1`中的'P`. (18认同)
  • @Kay:hex float文字是C99的一部分,但[GCC也接受它们用C++代码](https://gcc.gnu.org/onlinedocs/gcc/Hex-Floats.html).正如迪特里希所说,'p`将尾数和指数分开,就像正常科学浮点符号中的`e`一样; 一个区别是,对于十六进制浮点数,指数部分的基数是2而不是10,所以"0x0.1p1"等于0x0.1 = 1/16乘以2¹= 2.(无论如何,这里没有任何关系;任何非零值都可以在那里同样有效.) (8认同)
  • 很好的减少. (8认同)
  • @chux:显然,[依赖](http://stackoverflow.com/questions/433895/why-are-c-character-literals-ints-instead-of-chars)代码是否编译为C或(因为它最初被标记)C++.但我修改了文字说"character literal"而不是"`char` literal",并添加了一个维基百科链接.谢谢! (6认同)

jua*_*nza 67

它是涉及一些相当模糊代码有向图,分别<::>这对替代令牌[]分别.还有一些使用条件运算符.还有一个位移位运算符,右移位分配>>=.

这是一个更易读的版本:

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

和一个更易读的版本,替换[]他们解决的值中的表达式:

while( a[0] >>= a[1] )
Run Code Online (Sandbox Code Playgroud)

更换a[0]a[1]他们的价值观应该可以很容易找出循环是干什么的,即相当于:

int i = 10;
while( i >>= 1)
Run Code Online (Sandbox Code Playgroud)

这是在每次迭代中简单地执行(整数)除以2,产生序列5, 2, 1.

  • @Jongware 10在第一次迭代中得分.因此循环评估的值是5,2,1和0.所以它只打印出3次. (7认同)

0x4*_*2D2 41

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

a[ 0xFULL?'\0':-1:>>>=a<:!!0X.1P1 ]
Run Code Online (Sandbox Code Playgroud)

我注意到的第一件事是我们正在使用三元运算符?.所以子表达式:

0xFULL ? '\0' : -1
Run Code Online (Sandbox Code Playgroud)

是说"如果0xFULL是非零,则返回'\0',否则-1.0xFULL是带有无符号long-long后缀的十六进制文字 - 意味着它是类型的十六进制文字unsigned long long.但这并不重要,因为它0xF可以适合常规整数.

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

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

a[ 0 :>>>=a<:!!0X.1P1 ]
Run Code Online (Sandbox Code Playgroud)

接下来,:>是一个有向图.它是一个扩展为]:

a[0 ]>>=a<:!!0X.1P1 ]
Run Code Online (Sandbox Code Playgroud)

>>=是签名的右移操作符,我们可以将其从中a移除以使其更清晰.

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

a[0] >>= a[!!0X.1P1 ]
Run Code Online (Sandbox Code Playgroud)

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

a[0] >>= a[true]
-> a[0] >>= a[1]
Run Code Online (Sandbox Code Playgroud)

>>=是签署的右移运营商.它通过将其位向前移动操作员右侧的值来改变其左操作数的值.10二进制是1010.所以这里是步骤:

01010 >> 1 == 00101
00101 >> 1 == 00010
00010 >> 1 == 00001
00001 >> 1 == 00000
Run Code Online (Sandbox Code Playgroud)

>>=返回其操作的结果,因此只要a[0]每次将其位右移1时移位保持非零,循环就会继续.第四个尝试是哪里a[0]0,所以从未进入循环.

结果,?打印三次.

  • `:>`是*有向图*,而不是三角形.它不是由预处理器处理的,它只是被认为是一个等同于`]`的标记. (3认同)
  • @KeithThompson它可以由预处理器处理.预处理器必须知道有向图,因为`#`和`##`有有向图形式; 在早期翻译阶段,没有什么能阻止将有向图转换为非有向图的实现 (2认同)