[轉]Visual C++ Inline Assembly 簡介
如果你想編寫出極高效能的遊戲代碼,那麼使用組合語言無疑將會是你的最佳選擇。不過,眼看著編程技術已經發展到了今天這種格局,你再去直接用組合語言來進行編程也未免太不合時宜了吧。作為一個高效能遊戲程式的編寫者,你應該用的是 Inline Assembly。
什麼是 Inline Assembly
Inline Assembly 就是我們通常所說的線上彙編,即直接在你的 C/C++ 代碼裡加入組合語言代碼。
線上彙編的好處,那些習慣了線上彙編的人都覺得它用起來很方便。
同傳統的彙編方式相比,我們可以完全避免那些煩瑣的彙編和連結步驟,可以直接在彙編代碼中使用 C 變數名和函數名。這樣一來,我們的彙編代碼就能夠很容易地同我們的 C 語言程式緊密而自然地結合在一起了。另外,由於是把彙編語句和 C 或者 C++ 語句混合在一起進行編程,我們還將能實現許多原來光憑 C 或者 C++ 語句無法辦到的事。
線上彙編的用處
通常,線上彙編被用來寫某些特殊函數,例如最佳化代碼中對速度有極高要求的部分,直接存取硬體和裝置驅動程式和為一段調用代碼編寫進入和退出時用到的保護性代碼等等。
線上彙編的限制
一方面,線上彙編是一種專用工具,它不支援宏彙編中的宏和其它一些功能。另一方面,當你想往不同的平台移植程式碼時,這些機器相關的代碼會比較難於處理。
如何在 Visual C++ 環境中使用線上彙編
在 Visual C++ 環境下,使用線上彙編是一件非常容易的事情:你只要用一條“_asm { }”把你的彙編代碼括起來就行了。
下面給大家看一個簡單例子,這個例子是要實現一個叫 Mycode() 的 C 函數。這個函數的功能很簡單,它把兩個輸入參數的值相加,結果存放到另外一個全域變數中。
long result;
void Mycode(long a, long *b)
{
_asm
{
MOV EAX, a // eax = a
MOV EBX, b // ebx = b
ADD EAX, [EBX] // eax = eax + [ebx] = eax + (*b)
MOV result, EAX // result = eax
}
}
這個例子雖然簡單,但它在解釋“_asm { }”的用法的同時,順便示範了對一般變數和指標變數的不同處理方法。
Visual C++ Inline Assembly 的處理細節
根據微軟的不完全資料和自己的長期實踐,我總結出以下幾條處理細節,供大家參考,也期待著大家來補充和修正。
在 32 位應用程式中,因為是處於保護模式,所有定址的位移地址都是 32 位的,此時應該盡量避免修改和使用段寄存器。
全域變數被翻譯成立即地址。
函數內的局部變數被翻譯成堆棧位移地址,形如 [EBP + ????] 的形式,其中 EBP 在函數進入時被設定為 ESP 的值。故使用局部變數的函數中,注意不要破壞 EBP 的內容。
Visual C++ 編譯器不會對位於“_asm { }”中的代碼作任何最佳化和改動。
Visual C++ 編譯器在最佳化時,會自動廢棄穿越“_asm { }”程式碼片段的寄存器,因此不需要在“_asm { }”程式碼片段中添加寄存器保護與復原代碼。
補充說明
匆忙之中,就想到了這麼幾條,如果你有什麼意見、問題和建議,請務必提出來,那樣的話,大家可以一起作進一步的討論,使本文得以完善和升級,從而讓更多看到本文的人受益。
另外,如果你以前有一點組合語言的基礎,但沒學過 INTEL X86 體系的 32 位組合語言,請閱讀我的另一篇文字:『INTEL X86 體系的 32 位組合語言速成』。
VC 裡如果你定義了結構, 比如:
typedef struct {
int x;
int y;
} MYSTRUCT;
那麼在 inline asm 裡就多了兩個常量:
MYSTRUCT.x 是 0
MYSTURCT.y 是 4
它們表示了 x,y 在結構中的位移量。所以你就可以用:
MYSTURCT *a;
mov ebx,a;
mov eax,[ebx]MYSTRUCT.x;
(eax 裡就是 a->x)
上面一行也可以寫成
mov eax,[ebx+MYSTRUCT.x];
另外,VC 中你也可以建立一個函數不幫你做棧處理的,需要你自己來取輸入參數、保護寄存器、甚至自己寫 ret 等等。這可以看做一個“純粹”的 asm 函數了。方法是在函數定義前加入:
__declspec(naked)
就可以了。
VC inline asm 裡可以用 ALIGN n 來對齊代碼。如果需要填入資料,VC 將填入一些不改變任何寄存器和標誌位的指令(不一定是 nop),不過小心 VC 在這方面有 bug,我的首頁(cloudwu.yeah.net)上曾詳細講解。
另外,在 VC 的 inline asm 裡不象我們在 BC 的 inline asm 裡那樣簡潔的用 db 來插入一些非代碼的資料,它要用 _emit n 插入一個位元組。這有時很有用,比如 Pentium 的 cpuid 指令 VC 就不支援,我們可以用:
#define cpuid __asm _emit 0x0F __asm _emit 0xA2
來代替就可以了。