【问题标题】:What is the >>>= operator in C?什么是 C 中的 >>>= 运算符?
【发布时间】:2014-10-19 03:25:22
【问题描述】:

一位同事给出的一个谜题,我无法弄清楚这个 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;
}

【问题讨论】:

  • 其中一些是digraphs
  • @Kay,在这种情况下不: :> = ] 然后 a[...] >> = a[...]
  • @Marc 我不认为它可以是 ">> >=" 因为那不会编译,但是上面的代码实际上可以编译。
  • 0x.1P1 是一个带指数的十六进制文字。 0x.1 是数字部分,或这里的 1/16。 “P”后面的数字是该数字乘以2的幂。所以0x.1p1 实际上是 1/16 * 2 或 1/8。如果你想知道0xFULL 那只是0xFULLunsigned long long 的后缀
  • C 语法——为专家和琐事爱好者提供无穷无尽的材料,但最终并不是那么重要。

标签: c obfuscation literals bit-shift digraphs


【解决方案1】:

行:

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

包含digraphs:&gt;&lt;:,分别转换为][,所以它相当于:

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

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

同时,0X.1P1hexadecimal floating point literal 等于 2/16 = 0.125。在任何情况下,非零,它也是一个布尔值,所以用!! 否定它两次​​会产生1。因此,整个事情简化为:

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

运算符&gt;&gt;= 是一个compound assignment,它将其左操作数右移右操作数给出的位数,并返回结果。在这种情况下,右操作数a[1] 的值总是1,所以它等价于:

while( a[0] >>= 1 )

或者,等效地:

while( a[0] /= 2 )

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

【讨论】:

  • 能否详细说明0X.1P1中的P
  • @Kay: 和10e5 中的e 一样,除了你必须使用p 来表示十六进制文字,因为e 是一个十六进制数字。
  • @Kay:十六进制浮点文字是 C99 的一部分,但 GCC also accepts them in C++ code。正如 Dietrich 所指出的,p 将尾数和指数分开,就像普通科学浮点表示法中的 e 一样;一个区别是,对于十六进制浮点数,指数部分的底数是 2 而不是 10,所以 0x0.1p1 等于 0x0.1 = 1/16 乘以 2¹ = 2。(无论如何,这里都不重要;任何非零值在那里同样有效。)
  • 次要:某些 '\0'int 文字。试试printf("%zu %zu\n", sizeof ('\0'), sizeof (char));
  • @chux:显然,depends 关于代码是编译为 C 还是(最初标记为)C++。但我将文本固定为“字符文字”而不是“char 文字”,并添加了一个维基百科链接。谢谢!
【解决方案2】:

这是一些涉及digraphs 的相当晦涩的代码,即&lt;::&gt;,它们分别是[] 的替代标记。 conditional operator 也有一些用途。还有一个bit shifting operator,右移赋值&gt;&gt;=

这是一个更易读的版本:

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

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

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

a[0]a[1] 替换为它们的值应该可以很容易地弄清楚循环在做什么,即相当于:

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

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

【讨论】:

  • 我没有运行它 - 但是,这不会产生 ????,而不是像 OP 那样产生 ???? (呵呵。)codepad.org/nDkxGUNi确实产生???
  • @Jongware 10 在第一次迭代中被划分。所以循环评估的值是 5、2、1 和 0。所以它只打印 3 次。
【解决方案3】:

让我们从左到右遍历表达式:

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

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

0xFULL ? '\0' : -1

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

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

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

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

接下来,:&gt;digraph。它是一个扩展为]的构造:

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

&gt;&gt;= 是带符号的右移运算符,我们可以将其与a 隔开以使其更清晰。

此外,&lt;: 是一个扩展为[ 的有向图:

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

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

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

&gt;&gt;= 是带符号的右移运算符。它通过将其位向前移动运算符右侧的值来更改其左侧操作数的值。二进制中的101010。下面是步骤:

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

&gt;&gt;= 返回其操作的结果,因此只要 a[0] 在每次其位右移 1 时保持非零,循环就会继续。第四次尝试是a[0] 变为0,因此永远不会进入循环。

因此,? 被打印了 3 次。

【讨论】:

  • :&gt; 是一个二合字母,而不是三合字母。它不由预处理器处理,它只是被识别为等同于] 的令牌。
  • @KeithThompson 谢谢
  • 三元运算符 (?:) 的类型是第二项和第三项的通用类型。第一项始终是条件式,类型为bool。由于第二项和第三项的类型均为int,因此三元运算的结果将是int,而不是unsigned long long
  • @KeithThompson 它可以由预处理器处理。预处理器必须知道有向图,因为### 有有向图形式;在早期翻译阶段,没有什么能阻止实现将有向图转换为非有向图
  • @MattMcNabb 我已经很长时间没有知道这一点了,但是 IIRC 由于其他要求,有向图必须保持其有向图形式,直到将 pp-tokens 转换为令牌(就在翻译阶段 7 开始时)。
猜你喜欢
  • 2010-12-11
  • 2010-12-19
  • 2017-08-07
相关资源
最近更新 更多