是的,我重複這一點。好吧,主要是。我其實沒有得到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,你也必須重建所有的運行時支持庫。這確實很匆忙。 –