为什么这段代码给输出c++吸?它背后的概念是什么?

#include <stdio.h>

double m[] = {7709179928849219.0, 771};

int main() {
    m[1]--?m[0]*=2,main():printf((char*)m);    
}

在这里测试。


当前回答

首先,我们应该回忆一下,双精度数以二进制格式存储在内存中,如下所示:

(i)符号为1位

(ii)指数为11位

(iii)幅值为52位

位序从(i)递减到(iii)。

首先将十进制小数转换为等效的小数二进制数,然后用二进制表示为数量级形式。

所以数字7709179928849219.0就变成了

(11011011000110111010101010011001010110010101101000011)base 2


=1.1011011000110111010101010011001010110010101101000011 * 2^52

现在在考虑大小比特1。由于所有数量级方法都从1开始,因此忽略。

所以大小部分变成:

1011011000110111010101010011001010110010101101000011 

现在2的幂是52,我们需要将偏置数加到2^(指数-1位)-1 即2^(11 -1)-1 =1023,所以指数变成52 + 1023 = 1075

现在我们的代码将这个数字乘以2,771,这使得指数增加了771

所以我们的指数是(1075+771)= 1846它的二进制等价是(11100110110)

现在数字是正的,所以符号位是0。

那么我们的修正数就变成:

符号位+指数+幅度(位的简单连接)

0111001101101011011000110111010101010011001010110010101101000011 

由于m被转换为char指针,我们将把LSD中的位模式分成8个块

01110011 01101011 01100011 01110101 01010011 00101011 00101011 01000011 

(其十六进制等效为:)

 0x73 0x6B 0x63 0x75 0x53 0x2B 0x2B 0x43 

如图所示的字符映射为:

s   k   c   u      S      +   +   C 

现在,一旦这个已经完成,m[1]是0,这意味着一个NULL字符

现在假设你在一个小端序机器上运行这个程序(低阶位存储在较低的地址中),那么指针m指向最低的地址位,然后继续占用8字节的字节(作为类型转换为char*),当在最后一个块中遇到00000000时printf()停止…

但是这段代码是不可移植的。

其他回答

数字7709179928849219.0具有以下64位双精度二进制表示形式:

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()。

免责声明:这个答案被发布到问题的原始形式中,它只提到了c++,并包含了一个c++头。问题转换为纯C是由社区完成的,没有来自最初提问者的输入。


从形式上讲,不可能对这个程序进行推理,因为它是病态的(即它不是合法的c++)。它违反了c++ 11[basic.start.main]p3:

函数main不能在程序中使用。

This aside, it relies on the fact that on a typical consumer computer, a double is 8 bytes long, and uses a certain well-known internal representation. The initial values of the array are computed so that when the "algorithm" is performed, the final value of the first double will be such that the internal representation (8 bytes) will be the ASCII codes of the 8 characters C++Sucks. The second element in the array is then 0.0, whose first byte is 0 in the internal representation, making this a valid C-style string. This is then sent to output using printf().

在HW上运行这个,上面的一些不支持将导致垃圾文本(甚至可能是一个越界的访问)。

更易读的版本:

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]只包含0,因此它有一个用于c++ Sucks字符串的空结束符。所有这些都假设m[0]存储在8个字节中,因此每个char占用1个字节。

如果没有递归和非法的main()调用,它将看起来像这样:

double m[] = {7709179928849219.0, 0};
for (int i = 0; i < 771; i++)
{
    m[0] *= 2;
}
printf((char*) m);

代码可以像这样重写:

void f()
{
    if (m[1]-- != 0)
    {
        m[0] *= 2;
        f();
    } else {
          printf((char*)m);
    }
}

它所做的是在双数组m中生成一组字节,恰好对应于字符' c++ Sucks'后面跟着一个空结束符。他们通过选择一个双精度值来混淆代码,当该双精度值乘以771倍时,在标准表示中会产生由数组的第二个成员提供的空结束符的字节集。

注意,这段代码不能在不同的端序表示下工作。另外,严格来说不允许调用main()。

其他人已经很彻底地解释了这个问题,我想补充一点,根据标准,这是未定义的行为。

c++ 11 3.6.1/3主要功能

函数main不能在程序中使用。main的链接(3.5)是由实现定义的。将main定义为deleted或将main声明为inline、static或constexpr的程序是格式错误的。名称main没有其他保留。[示例:成员函数、类和枚举可以被称为main,其他命名空间中的实体也可以。]-end示例]