为什么这段代码给输出c++吸?它背后的概念是什么?
#include <stdio.h>
double m[] = {7709179928849219.0, 771};
int main() {
m[1]--?m[0]*=2,main():printf((char*)m);
}
在这里测试。
为什么这段代码给输出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()停止…
但是这段代码是不可移植的。
其他回答
下面的代码打印c++的Suc;C,所以整个乘法只针对最后两个字母
double m[] = {7709179928849219.0, 0};
printf("%s\n", (char *)m);
更易读的版本:
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);
首先,我们应该回忆一下,双精度数以二进制格式存储在内存中,如下所示:
(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()停止…
但是这段代码是不可移植的。
其他人已经很彻底地解释了这个问题,我想补充一点,根据标准,这是未定义的行为。
c++ 11 3.6.1/3主要功能
函数main不能在程序中使用。main的链接(3.5)是由实现定义的。将main定义为deleted或将main声明为inline、static或constexpr的程序是格式错误的。名称main没有其他保留。[示例:成员函数、类和枚举可以被称为main,其他命名空间中的实体也可以。]-end示例]
代码可以像这样重写:
void f()
{
if (m[1]-- != 0)
{
m[0] *= 2;
f();
} else {
printf((char*)m);
}
}
它所做的是在双数组m中生成一组字节,恰好对应于字符' c++ Sucks'后面跟着一个空结束符。他们通过选择一个双精度值来混淆代码,当该双精度值乘以771倍时,在标准表示中会产生由数组的第二个成员提供的空结束符的字节集。
注意,这段代码不能在不同的端序表示下工作。另外,严格来说不允许调用main()。