09. C語言內嵌彙編程式碼

阿狸喜羊羊發表於2024-05-09


C語言函式內可以自定義一段彙編程式碼,在GCC編譯器中使用 asm 或 __asm__ 關鍵詞定義一段彙編程式碼,並可選新增volatile關鍵字,表示不要讓編譯器最佳化這段彙編程式碼。

內嵌彙編程式碼格式如下:

__asm__
(
    "彙編程式碼"
    :輸出描述
    :輸入描述
    :修改描述
);


彙編程式碼部分

彙編程式碼部分是一個字串,嵌入的彙編程式碼使用此字串儲存,多個彙編程式碼語句之間使用;符號隔開,字串可以換行儲存,換行儲存方式如下:

__asm__
(
    "mov eax,ecx; \
    add eax,ebx;"
);

或者:

__asm__
(
    "mov eax,ecx;"
    "add eax,ebx;"
);


注:
1.彙編程式碼中16進位制資料只能使用0x字首,不能使用h字尾。
2.指定記憶體資料長度時,使用 word prt 關鍵詞,不能省略 prt。
3.內嵌彙編程式碼預設使用AT&T語法,若使用inter語法則需要在編譯時新增如下引數:-masm=intel,本文使用inter彙編語法。


輸出描述部分

若彙編程式碼執行完畢後需要將暫存器、記憶體單元中的資料儲存在C語言程式碼定義的區域性變數中,則需要在輸出描述部分定義暫存器或記憶體單元與區域性變數的繫結關係,繫結程式碼格式如下:

#include <stdio.h>
int main()
{
    int i;

    __asm__
    (
        "mov eax,1; \
        mov ebx,2; \
        add eax,ebx;"
        
        :"=a"(i)          //輸出描述部分,"=a"表示輸出eax暫存器,(i)表示使用變數i接收輸出資料
    );
    
    printf("%d\n", i);
    return 0;
}


輸出描述部分使用簡寫程式碼表示要輸出的暫存器或記憶體單元,常用程式碼如下:
a,表示ax系暫存器
b,表示bx系暫存器
c,表示cx系暫存器
d,表示dx系暫存器
S,表示si系暫存器
D,表示di系暫存器
r,表示自動分配的暫存器
m,表示自動分配的記憶體單元
g,表示自動分配的暫存器或記憶體單元


繫結的區域性變數可以使用指標指定。

#include <stdio.h>
int main()
{
    int i;
    int *p1 = &i;
    
    __asm__
    (
        "mov eax,1; \
        mov ebx,2; \
        add eax,ebx;"
        
        :"=a"(*p1)
    );
    
    printf("%d\n", i);
    return 0;
}


輸入描述部分

輸入描述用於將區域性變數與指定的暫存器或記憶體單元繫結,繫結的變數會首先複製到對應的暫存器或記憶體單元,然後再執行彙編程式碼。

#include <stdio.h>
int main()
{
    int i1, i2, i3;
    
    printf("輸入兩個整數\n");
    scanf("%d%d", &i1, &i2);
    
    __asm__
    (
        "add eax,ebx;"
        
        :"=a"(i3)            //輸出描述部分,eax寫入i3
        
        :"a"(i1), "b"(i2)    //輸入描述部分,i1寫入eax,i2寫入ebx
    );
    
    printf("兩數相加結果為:%d\n", i3);
    return 0;
}


修改描述部分

修改描述用於告知編譯器哪些暫存器、記憶體單元被彙編程式碼修改過,讓編譯器在編譯程式碼時對這些暫存器或記憶體單元進行保護,當然也可以手動進行保護,在使用暫存器之前首先將其入棧儲存,在彙編程式碼末尾處還原暫存器。

修改描述注意事項:
1.若被修改的暫存器在輸出描述、輸入描述中記錄過,則無需在修改描述中重複指定,編譯器會自動處理。
2.若修改了記憶體單元,則應該在此部分定義"memory"。
3.若修改了標誌暫存器,則應該在此部分定義"c"。

#include <stdio.h>
int main()
{
    int i1, i2, i3;
    
    printf("輸入兩個整數\n");
    scanf("%d%d", &i1, &i2);
    
    __asm__
    (
        "add eax,ebx; \
        mov edx,3; \
        imul ecx,edx"        //ecx與edx相乘僅做示例,沒有實際作用
        
        :"=a"(i3)            //輸出描述部分,eax寫入i3
        
        :"a"(i1), "b"(i2)    //輸入描述部分,i1寫入eax,i2寫入ebx
        
        :"ecx", "edx"        //修改描述部分,告知編譯器ecx、edx被修改過,並且沒有在輸入輸出描述中記錄
    );
    
    printf("兩數相加結果為:%d\n", i3);
    return 0;
}


編譯器自動分配暫存器、記憶體單元

在彙編程式碼中儲存資料時可以讓編譯器自動分配暫存器或記憶體,彙編程式碼使用“%數字”的方式呼叫編譯器自動分配的暫存器或記憶體,比如:%0、%1、%2,這些名稱稱為佔位符,佔位符可以不按順序定義。

佔位符可以與輸入輸出描述中的區域性變數繫結,繫結順序為區域性變數在輸入輸出描述中出現的順序,%0繫結第一個變數、%1第二個、%2第三個。

#include <stdio.h>
int main()
{
    int i1, i2, i3;
    
    printf("輸入兩個整數\n");
    scanf("%d%d", &i1, &i2);
    
    __asm__
    (
        "add %1,%2; \
        mov %0,%1"
        
        :"=m"(i3)            //輸出描述部分,%0寫入i3,這裡使用m,表示讓編譯器自動分配記憶體單元儲存繫結的i3
        
        :"r"(i1), "r"(i2)    //輸入描述部分,i1寫入%1,i2寫入%2,這裡使用r,表示讓編譯器自動分配暫存器儲存繫結的i1、i2
    );
    
    printf("兩數相加結果為:%d\n", i3);
    return 0;
}

上面彙編程式碼中三個變數出現順序是i3、i1、i2,%0繫結i3,%1繫結i1,%2繫結i2。


使用全域性變數

在彙編程式碼中呼叫全域性變數無需進行任何設定,直接使用變數名即可,編譯器會自動轉換為對應的記憶體地址。

#include <stdio.h>
int i1, i2;
int main()
{
    printf("輸入兩個整數\n");
    scanf("%d%d", &i1, &i2);
    
    __asm__  __volatile__
    (
        "push rax; \
        mov eax,i1; \
        add eax,i2; \
        mov i1,eax; \
        pop rax;"
    );
    
    printf("兩數相加結果為:%d\n", i1);
    return 0;
}

上述彙編程式碼中,全域性資料的名稱無需放在[]符號內,但是若將一個立即數寫入記憶體資料,則需要使用如下程式碼:mov dwort ptr[i1], 5;

相關文章