2017-06-22 34 views
7

TEST.CPP:SSE和iostream的:錯誤輸出,用於浮點類型

#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。

+2

你的左手不知道右手是doi ng,你也必須重建所有的運行時支持庫。這確實很匆忙。 –

回答

9

是的,我重複這一點。好吧,主要是。我其實沒有得到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 ABIx86-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指令作爲高級優化的一部分,與浮點操作有關。)

+0

當我嘗試做這樣的事情時,我應該會收到警告或錯誤。編譯器意識到我正在連接適合ABI x64的庫。對? – olegkhr

+2

查看最後一段。這在技術上是64位構建的有效選項,並且在某些情況下使用,因此它不會是錯誤。它只是假定你沒有做任何浮點運算,因此不會調用標準庫中的任何浮點函數。我想編譯器理論上可能會檢測到你正在做這樣一個函數調用併發出一個診斷信息,但它肯定不是必需的,而且我想這對於一個非常罕見的錯誤來說是一筆很大的開發工作。 @olegkhr –