2017-06-22 1 views
7
の間違った出力

TEST.CPP:SSEとのiostream:浮動小数点型

g++ -mno-sse test.cppとcygwinの64ビットでコンパイル
#include <iostream> 
using namespace std; 

int main() 
{ 
    double pi = 3.14; 
    cout << "pi:"<< pi << endl; 
} 

、出力される。

PI:0

ただし、g++ test.cppでコンパイルすると正しく動作します。

私はGCCバージョン5.4.0を持っています。

+2

あなたの左手は右手が何であるかを知らないすべてのランタイムサポートライブラリも再構築する必要があります。これは大きな急いで無意味になります。 –

答えて

9

はい、私はこれをreproします。まあ、ほとんど。私は実際には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)をメモリからロードします(バイナリの定数として格納されます)。スタック。次に、その値をスタックからポップし、メモリに格納します(プログラムスタック)。これは、最適化されていないコードで実行されているだけでも忙しい作業です。ほとんど無視できます。ここでの浮動小数点値は、浮動小数点値がメモリにrbp-8(ベースポインタから8バイトのオフセット)に格納されていることです。

次の命令ブロックは完全に無視することができます。彼らは単に文字列 "pi:"を出力します。

命令の第3ブロックは、であり、浮動小数点値を出力すると仮定すると、となります。まず、8バイトのスペースがスタックに割り当てられます。次に、以前にメモリに格納していた浮動小数点値がスタックにプッシュされます。

これまでのところ、とても良いです。これは、が通常の場合、浮動小数点パラメータを関数に渡します。つまり、x87命令を使用していた32ビットABIに続いて、32ビットのビルドに渡します。64ビットのビルドでは、64ビットのABIに続いて、浮動小数点パラメータがXMMレジスタに渡され、これがoperator<<(double)関数がそのパラメータを受け取る予定の場所です。 でもSSEコードは生成できないので、XMMレジスタを使用することはできません。その手は結ばれている。あなたの特定のオプションがABIのを壊すので、それはABIに続くライブラリ関数を適切に呼び出すことができません。

ここからはすべて下り坂です。コンパイラは、raxレジスタの内容をrdiレジスタにコピーし、次にoperator<<(double)関数を呼び出します。この関数は、XMM0レジスタに渡された浮動小数点値をstdoutに書き込もうとしますが、そのレジスタにはガベージが含まれています(実際には内容は正式には未定義です)、このガベージはstdout 、あなたが見ることが期待される浮動小数点値の代わりに。

問題を理解したので、解決策は何ですか?

  • あなたが-m32フラグを使用してコンパイルする32ビットのバイナリを強制する、SSE命令を使用しない場合。これは安全に-mno-sseと組み合わされています。
  • 64ビットバイナリが必要な場合は、-mno-sseフラグを渡さないでください。これは、64ビットABIに違反しているためです。これはSSE2のサポートが最小限であることを前提としています。

(私はここでそれを無視していますが、-m64フラグと一緒に-mno-sseフラグを渡すことが技術的に合理的である。Linuxカーネルのコードをコンパイルするために使用されているため、実際に、これは明示的にGCCによってサポートされていますこの場合、XMMレジスタの状態は呼び出し間で保持されません。これは、カーネルコードが浮動小数点演算を実行しないためにのみ機能します。浮動小数点演算と関係がある)。

+0

このようなことをやろうとすると、警告またはエラーが発生するはずです。コンパイラは、私が適切なABI x64に従うライブラリとリンクしていることを認識しています。右? – olegkhr

+2

最後の段落を参照してください。これは技術的には64ビットビルドの有効なオプションであり、場合によっては使用されるため、エラーにはなりません。これは、あなたが浮動小数点演算を行っていないことを前提としているため、標準ライブラリから浮動小数点関数を呼び出すことはありません。私は、コンパイラが理論的に*あなたがこのような関数呼び出しをして診断を出すのを検出できたと思うが、確かに必須ではない。かなり珍しいエラーのためにかなりの量の開発者の努力があるだろうと思う。 @olegkhr –

関連する問題