幾天看了孫原等幾位仁兄關於匯編語言的幾篇文章,頗感興趣。於是查了查98版的MSDN中,其中也有幾篇關於內聯匯編的基礎,索引字是asm。講得不算太難,於是試著將其內容寫下來了,特此貼來。
一、內聯匯編簡述
Visual C++ 6.0編譯器下,內聯匯編可以使用所有的Intel486處理器指令集。而且可以對目標處理器建立起偽指令來實現附加指令功能。內聯匯編可以使用MASM編譯器所允許的表達式,其中的一些表達式可以通過操作符和操作數的組合,對單精值進行運算.
雖然內聯匯編可以訪問C\C++中的數據變量以及類對象,但它不可能通過MASM指令和操作符來定義數據及對象。尤其你還不能使用DB, DW, DD, DQ, DT和DF等定義指令以及DUP和This操作符。匯編中的結構記錄也不是可用的。內聯匯編也不支持directives STRUC, RECORD, WIDTH, 和 MASK指令。不過,在內聯匯編可以使用到一個_emit宏指令,它類似於MASM中的DB指令,它可以在本區域內定義出一個字節型,雖然它每次只能定義一個字節出來,但還是可以用它來定義出一個字符串,請看示例:
#define randasm __asm _emit 0x4A __asm _emit 0x43 __asm _emit 0x4B
…
__asm
{
randasm
}
雖然內聯匯編不支持MASM中的很多指令,但它支持EVEN 和 ALIGN指令。它們被用於那些需要使用align labels來指定分界線的匯編指令。
內聯匯編不可以是一個宏匯編程序,你不可以使用MASM中的宏定義指令以及宏操作符。但內聯匯編是可以使用C\C++中的預理指令來定義宏。
在處理段時,你只能使用寄存器,而不是通過名字,因為在內聯匯編中這是非法的。而且段必須顯式地使用寄存器,如: ES:[BX]
在內聯匯編使用操作符LENGTH, SIZE, 和 TYPE可以來對變量以及類型進行長度的測量,你可以使用它們來求得C\C++中的變量及類型的長度:
*LENGTH操作符可以返回在一個變量數組中的元素個數,如果返回為1則代表這不是一個變量數組。
*SIZE操作符可以求得一個變量及類型的總長度。這個值也可以由LENGTH與TYPE積來求得。
*TYPE操作符可以求得一個變量及類型的長度,與SIZE不同的是,如果變量名是一個數組的話,則返加這個數組中單個元素的長度。
具體情況請看下表:
__asm C Size LENGTH arr sizeof(arr)/sizeof(arr[0]) 8 SIZE arr sizeof(arr) 16 TYPE arr sizeof(arr[0]) 2包含著內聯匯編的程序可以使用/Zi選項編譯,從而來進行代碼級的調試工作。這樣,你就可以同時在C\C++程序段與內聯匯編段中設置斷點,如果你使用/Fas選項允許混合匯編與C\C++源程序調試方式,那麼你就可以看到混合著匯編與源程序的代碼集合。
Visual C++編譯器允許你在內聯匯編程序中使用Intel處理器的MMX指令集。不過如果使用MMX指令集編譯器會發生警告。更多的信息請查看MSDN的Web站點。
二、關於內聯匯編的具體使用說明:
因為內聯匯編不需要編譯與鏈接過程,因此它比一個匯編程序更為方便。由於它能夠訪問C\C++中的變量及函數,所以它能夠更好和你C\C++代碼融為一體。內聯匯編可以在以下方面進行編程:
*用匯編語言編寫函數。
*使用匯編語言來產生速度最優化代碼段。
*直接使用匯編語言來對硬件進行訪問。
*為naked函數調用編寫保護現場和恢復現場代碼( prolog and epilog code)
如果你計劃在不同機器上運行程序的話,那麼你應該分別放置不同機種的專一匯編代碼。因為內聯匯編有一定的機器專一性,它不完全支持所有MASM中的宏與數據類型。
VC不支持C++中的asm關鍵字,所以你需要使用__asm(兩個下劃線)關鍵字來編寫你的內聯匯編代碼。
你可以使用這個關鍵字來編寫一個內聯代碼段,如:
__asm
{
mov al, 2
mov dx, 0xD007
out al, dx
}
也可以只編寫一行式的內聯代碼,如:
__asm mov al, 2
__asm mov dx, 0xD007
__asm out al, dx
以上兩段代碼是同義的。但是第一種寫法比較有好處(advantages?),它可以與C源碼明顯的區別開來,而且避免重復輸入不必要__asm關鍵字。 在內聯匯編代碼段中以下的C語言元素是可以被應用的:
*符號,包括跳轉標簽,變量名和函數名。(所使用C\C++符號必須在其使用名域之內。)
*C\C++常量,包括const符號化常量和共用體(enum)中的常量
*宏以及預處理表達式。
*C\C++風格的注釋,//,/*,*/
*類型名
*typedef定義的類型名
三、在內聯匯編代碼中使用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; /* Legal: correct case */
goto c_dest; /* Error: incorrect case在C中大小寫區分。*/
goto A_Dest; /* Legal: correct case */
goto a_dest; /* Legal: incorrect case */
__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: /* C label */
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
}
注意內聯匯編無法調用重載函數,因為被重載函數名與原函數名不一樣。所以如果你需求調用的話, (我記得vckbase中有關於重載函數的文章),就不要定義重載函數,且C++函數必須使用extern "C"關鍵字來定義。因為C中的預處理指令#define是字符代換,所以你可以使用#define來定義一個匯編宏,例如:
#define PORTIO __asm \ /* Port output */ \ { \ __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;
}
不過,對於成員函數的調用仍沒有成功。請各位高手幫忙解決這個問題。謝謝。