1、很早之前就聽說 C 語言能夠直接內嵌匯編指令。但是之前始終沒有去詳細了解過。最近由於某種需求,看到了相關的 C 語言代碼。也就自然去簡單的學習了一下如何在 C 代碼中內嵌匯編指令。
1、總的來說在 C 代碼中我們通過 asm/__asm__ 關鍵字來告訴編譯器將指定的內容當匯編指令處理。廢話不多說,先看個例子:
#include <stdio.h>
int main(int argc, char *argv[])
{
int x = 3, y = 4;
__asm__("addl %%ebx, %%eax"
: "=a" (y)
: "b" (x), "a" (y));
printf("x + y = %d\n", y);
return 0;
}
2、這個例子,求兩數之和。將 x 的值加到 y 中,並輸出 y 值。首先來看一下在 C 代碼中插入匯編指令的框架代碼:
__asm__("匯編指令1\n\t"
"匯編指令2\n\t"
"匯編指令3\n\t"
"匯編指令n"
: 輸出變量列表
: 輸入變量列表
: 被破壞的寄存器列表);
1、在 __asm__(); 的“”中,便是編寫匯編指令的地方。利用 C 語言自動連接雙引號的特性,我們可以像框架那樣每一行只寫一條指令,當然你也可以全部寫在一行,那麼需要用 ';' 將不同的指令分開。
2、\n
用於指令換行,\t
使 GCC 編譯的時候產生的匯編指令格式保持規范。
GCC 默認使用 AT&T 格式的匯編語法 它與 intel 的匯編語法之間稍有不同。簡單說兩點不同的地方:
mov eax, #1
AT&T:movl $1, %eax
.3、這裡只是提到了本文中會見到的一部分差異,更多具體關於 AT&T 匯編的知識,這裡就不再贅述。可參見相關描述 AT&T 匯編的書籍。
1、輸出變量列表是描述,在內嵌的匯編指令中將哪些值輸出到 C 代碼環境中的哪個變量中。比如第一個例子中我們指定在執行完了所寫的匯編指令後將 eax 寄存器的值輸出到變量 y 中。
其中 "=a" 指明使用 eax 寄存器為輸出寄存器,輸出到緊跟的變量 (y) 中。
2、輸出變量列表可以寫多個變量,每個之間使用逗號隔開。例如:: “=a” (x), "=b" (y), "=r" (z)
。其中用到的 a, b 等代表相應的寄存器。如下是一部分對應關系。
3、這裡僅僅列出了一部分常用到的代碼,更多詳細請參考 GNU C 的 GCC 使用手冊。
這裡講一下 "=r" 的用法,像 a, b 這些代碼都是指定使用的寄存器。但是 r 是讓編譯器隨機給一個,那麼我怎麼知道是那個呢?
不用擔心,編譯器為使用的隨機寄存器遍了一個號。規則是:從輸出列表開始,一直到輸入列表結束,從左到右,從上到下一次為 %0, %1, %2....所以我們可以這樣改寫第一個代碼例子:
#include <stdio.h>
int main(int argc, char *argv[])
{
int x = 3, y = 4;
__asm__("addl %1, %0"
: "=r" (y)
: "r" (x), "0" (y));
printf("x + y = %d\n", y);
return 0;
}
1、和輸出變量列表一樣,使用的寄存器代碼依然一樣的含義。只是少了 '=' 而已。注意如果一個變量使用 'r' 代碼時,既做輸出,又做輸入的話,在寫輸入變量對應的寄存器時,就寫它在輸出列表裡對應的編號。如上一個例子中 y 既做輸出又做輸入,那麼剛進入匯編指令時,%0的值便為 y 之前的值 4 ,指令結束後 %0 為 7 , 接著又把 %0 輸出到了 y 。
1、這一行告訴 GCC 在內聯的匯編代碼中,哪些寄存器可能會被使用到(顯式/隱式)。那麼 GCC 就會在進入內聯匯編之前將這些寄存器保存起來,最後再恢復。避免影響到其他的代碼。
早期的 GCC 要求把輸入、輸出用到的寄存器寫到破壞列表裡面。但是現在的編譯器能夠自動保存、恢復在輸出、輸入列表裡面用到的寄存器。因此上述的例子中由於沒有影響到其他非輸出、非輸入的寄存器,所以可以省略破壞列表。
看個栗子:
#include <stdio.h>
char* strcpy(char *dst, const char *src)
{
__asm__("cld\n"
"1:\tlodsb\n\t"
"stosb\n\t"
"testb %%al, %%al\n\t"
"jne 1b"
:
:"S" (src), "D" (dst)
:"ax");
return dst;
}
int main(int argc, char *argv[])
{
char buf[512];
strcpy(buf,"Hello,AT&T!");
printf("%s\n", buf);
return 0;
}
// 代碼中隱式的使用到了 ax 寄存器,因此我們特別的指明了 ax 為被破壞的寄存器。
1、新的 GCC 允許我們為隨機分配的寄存器命名,這樣極大的方便我們編寫內聯匯編代碼。看個例子:
#include <stdio.h>
int main ( int argc , char *argv[] )
{
int a = 1;
int b = 2;
__asm__("addl %[b], %[a]"
: [a] "=r"(a)
: [b] "r"(b), "[a]"(a));
printf ("a = %d\n" , a);
return 0;
}
2、其實一看代碼,你就明白,只需要在指明 "=r" , "r" 的前面加上 [name] 之後,便可以在匯編指令裡面直接通過 %[name] 的方式使用相應分配的寄存器了。
我在閱讀 GCC 的使用手冊時,發現了這個特性十分方便,因此在這裡特別提出。當然還有很多新特性,感興趣的讀者可以自行閱讀 GNU GCC 的開發者手冊,並尋找有用的特性。記得回來分享哦。
好了,這次就到這裡吧!