SSE和iostream:浮点类型的错误输出
SSE and iostream: wrong output for floating point types
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的输出,而是其他一些垃圾输出。这样我就可以重现无效的行为,并且已经指出了原因。
您可以看到GCC 5.4.0在Goldbolt的编译器资源管理器上使用-m64 -mno-sse
标志生成的代码。特别是,这些是我们关心的说明:
// 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指令(以及任何以后的说明集扩展程序(的任何代码。因此,这意味着必须使用Legacy X87 FPU进行所有浮点操作。这效果很好,并且在32位版本上得到了很好的支持,但是在64位版本上是荒谬的。AMD64规范需要SSE2支持作为最小值,因此可以假定所有所有 64位能力的X86 CPU都将支持SSE和SSE2。此假设已进入ABI: X86-64上的所有浮点操作都是使用SSE2指令完成的,并且在XMM寄存器中传递了浮点值。因此,执行浮点操作,但禁止编译器使用SSE/SSE2指令将代码生成器处于不可能的位置,并导致不可避免的故障。
它到底是如何失败的?让我们浏览上面的代码。它是不优化的(由于您没有传递优化标志,因此默认为-O0
(,这使得很难阅读,但请与我同意。
在第一个块中,它使用x87 FPU指令从内存(将其作为二进制中的常数存储在X87 FPU堆栈顶部的寄存器中(加载双精度浮点数(3.14((3.14(。然后,它弹出该值,并将其存储在内存中( program 堆栈(。这完全只是在不优化的代码中完成的忙碌工作,您几乎可以忽略它。这里的结果是您的浮点值存储在rbp-8
的内存中(从基本指针中偏移了8个字节(。
下一个指令可以完全忽略。他们只是输出字符串" pi:"。
指令的第三个块是假定的以输出浮点值。首先,在堆栈上分配了8个字节。然后,我们以前存储在存储器上的浮点值被推到堆栈上。
到目前为止,一切都很好。这就是您通常通常会将浮点数参数传递给函数的方式,也就是说,在32位构建中,遵循32位ABI,您使用x87指令。在64位构建中,遵循64位ABI,应该在XMM寄存器中传递浮点参数,这是operator<<(double)
函数期望接收其参数的地方。但是,您告诉编译器它无法生成SSE代码,因此无法使用XMM寄存器。它的手被绑住。它无法正确调用遵循ABI的库函数,因为您的特定选项 break abi。
这一切都从这里下坡。编译器将rax
寄存器的内容复制到rdi
寄存器中,然后调用operator<<(double)
函数。此函数试图在XMM0
寄存器中写入传递的浮点值,但该寄存器包含垃圾(在您的情况下,它似乎包含0,但其实际内容正式未定义(,因此该垃圾写入STDOUT,而不是您期望看到的浮点值。
现在我们了解问题了,解决方案是什么?
- 如果您不想使用SSE指令,请使用
-m32
标志强制32位二进制文件。这将与-mno-sse
安全联合使用。
。 - 如果您需要64位二进制文件,则不要传递
-mno-sse
标志,因为这是对64位ABI的违规,它假设SSE2支持最低。
(尽管我在这里忽略了它,但在技术上是与-mno-sse
标志一起通过-m64
标志合理的。的确,GCC明确支持了这一点,因为它用于编译Linux内核代码,XMM寄存器的状态在呼叫之间不持续。此作用仅是因为内核代码不执行浮点操作。-mno-sse
交换机仅用于防止编译器使用SSE指令作为具有已有高级优化的一部分与浮点操作无关。(
- 类没有命名C++代码中的类型错误
- 不完整类型错误(E0409、E0070、E0515)
- 使用具有结构不完整类型错误的模板
- 不命名构造函数和析构函数上的类型错误
- 返回派生类型时出现协变类型错误
- 为什么此构造函数没有给出不完整的类型错误?
- 定义模板化结构的特征时出现不完整的类型错误
- 使用重载构造函数时出现不完整的类型错误
- 为什么我得到表达式必须有类类型错误?
- 输出 [left]=input[i] 行中的 c++ 代码中存在无效的类型错误
- 枚举成员不是类型错误
- 如何修复"ctypes"。参数错误:参数 2:<键入"异常.类型错误">:RaspberryPi 中的错误类型"错误
- 外部 "C" Visual Studio 2015 中的显式类型错误,DLL 测试代码
- 非默认析构函数会导致不完整的类型错误
- 为什么按引用传入会导致绑定引用类型错误
- Objective-C C++ 包装器 类型错误的不完整定义
- 输入类型错误
- 当构造函数的参数类型错误时引发异常
- 命名空间中的'bad_cast' 'std' 未命名类型错误
- C UNWORKOWN类型错误