TEST.CPP:
#include <iostream>
using namespace std;
int main()
{
double pi = 3.14;
cout << "pi:"<< pi << endl;
}
在使用g++ -mno-sse test.cpp
的cygwin 64位编译时,输出为:
PI:0
但是,如果使用g++ test.cpp
进行编译,它可以正常工作。
我有GCC版本5.4.0。
答案 0 :(得分:9)
是的,我重复这个。好吧,主要是。我实际上没有输出0,但其他一些垃圾输出。所以我可以重现无效的行为,我已经确定了原因。
您可以使用-m64 -mno-sse
标记here on Goldbolt's Compiler Explorer查看GCC 5.4.0生成的代码。特别是,这些是我们关心的指示:
// 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 CPU都支持SSE和SSE2。这个假设已经进入the ABI: x86-64上的所有浮点运算都是使用SSE2指令完成的,浮点值是在XMM寄存器中传递的。因此,执行浮点运算但禁止编译器使用SSE / SSE2指令会使代码生成器处于不可能的位置并导致不可避免的失败。
它到底是怎么失败的?让我们来看看上面的代码。它没有经过优化(因为你没有通过优化标记,它默认为-O0
),这使得它有点难以阅读,但请耐心等待。
在第一个块中,它使用x87 FPU指令从存储器加载双精度浮点值(3.14)(它作为二进制中的常量存储)到x87 FPU堆栈顶部的寄存器中。然后,它将该值从堆栈中弹出并将其存储到内存中(程序堆栈)。这完全是在未经优化的代码中完成的繁忙工作,你几乎可以忽略它。这里的结果是你的浮点值存储在rbp-8
的内存中(与基指针相差8个字节)。
可以完全忽略下一个指令块。他们只输出字符串&#34; pi:&#34;。
第三个指令块假设输出浮点值。首先,在堆栈上分配8个字节的空间。然后,我们先前存储到内存中的浮点值被压入堆栈。
到目前为止,这么好。这就是通常将一个浮点参数传递给一个函数的方式 - 也就是说,在32位构建中,遵循32位ABI,您使用的是x87指令。在64位构建中,遵循64位ABI,浮点参数应该在XMM寄存器中传递,这是operator<<(double)
函数期望接收其参数的位置。 但是,你告诉编译器它不能生成SSE代码,所以它不能使用XMM寄存器。它的双手并列。它无法正确调用ABI之后的库函数,因为您的特定选项打破了 ABI。
从这里开始走下坡路。编译器将rax
寄存器的内容复制到rdi
寄存器中,然后调用operator<<(double)
函数。此函数尝试将XMM0
寄存器中传递的浮点值写入stdout,但该寄存器包含垃圾(在您的情况下,它似乎包含0,但其实际内容是正式未定义的),所以这个垃圾被写入stdout,而不是您期望看到的浮点值。
既然我们了解了这个问题,那么解决方案是什么?
-m32
标记编译32位二进制文件。这可以安全地与-mno-sse
结合使用-mno-sse
标志,因为这违反了64位ABI,它假设SSE2支持最小。 强> (虽然我在这里忽略它,但在技术上合理地传递-mno-sse
标志和-m64
标志是合理的。事实上,这是明确支持的由GCC用于编译Linux内核代码,其中XMM寄存器状态在调用之间不存在。这仅仅是因为内核代码不执行浮点运算。-mno-sse
开关仅用于防止编译器将SSE指令用作与浮点运算无关的高级优化的一部分。)