在Visual C++中使用内聯彙編
内聯彙編的優缺點
因為在Visual C++中使用内聯彙編不需要額外的編譯器和聯接器,且可以處理Visual C++中不能處理的一些事情,而且可以使用在C/C++中的變量,是以非常友善。内聯彙編主要用于如下場合:
1.使用彙編語言寫函數;
2.對速度要求非常高的代碼;
3.裝置驅動程式中直接通路硬體;
4. "Naked " Call的初始化和結束代碼。
//(. "Naked ",了解了意思,但是不知道怎麼翻譯^_^,大概就是不需要C/C++的編譯器(自作聰明)生成的函數初始化和收尾代碼,請參看MSDN的 "Naked Functions "的說明)
内聯彙編代碼不易于移植,如果你的程式打算在不同類型的機器(比如x86和Alpha)上運作,應當盡量避免使用内聯彙編。這時候你可以使用MASM,因為MASM支援更友善的的宏指令和資料訓示符。
VC内聯彙編
一 内聯彙編關鍵字
在Visual C++使用内聯彙編用到的是__asm關鍵字,這個關鍵字有兩種使用方法:
1.簡單__asm塊
__asm
{
MOV AL, 2
MOV DX, 0XD007
OUT AL, DX
}
2.在每條彙編指令之前加__asm關鍵
__asm MOV AL, 2
__asm MOV DX, 0xD007
__asm OUT AL, DX
因為__asm關鍵字是語句分隔符,是以你可以把彙編指令放在同一行:
__asm MOV AL, 2 __asm MOV DX, 0XD007 __asm OUT AL, DX
顯然,第一種方法和C/C++的風格很一緻,并且有很多其它優點,是以推薦使用第一種方法。
不象在C/C++中的"{}",__asm塊的"{}"不會影響C/C++變量的作用範圍。同時,__asm塊可以嵌套,嵌套也不會影響變量的作用範圍。
二 在__asm塊中使用彙編語言
1.内聯彙編指令集
内聯彙編完全支援的Intel 486指令集,允許使用MMX指令。不支援的指令可以使用_EMIT僞指令定義(_EMIT僞指令說明見下文)。
2.MASM表達式
内聯彙編可以使用MASM中的表達式。比如: MOV EAX, 1。
3.資料訓示符和操作符
雖然__asm塊中允許使用C/C++的資料類型和對象,但它不能用MASM訓示符和操作符定義資料對象。這裡特别指出,__asm塊中不允許
MASM中的定義訓示符: DB、DW、DD、DQ、DT和DF,也不允許DUP和THIS操作符。MASM結構和記錄也不再有效,内聯彙編不接受STRUC、RECORD、
WIDTH或者MASK。
4.EVEN和ALIGN訓示符
盡管内聯彙編不支援大多數MASM訓示符,但它支援EVEN和ALIGN,當需要的時候,這些訓示符在彙編代碼裡面加入NOP(空操作)指令使
标号對齊到特定邊界。這樣可以使某些處理器取指令時具有更高的效率。
5.MASM宏訓示符
内聯彙編不是宏彙編,不能使用MASM宏訓示符(MACRO、REPT、IRC、IRP和ENDM)和宏操作符(<>、!、&、%和.TYPE)。
6.段說明
必須使用寄存器來說明段,跨越段必須顯式地說明,如ES:[BX]。
7.類型和變量大小
我們可以使用LENGTH來取得C/C++中的數組中的元素個數,如果不是一個數組,則結果為一。使用SIZE來取得C/C++中變量的大小,一
個變量的大小是LENGTH和TYPE的乘積。TYPE用來取得一個變量的大小,如果是一個數組,它得到的一個數組中的單個元素的大小。
8.注釋
可以使用C/C++的注釋(//,),但推薦用ASM的注釋,即";"号。
9._EMIT僞指令
_EMIT僞指令相當于MASM中的DB,但一次隻能定義一個位元組,比如:
__asm
{
JMP _CodeOfAsm
_EMIT 0x00 ; 定義混合在代碼段的資料
_EMIT 0x01
_CodeOfAsm:
; 這裡是代碼
_EMIT 0x90 ; NOP指令
}
三 在内聯彙編代碼中使用C操作符
在内聯彙編中不能使用C\C++專有的操作符,諸如:<<,雖然,有一些操作符是MASM與C中都在使用的,比如:*操作符。但在内聯彙編中
被優先解釋為彙編操作符。例如,在C中方括号是用來通路數組的元素的。C将它解釋為首位址+單個元素長度*元素序号。而在内聯彙編中,則
将它解釋為首位址+方括号中定義的數量。是對一個位址的位元組偏移量。這一點是在程式設計中應該特注意的。是以以下這一段代碼是錯誤的:
int array[10];
__asm mov array[6], 0 ; 期望達到C中的array[6] = 0功能,但這是錯誤的。
正确的代碼如下:
__asm mov array[6 * TYPE int], 0 ;
array[6] = 0;
在内聯彙編中使用C\C++符号(如前面如述,符号包括常量名,變量名,函數名以及跳轉标簽)應注意以下幾點:
*所使用C\C++符号必須在其使用名域之内。
*一般的情況下,一句彙編語句隻允許出現一個C\C++符号。在LENGTH, TYPE, 和 SIZE表達式中則可以使用多個C\C++符号。
*就像C語言一樣,在内聯彙編中調用函數之前,必須顯式的聲明函數。否則編譯器将會報錯。
*注意在内聯彙編中不能使用那些與MASM中保留字相同的C\C++符号。
*注意C\C++中的類,結構體以及共用體在内聯彙編中不直接使用。
下面将舉幾個關于使用C\C++符号的例子。
如果先前C已經定義了一個變量var,那麼則内聯彙編可以通路這個變量如下:
__asm mov eax, var ;将變量var中的值賦給eax寄存器中。
如果有一個結構體first_type和一個執行個體hal:
struct first_type
{
char *weasel;
int same_name;
} hal;
在通路hal對象時,則必須如下:
__asm
{
mov ebx, OFFSET hal ;取得hal對象的首位址
mov ecx, [ebx]hal.same_name ;加上same_name偏移值,則可以通路到成員same_name
mov esi, [ebx]hal.weasel ;加上weasel偏移值。
}
下面是一個内聯彙編如何實作一個函數的例子:
#include <stdio.h>
int power2( int num, int power );
void main( void )
{
printf( "3 times 2 to the power of 5 is %d\n", \
power2( 3, 5) );
}
int power2( int num, int power )
{
__asm
{
mov eax, num ; 取得第一個參數
mov ecx, power ; 取得第二個參數
shl eax, cl ; EAX = EAX * CL
}
//在函數中,傳回值是由eax負責往回傳遞的。(順便問一句ax與eax有什麼不同啊?是不是一樣的?)
}
因為内聯函數中沒有return,是以在上面的例子中,編譯器會報出警告。還好,不像Java一樣,少一個多一個return都會編譯不通過。你可以使用宏#pragma warning來關掉警告器。在pascall式函數中堆棧的複位是由函數負責的,而不是調用者。在上面的例子中,由是在C函數中内部嵌入彙編來完成彙編函數的。在C函數出口處,C編譯器會自動添加複棧指令,而不必自己添寫。那反而會使系統混亂.
在内聯彙編中跳轉指令(包括條件跳轉),可以跳轉到C語言goto能到的所有地方。Goto也可以跳到内聯彙編中定義的标簽,示例如下:
void func( void )
{
goto C_Dest;
goto c_dest;
goto A_Dest;
goto a_dest;
__asm
{
jmp C_Dest ; Legal: correct case
jmp c_dest ; Legal: incorrect case
jmp A_Dest ; Legal: correct case
jmp a_dest ; Legal: incorrect case
a_dest: ; __asm label
}
C_Dest:
return;
}
另外,在給标簽起名時盡量避免與C内部的或已經使用了的标簽名重名,如果那樣的将會出現災難性的程式錯誤。是以,在起名時最好追查一下是否這個名字已經被使用了。在引用函數時,應注意參數的從右向左方向地壓棧。比如有一個函數是
int CAdd (int a,int b)
則應該如此調用:
__asm
{
mov eax,2;
push; 參數b等于2
mov eax,3;
push; 參數a等于3
call CAdd;調用CAdd函數
mov Result,eax;所有函數的傳回值都被存放在eax。于是,Result等于5
}
注意内聯彙編無法調用重載函數,因為被重載函數名與原函數名不一樣。是以如果你需求調用的話, (我記得vcbase中有關于重載函數的文章),就不要定義重載函數,且C++函數必須使用extern "C"關鍵字來定義。
因為C中的預處理指令#define是字元代換,是以你可以使用#define來定義一個彙編宏,例如:
#define PORTIO __asm \
\
{ \
__asm mov al, 2 \
__asm mov dx, 0xD007 \
__asm out al, dx \
}
以上,就是内聯彙編的基本使用描述。由于,本人的英文并不是太好,是以寫出來的文章有些不連續,而且大部分話是我自己說的,或許還會
譯錯的地方,還請大家指教見諒。
以下是我自己寫的一段關于類,結構體的示例:
#include <iostream.h>
struct MyData
{
int nMember1;
int * lpMember2;
};
void main()
{
MyData sample;
__asm//這是對成員變量指派
{
mov eax,12;
mov sample.nMember1,eax;
}
cout <<sample.nMember1<<endl;
__asm//這是對成員指針指派
{
lea eax,sample.nMember1;
mov sample.lpMember2,eax;
}
cout <<*sample.lpMember2<<endl;
__asm//這是對指針所指向的變量指派
{
mov ebx,sample.lpMember2;
mov eax,5;
mov [ebx],eax;
}
cout <<sample.nMember1<<endl;
}
_CodeOfAsm:
; 這裡是代碼
_EMIT 0x90 ; NOP指令
}