程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> c++中內聯匯編

c++中內聯匯編

編輯:C++入門知識

一、 優點

        使用內聯匯編可以在 C/C++ 代碼中嵌入匯編語言指令,而且不需要額外的匯編和連接步驟。在 Visual C++ 中,內聯匯編是內置的編譯器,因此不需要配置諸如 MASM 一類的獨立匯編工具。這裡,我們就以 Visual Studio .NET 2003 為背景,介紹在 Visual C++ 中使用內聯匯的相關知識(如果是早期的版本,可能會有些許出入)。

        內聯匯編代碼可以使用 C/C++ 變量和函數,因此它能非常容易地整合到 C/C++ 代碼中。它能做一些對於單獨使用 C/C++ 來說非常笨重或不可能完成的任務。

        內聯匯編的用途包括:

        * 使用匯編語言編寫特定的函數;
        * 編寫對速度要求非常較高的代碼;
        * 在設備驅動程序中直接訪問硬件;
        * 編寫 naked 函數的初始化和結束代碼。


二、 關鍵字

        使用內聯匯編要用到 __asm 關鍵字,它可以出現在任何允許 C/C++ 語句出現的地方。我們來看一些例子:

        * 簡單的 __asm 塊:

            __asm
            {
                MOV AL, 2
                MOV DX, 0xD007
                OUT AL, DX
            }

        * 在每條匯編指令之前加 __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 塊的“{}”不會影響 C/C++ 變量的作用范圍。同時,__asm 塊可以嵌套,而且嵌套也不會影響變量的作用范圍。

        為了與低版本的 Visual C++ 兼容,_asm 和 __asm 具有相同的意義。另外,Visual C++ 支持標准 C++ 的 asm 關鍵字,但是它不會生成任何指令,它的作用僅限於使編譯器不會出現編譯錯誤。要使用內聯匯編,必須使用 __asm 而不是 asm 關鍵字。


三、 匯編語言

1. 指令集

        內聯匯編支持 Intel Pentium 4 和 AMD Athlon 的所有指令。更多其它處理器的指令可以通過 _EMIT 偽指令來創建(_EMIT 偽指令說明見下文)。

2. MASM 表達式

        在內聯匯編代碼中,可以使用所有的 MASM 表達式(MASM 表達式是指用來計算一個數值或一個地址的操作符和操作數的組合)。

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. 段

        必須使用寄存器而不是名稱來指明段(段名稱“_TEXT”是無效的)。並且,段跨越必須顯式地說明,如 ES:[EBX]。

7. 類型和變量大小

        在內聯匯編中,可以用 LENGTH、SIZE 和 TYPE 來獲取 C/C++ 變量和類型的大? ?

        * LENGTH 操作符用來取得 C/C++ 中數組的元素個數(如果不是一個數組,則結果為 1)。
        * SIZE 操作符可以獲取 C/C++ 變量的大小(一個變量的大小是 LENGTH 和 TYPE 的乘積)。
        * TYPE 操作符可以返回 C/C++ 類型和變量的大小(如果變量是一個數組,它得到的是數組中單個元素的大小)。

        例如,程序中定義了一個 8 維的整數型變量:

            int iArray[8];

        下面是 C 和匯編表達式中得到的 iArray 及其元素的相關值:

            __asm                       C                                       Size

            LENGTH iArray               sizeof(iArray)/sizeof(iArray[0])        8
            SIZE iArray                 sizeof(iArray)                          32
            TYPE iArray                 sizeof(iArray[0])                       4

8. 注釋

        內聯匯編中可以使用匯編語言的注釋,即“;”。例如:

            __asm MOV EAX, OFFSET pbBuff      ; Load address of pbBuff

        因為 C/C++ 宏將會展開到一個邏輯行中,為了避免在宏中使用匯編語言注釋帶來的混亂,內聯匯編也允許使用 C/C++ 風格的注釋。

9. _EMIT 偽指令

        _EMIT 偽指令相當於 MASM 中的 DB,但是 _EMIT 一次只能在當前代碼段(.text 段)中定義一個字節。例如:

            __asm
            {
                JMP _CodeLabel

                _EMIT 0x00            ; 定義混合在代碼段的數據
                _EMIT 0x01

            _CodeLabel:               ; 這裡是代碼
                _EMIT 0x90            ; NOP指令
            }

10. 寄存器使用

        一般來說,不能假定某個寄存器在 __asm 塊開始的時候有已知的值。寄存器的值將不能保證會從 __asm 塊保留到另外一個 __asm 塊中。

        如果一個函數聲明為 __fastcall 調用方式,則其參數將通過寄存器而不是堆棧來傳遞。這將會使 __asm 塊產生問題,因為函數無法被告知哪個參數在哪個寄存器中。如果函數接收了 EAX 中的參數並立即儲存一個值到 EAX 中的話,原來的參數將丟失掉。另外,在所有聲明為 __fastcall 的函數中,ECX 寄存器是必須一直保留的。為了避免以上的沖突,包含 __asm 塊的函數不要聲明為 __fastcall 調用方式。

        * 提示:如果使用 EAX、EBX、ECX、EDX、ESI 和 EDI 寄存器,你不需要保存它。但如果你用到了 DS、SS、SP、BP 和標志寄存器,那就應該用 PUSH 保存這些寄存器。

        * 提示:如果程序中改變了用於 STD 和 CLD 的方向標志,必須將其恢復到原來的值。


四、 使用 C/C++ 元素

1. 可用的 C/C++ 元素

        C/C++ 與匯編語言可以混合使用,在內聯匯編中可以使用 C/C++ 變量以及很多其它的 C/C++ 元素,包括:

        * 符號,包括標號、變量和函數名;
        * 常量,包括符號常量和枚舉型成員;
        * 宏定義和預處理指示符;
        * 注釋,包括“/**/”和“//”;
        * 類型名,包括所有 MASM 中合法的類型;
        * typedef 名稱,通常使用 PTR 和 TYPE 操作符,或者使用指定的的結構或枚舉成員。

        在內聯匯編中,可以使用 C/C++ 或匯編語言的基數計數法。例如,0x100 和 100H 是相等的。

2. 操作符使用

        內聯匯編中不能使用諸如“<<”一類的 C/C++ 操作符。但是,C/C++ 和 MASM 共有的操作符(比如“*”和“[]”操作符),都被認為是匯編語言的操作符,是可以使用的。舉個例子:

            int iArray[10];

            __asm MOV iArray[6], BX                 ; Store BX at iArray + 6 (Not scaled)
            iArray[6] = 0;                          // Store 0 at iArray+12 (Scaled)

        * 提示:在內聯匯編中,可以使用 TYPE 操作符使其與 C/C++ 一致。比如,下面兩條語句是一樣的:

            __asm MOV iArray[6 * TYPE int], 0       ; Store 0 at iArray + 12
            iArray[6] = 0;                          // Store 0 at iArray + 12

3. C/C++ 符號使用

        在 __asm 塊中可以引用所有在作用范圍內的 C/C++ 符號,包括變量名稱、函數名稱和標號。但是不能訪問 C++ 類的成員函數。

        下面是在內聯匯編中使用 C/C++ 符號的一些限制:

        * 每條匯編語句只能包含一個 C/C++ 符號。在一條匯編指令中,多個符號只能出現在 LENGTH、TYPE 或 SIZE 表達式中。
        * 在 __asm 塊中引用函數必須先聲明。否則,編譯器將不能區別 __asm 塊中的函數名和標號。
        * 在 __asm 塊中不能使用對於 MASM 來說是保留字的 C/C++ 符號(不區分大小寫)。MASM 保留字包含指令名稱(如 PUSH)和寄存器名稱(如 ESI)等。
        * 在 __asm 塊中不能識別結構和聯合標簽。

4. 訪問 C/C++ 中的數據 www.2cto.com

        內聯匯編的一個非常大的方便之處是它可以使用名稱來引用 C/C++ 變量。例如,如果 C/C++ 變量 iVar 在作用范圍內:

            __asm MOV EAX, iVar      ; Stores the value of iVar in EAX

        如果 C/C++ 中的類、結構或者枚舉成員具有唯一的名稱,則在 __asm 塊中可以只通過成員名稱來訪問(省略“.”操作符之前的變量名或 typedef 名稱)。然而,如果成員不是唯一的,你必須在“.”操作符之前加上變量名或 typedef 名稱。例如,下面的兩個結構都具有 SameName 這個成員變量:

            struct FIRST_TYPE
            {
                char *pszWeasel;
                int SameName;
            };

            struct SECOND_TYPE
            {
                int iWonton;
                long SameName;
            };

        如果按下面方式聲明變量:

            struct FIRST_TYPE ftTest;
            struct SECOND_TYPE stTemp;

        那麼,所有引用 SameName 成員的地方都必須使用變量名,因為 SameName 不是唯一的。另外,由於上面的 pszWeasel 變量具有唯一的名稱,你可以僅僅使用它的成員名稱來引用它:

            __asm
            {
                MOV EBX, OFFSET ftTest
                MOV ECX, [EBX]ftTest.SameName           ; 必須使用“ftTest”
                MOV ESI, [EBX]. pszWeasel               ; 可以省略“ftTest”
            }

        * 提示:省略變量名僅僅是為了書寫代碼方便,生成的匯編指令還是一樣的。

5. 用內聯匯編寫函數

        如果用內聯匯編寫函數的話,要傳遞參數和返回一個值都是非常容易的。看下面的例子,比較一下用獨立匯編和內聯匯編寫的函數:

            ; PowerAsm.asm
            ; Compute the power of an integer

            PUBLIC          GetPowerAsm
            _TEXT           SEGMENT WORD PUBLIC 'CODE'
            GetPowerAsm PROC
                PUSH        EBP                 ; Save EBP
                MOV         EBP, ESP            ; Move ESP into EBP so we can refer
                                        ; to arguments on the stack
                MOV         EAX, [EBP+4]        ; Get first argument
                MOV         ECX, [EBP+6]        ; Get second argument
                SHL         EAX, CL             ; EAX = EAX * (2 ^ CL)
                POP         EBP                 ; Restore EBP
                RET                         ; Return with sum in EAX
            GetPowerAsm ENDP
            _TEXT           ENDS
                        END

        C/C++ 函數一般用堆棧來傳遞參數,所以上面的函數中需要通過堆棧位置來訪問它的參數(在 MASM 或其它一些匯編工具中,也允許通過名稱來訪問堆棧參數和局部堆棧變量)。

        下面的程序是使用內聯匯編寫的:

            // PowerC.c

            #i nclude <Stdio.h>

            int GetPowerC(int iNum, int iPower);

            int main()
            {
                printf("3 times 2 to the power of 5 is %d\n", GetPowerC( 3, 5));
            }

            int GetPowerC(int iNum, int iPower)
            {
                __asm
                {
                    MOV EAX, iNum       ; Get first argument
                    MOV ECX, iPower ; Get second argument
                    SHL EAX, CL         ; EAX = EAX * (2 to the power of CL)
                }
                // Return with result in EAX
            }

        使用內聯匯編寫的 GetPowerC 函數可以通過參數名稱來引用它的參數。由於 GetPowerC 函數沒有執行 C 的 return 語句,所以編譯器會給出一個警告信息,我們可以通過 #pragma warning 禁止生成這個警告。

        內聯匯編的其中一個用途是編寫 naked 函數的初始化和結束代碼。對於一般的函數,編譯器會自動幫我們生成函數的初始化(構建參數指針和分配局部變量等)和結束代碼(平衡堆棧和返回一個值等)。使用內聯匯編,我們可以自己編寫干干淨淨的函數。當然,此時我們必須自己動手做一些有關函數初始化和掃尾的工作。例如:

            void __declspec(naked) MyNakedFunction()
            {
                // Naked functions must provide their own prolog.
                __asm
                {
                    PUSH EBP
                    MOV ESP, EBP
                    SUB ESP, __LOCAL_SIZE
                }

                .
                .
                .

                // And we must provide epilog.
                __asm
                {
                    POP EBP
                    RET
                }
            }

6. 調用 C/C++ 函數

        內聯匯編中調用聲明為 __cdecl 方式(默認)的 C/C++ 函數必須由調用者清除參數堆棧,下面是一個調用 C/C++ 函數例子:

            #i nclude <Stdio.h>

            char szFormat[] = "%s %s\n";
            char szHello[] = "Hello";
            char szWorld[] = " world";

            void main()
            {
                __asm
                {
                    MOV         EAX, OFFSET szWorld
                    PUSH        EAX
                    MOV         EAX, OFFSET szHello
                    PUSH        EAX
                    MOV         EAX, OFFSET szFormat
                    PUSH        EAX
                    CALL        printf

                    // 壓入了 3 個參數在堆棧中,調用函數之後要調整堆棧
                    ADD         ESP, 12
                }
            }

        * 提示:參數是按從右往左的順序壓入堆棧的。

        如果調用 __stdcall 方式的函數,則不需要自己清除堆棧。因為這種函數的返回指令是 RET n,會自動清除堆棧。大多數 Windows API 函數均為 __stdcall 調用方式(僅除 wsprintf 等幾個之外),下面是一個調用 MessageBox 函數的例子:

            #i nclude <Windows.h>

            TCHAR g_tszAppName[] = TEXT("API Test");

            void main()
            {
                TCHAR tszHello[] = TEXT("Hello, world!");

                __asm
                {
                    PUSH        MB_OK OR MB_ICONINFORMATION
                    PUSH        OFFSET g_tszAppName             ; 全局變量用 OFFSET
                    LEA         EAX, tszHello                   ; 局部變量用 LEA
                    PUSH        EAX
                    PUSH        0
                    CALL        DWORD PTR [MessageBox]      &

 

 

摘自 踏雪無痕

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved