是的,我重复这一点。好吧,主要是。我其实没有得到0的输出,但有一些其他的垃圾输出。所以我可以重现这种无效的行为,并且我确定了原因。
您可以看到GCC 5.4.0生成的代码与-m64 -mno-sse
标志here on Goldbolt's Compiler Explorer。特别是,这些是我们关心的指令:
// double pi = 3.14;
fld QWORD PTR .LC0[rip]
fstp QWORD PTR [rbp-8]
// std::cout << "pi:";
mov esi, OFFSET FLAT:.LC1
mov edi, OFFSET FLAT:std::cout
call std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
// std::cout << pi;
sub rsp, 8
push QWORD PTR [rbp-8]
mov rdi, rax
call std::basic_ostream<char, std::char_traits<char> >::operator<<(double)
add rsp, 16
这里发生了什么?那么,首先,我们需要了解-mno-sse
标志的含义。这可以防止编译器生成任何使用SSE指令的代码(以及任何后续的指令集扩展)。因此,这意味着所有浮点操作都必须使用传统的x87 FPU完成。这很好,在32位版本上得到很好的支持,但是在64位版本上它是无意义的。 AMD64规范要求至少支持SSE2,因此可以认为,所有支持64位的x86处理器的都支持SSE和SSE2。这个假设已将它变为the ABI:x86-64上的所有浮点操作都是使用SSE2指令完成的,并且浮点值在XMM寄存器中传递。因此,做浮点操作但禁止编译器使用SSE/SSE2指令会使代码生成器处于不可能的位置,并导致不可避免的故障。
它究竟如何失败?我们来看看上面的代码。它没有优化(因为你没有通过一个优化标志,它默认为-O0
),这使它有点难以阅读,但忍受着我。
在第一个块中,它使用x87 FPU指令将来自内存的双精度浮点值(3.14)(它作为二进制常量存储)放入x87 FPU顶部的寄存器中叠加。然后,它从堆栈中弹出该值并将其存储到内存中(程序堆栈)。这完全只是忙于在未优化的代码中完成的工作,并且几乎可以忽略它。这里的结果是你的浮点值被存储在内存中rbp-8
(与基址指针相距8个字节)。
下一块指令可以完全忽略。他们只输出字符串“pi:”。
第三块指令是假设输出浮点值。首先,在堆栈上分配8个字节的空间。然后,我们先前存储到内存的浮点值被压入堆栈。
到目前为止,这么好。这就是你的通常会传递一个浮点参数给一个函数,也就是说,在一个32位版本中,在你使用x87指令的32位ABI之后。在64位版本中,在64位ABI之后,浮点参数应该在XMM寄存器中传递,这是函数期望接收其参数的地方。 但是,你告诉编译器它不能生成SSE代码,所以它不能使用XMM寄存器。它的手绑在一起。它不能正确调用ABI之后的库函数,因为您的特定选项会打破 ABI。
从这里开始下坡。编译器将rax
寄存器的内容复制到rdi
寄存器中,然后调用operator<<(double)
函数。该函数试图将在XMM0
寄存器中传递的浮点值写入stdout,但该寄存器包含垃圾(在你的情况下,它似乎包含0,但它的实际内容在形式上是未定义的),所以这个垃圾被写入stdout而不是您期望看到的浮点值。
现在我们了解问题了,解决方案是什么?
- 如果你不想使用SSE指令,强制32位二进制使用
-m32
标志进行编译。这与-mno-sse
安全结合。
- 如果您需要一个64位二进制文件,那么不要传递
-mno-sse
标志,因为这违反了64位ABI,它将SSE2支持作为最低限度。
(虽然我在这里忽略它,它是技术上合理传递-mno-sse
标志和-m64
标志一起。事实上,这是明确GCC,因为它是用来编译Linux内核代码支持,其中XMM寄存器的状态在调用之间并没有持续,这仅仅是因为内核代码不执行浮点操作,-mno-sse
开关仅用于防止编译器使用SSE指令作为高级优化的一部分,与浮点操作有关。)
你的左手不知道右手是doi ng,你也必须重建所有的运行时支持库。这确实很匆忙。 –