为什么这段代码会输出 C++Sucks
?背后的概念是什么?
#include <stdio.h>
double m[] = {7709179928849219.0, 771};
int main() {
m[1]--?m[0]*=2,main():printf((char*)m);
}
在这里进行测试。
为什么这段代码会输出 C++Sucks
?背后的概念是什么?
#include <stdio.h>
double m[] = {7709179928849219.0, 771};
int main() {
m[1]--?m[0]*=2,main():printf((char*)m);
}
在这里进行测试。
数字7709179928849219.0
在64位的double
浮点数中的二进制表示如下:
1101101011000111101110100010001111011111101100111101000011
01000011 00111011 01100011 01110101 01010011 00101011 00101011 01000011
+^^^^^^^ ^^^^---- -------- -------- -------- -------- -------- --------
+
表示符号的位置;^
表示指数的位置;-
表示幂次(即不包括指数的值)的位置。
由于该表示法使用二进制指数和幂次,将数字加倍会使指数增加1。您的程序精确地执行了这个过程771次,因此起始时为1075(十进制表示为10000110011
)的指数在最后变成了1075 + 771 = 1846;1846的二进制表示为11100110110
。结果模式看起来像这样:
01110011 01101011 01100011 01110101 01010011 00101011 00101011 01000011
-------- -------- -------- -------- -------- -------- -------- --------
0x73 's' 0x6B 'k' 0x63 'c' 0x75 'u' 0x53 'S' 0x2B '+' 0x2B '+' 0x43 'C'
这个模式对应于你看到的字符串,只是反过来。同时,数组的第二个元素变为零,提供了空终止符,使得字符串适合传递给printf()
函数。
double m[2] = {7709179928849219.0, 771};
// m[0] = 7709179928849219.0;
// m[1] = 771;
int main()
{
if (m[1]-- != 0)
{
m[0] *= 2;
main();
}
else
{
printf((char*) m);
}
}
它递归调用 main()
函数 771 次。
一开始,m[0] = 7709179928849219.0
,它代表的是 C++Suc;C
。在每次调用中,m[0]
被加倍,以“修复”最后两个字母。在最后一次调用中,m[0]
包含了 C++Sucks
的 ASCII 字符表示,而 m[1]
仅包含零,因此它为 C++Sucks
字符串设置了一个空终止符。所有这些都建立在假设m[0]
存储在8字节上,因此每个字符占用1个字节。
如果没有递归和非法的main()
调用,它将如下所示:
double m[] = {7709179928849219.0, 0};
for (int i = 0; i < 771; i++)
{
m[0] *= 2;
}
printf((char*) m);
免责声明:本答案发布于原问题的形式,其中只提到了C++并包含了一个C++头文件。将问题转换为纯C是由社区完成的,没有原始提问者的输入。
严格来说,这个程序无法进行推理,因为它不合法(即不是合法的C++)。它违反了C++11[basic.start.main]p3中的规定:
函数main不得在程序内使用。
除此之外,它依赖于一个事实,即在典型的消费电脑上,double
长度为8字节,并使用某种众所周知的内部表示。数组的初始值是计算出来的,以便在执行“算法”时,第一个double
的最终值将是内部表示(8字节)的8个字符C++Sucks
的ASCII码。然后,数组中的第二个元素是0.0
,其第一个字节是内部表示中的0
,使其成为有效的C风格字符串。然后使用printf()
将其发送到输出。
在一些不满足上述条件的硬件上运行此程序将导致垃圾文本(或甚至越界访问)。
basic.start.main
3.6.1/3。 - sharptoothmain()
的调用,或者用API调用替换它来格式化硬盘,或者其他任何操作。 - Angew is no longer proud of SO也许理解这段代码最简单的方法是逆向思考。我们从要打印的字符串开始 - 为了平衡,我们将使用"C++Rocks"。关键点:就像原始代码一样,它恰好有八个字符。因为我们打算(大致)按照原来的方式,以相反的顺序打印出来,所以我们会先将其倒序排列。首先,我们将把它视为一个double
位模式,并打印出结果:
#include <stdio.h>
char string[] = "skcoR++C";
int main(){
printf("%f\n", *(double*)string);
}
这将产生3823728713643449.5
。因此,我们希望以一种不明显但易于反转的方式进行操作。我将选择乘以256(半随意),这将给我们978874550692723072
。现在,我们只需要编写一些混淆的代码来除以256,然后倒序打印出该字节:
#include <stdio.h>
double x [] = { 978874550692723072, 8 };
char *y = (char *)x;
int main(int argc, char **argv){
if (x[1]) {
x[0] /= 2;
main(--x[1], (char **)++y);
}
putchar(*--y);
}
现在我们有很多转型,传递给(递归)main
的参数完全被忽略(但是为了获取增量和减量而进行估值是非常关键的),当然还有那个看起来完全任意的数字来掩盖我们实际上正在做的事情非常简单。
当然,由于整个重点是混淆,如果我们愿意,我们还可以采取更多步骤。例如,我们可以利用短路求值将我们的if
语句转换为单个表达式,这样main
函数的主体如下:
x[1] && (x[0] /= 2, main(--x[1], (char **)++y));
putchar(*--y);
对于那些不习惯混淆代码(和/或代码高尔夫)的人来说,这看起来确实很奇怪 - 计算并丢弃一些无意义浮点数与 main
的返回值的逻辑 and
,而 main
甚至没有返回值。更糟糕的是,如果没有意识到(并思考)短路求值的工作原理,它甚至可能不会立即清楚地知道它如何避免无限递归。
我们下一步可能要做的是将打印每个字符与查找该字符分开。我们可以通过将正确的字符生成为 main
的返回值,并打印出 main
返回的内容来轻松实现:
x[1] && (x[0] /= 2, putchar(main(--x[1], (char **)++y)));
return *--y;
至少对我而言,那看起来已经够混淆了,所以我就不多说了。
这只是构建一个双重数组(16字节),如果将其解释为char数组,则构建的是字符串“C++Sucks”的ASCII码。
然而,该代码在每个系统上都不起作用,它依赖于以下一些未定义的事实:
C++Suc;C
,因此整个乘法仅适用于最后两个字母。double m[] = {7709179928849219.0, 0};
printf("%s\n", (char *)m);
其他人已经相当详细地解释了这个问题,我想补充一点,根据标准,这是未定义行为。
C++11 3.6.1/3 主函数
函数main不应在程序中使用。 main的连接(3.5)是实现定义的。 将main定义为已删除的程序或声明main为内联,静态或constexpr是不良形式的。 名称main没有其他保留意义。 [示例:可以调用成员函数、类和枚举,也可以调用其他命名空间中的实体。—end example]
void f()
{
if (m[1]-- != 0)
{
m[0] *= 2;
f();
} else {
printf((char*)m);
}
}
首先,我们需要回忆一下,双精度浮点数以二进制格式存储在内存中,具体如下:
(i) 1位用于表示符号
(ii) 11位用于表示指数
(iii) 52位用于表示幅值
这些位的顺序从(i)到(iii)递减。
首先将十进制小数转换为等效的二进制小数,然后将其表示为二进制幂级数形式。
因此,数字7709179928849219.0变成了
(11011011000110111010101010011001010110010101101000011)base 2
=1.1011011000110111010101010011001010110010101101000011 * 2^52
1011011000110111010101010011001010110010101101000011
0111001101101011011000110111010101010011001010110010101101000011
由于m被转换为字符指针,因此我们应该从LSD将位模式分割成8个块。
01110011 01101011 01100011 01110101 01010011 00101011 00101011 01000011
(其十六进制等价值为:)
0x73 0x6B 0x63 0x75 0x53 0x2B 0x2B 0x43
Which from the character map as shown is :
s k c u S + + C
skcuS++C
。 - Adam Rosenfield