GCC uses a completely different syntax for inline assembly比MSVC做,所以這是相當多的工作來維持兩種形式。這也不是一個特別好的主意。 There are many problems with inline assembly。人們經常使用它,因爲他們認爲它會讓代碼運行得更快,但通常會產生相反的效果。 Unless you're an expert in both assembly language and the compiler's code-generation strategies, you are far better off letting the compiler's optimizer generate the code。
當你試圖做到這一點時,你必須在這裏小心一點,但是:簽名的右移是用C實現定義的,所以如果你關心可移植性,你需要將值轉換爲等價的無符號類型:
#include <limits.h> // for CHAR_BIT
signed long ROR13(signed long val)
{
return ((unsigned long)val >> 13) |
((unsigned long)val << ((sizeof(val) * CHAR_BIT) - 13));
}
(另請參閱Best practices for circular shift (rotate) operations in C++)。
這將具有相同的語義原密碼:ROR val, 13
。事實上,MSVC會像GCC一樣精確地生成目標代碼。 (Clang,有趣的是,將會做ROL val, 19
,由於旋轉的方式產生相同的結果,ICC 17產生了一個擴展的移位:SHLD val, val, 19
。我不確定爲什麼;也許這比某些Intel處理器上的旋轉要快,或者。也許是英特爾相同的,但對AMD慢)
要在純C實現Div16
,你想:
signed long Div16(signed long a, signed long b)
{
return ((long long)a << 16)/b;
}
在64位架構,可以做原生64位除法,(假設long
仍然是一個32位的類型等在Windows上),這將轉化爲:
movsxd rax, a # sign-extend from 32 to 64, if long wasn't already 64-bit
shl rax, 16
cqo # sign-extend rax into rdx:rax
movsxd rcx, b
idiv rcx # or idiv b if the inputs were already 64-bit
ret
不幸的是,在32位x86,代碼是不是幾乎一樣好。編譯器向內部庫函數發出調用,提供擴展的64位除法,因爲它們不能證明使用單個64b/32b => 32b idiv
instruction不會發生故障。 (這將引發#DE
異常,如果該商數不eax
適合,而不是僅僅截斷)
換句話說,轉化:
int32_t Divide(int64_t a, int32_t b)
{
return (a/b);
}
到:
mov eax, a_low
mov edx, a_high
idiv b # will fault if a/b is outside [-2^32, 2^32-1]
ret
不一個合法的優化 - 編譯器無法發出此代碼。該語言標準說64/32分區被提升爲64/64分區,總是產生64位結果。您稍後將64位結果強制轉換或強制爲32位值與分割操作本身的語義無關。對於a
和b
的某些組合的錯誤將違反假定規則,除非編譯器可以證明a
和b
的這些組合是不可能的。(例如,如果知道b
大於1<<16
,這可能是a = (int32_t)input; a <<= 16;
的合法優化但儘管這會產生與所有輸入的C抽象機器相同的行爲,但gcc和clang 目前並不這樣做優化。)
目前根本沒有覆蓋語言標準規定的規則,迫使編譯器生成所需的對象代碼的好方法。 MSVC沒有爲它提供內在的功能(雖然有Windows API函數,MulDiv
,它不是快速的,並且只是使用內聯彙編實現它自己的實現—和a bug in a certain case,現在由於需要向後兼容性而被強化)。實質上,您只能通過內聯或從外部模塊鏈接到組件來進行組裝。
因此,你陷入了醜陋之中。它看起來像這樣:
signed long Div16(signed long a, signed long b)
{
#ifdef __GNUC__ // A GNU-style compiler (e.g., GCC, Clang, etc.)
signed long quotient;
signed long remainder; // (unused, but necessary to signal clobbering)
__asm__("idivl %[divisor]"
: "=a" (quotient),
"=d" (remainder)
: "0" ((unsigned long)a << 16),
"1" (a >> 16),
[divisor] "rm" (b)
:
);
return quotient;
#elif _MSC_VER // A Microsoft-style compiler (i.e., MSVC)
__asm
{
mov eax, DWORD PTR [a]
mov edx, eax
shl eax, 16
sar edx, 16
idiv DWORD PTR [b]
// leave result in EAX, where it will be returned
}
#else
#error "Unsupported compiler"
#endif
}
這會導致在Microsoft和GNU風格的編譯器上都需要輸出。
嗯,主要是。出於某種原因,當您使用rm
約束條件時,編譯器可以自由選擇將除數作爲內存操作數處理還是將其加載到寄存器中,但與僅使用r
(其中的force它將其加載到一個寄存器中)。這不影響GCC或ICC。如果您關心Clang的輸出質量,那麼您可能只想使用r
,因爲這會在所有編譯器上提供同樣好的目標代碼。
Live Demo on Godbolt Compiler Explorer
(注:GCC使用SAL
記憶在它的輸出,而不是SHL
助記符這些相同指令,區別僅事項右移和所有健全的組裝程序員使用SHL
。我不知道爲什麼海灣合作委員會發出SAL
,但你可以把它精神上轉換成SHL
。)
「asm」指令的不同語法不是你唯一的問題。即使對於彙編程序指令,GCC也使用[不同語法](https://en.wikibooks.org/wiki/X86_Assembly/GAS_Syntax)。 –
'ror' rotate ** right ** so'(val >> 13)| (val <<(32 - 13))' – Jester
[編譯器內部函數](https://en.wikipedia.org/wiki/Intrinsic_function)可能對您有所幫助。例如,這個Visual Studio [x86 Intrinsics List](https://msdn.microsoft.com/en-us/library/hh977023.aspx) –