用C寫程序比直接用彙編寫程序更簡潔,可讀性更好,但效率可能不如彙編程序,因為C程序畢竟要經由編譯器生成彙編代碼,儘管現代編譯器的優化已經做得很好了,但還是不如手寫的彙編代碼。另外,有些平台相關的指令必須手寫,在C語言中沒有等價的語法,因為C語言的語法和概念是對各種平台的抽象,而各種平台特有的一些東西就不會在C語言中出現了,例如x86是連接埠I/O,而C語言就沒有這個概念,所以in/out指令必須用彙編來寫。
C語言簡潔易讀,容易組織規模較大的代碼,而彙編效率高,而且寫一些特殊指令必須用彙編,為了把這兩方面的好處都占全了,gcc提供了一種擴展語法可以在C代碼中使用內聯彙編(Inline Assembly)。最簡單的格式是__asm__("assembly code");,例如__asm__("nop"); ,nop 這條指令什麼都不做,只是讓CPU空轉一個指令執行周期。如果需要執行多條彙編指令,則應該用\n\t將各條指令分隔開,例如:
__asm__("movl $1, %eax\n\t"
"movl $4, %ebx\n\t"
"int $0x80");通常 C 代碼中的內聯彙編需要和C的變數建立關聯,需要用到完整的內聯彙編格式:
__asm__(assembler template : output operands /* optional */ : input operands /* optional */ : list of clobbered registers /* optional */ );
這種格式由四部分組成,第一部分是彙編指令,和上面的例子一樣,第二部分和第三部分是約束條件,第二部分指示彙編指令的運算結果要輸出到哪些C操作數中,C操作數應該是左值表達式,第三部分指示彙編指令需要從哪些C操作數獲得輸入,第四部分是在彙編指令中被修改過的寄存器列表,指示編譯器哪些寄存器的值在執行這條__asm__語句時會改變。後三個部分都是可選的,如果有就填寫,沒有就空着只寫個:號。例如:
例 19.6. 內聯彙編
#include <stdio.h>
int main()
{
int a = 10, b;
__asm__("movl %1, %%eax\n\t"
"movl %%eax, %0\n\t"
:"=r"(b) /* output */
:"r"(a) /* input */
:"%eax" /* clobbered register */
);
printf("Result: %d, %d\n", a, b);
return 0;
}這個程序將變數a的值賦給b。"r"(a)指示編譯器分配一個寄存器保存變數a的值,作為彙編指令的輸入,也就是指令中的%1(按照約束條件的順序,b對應%0,a對應1%),至于%1究竟代表哪個寄存器則由編譯器自己決定。彙編指令首先把%1所代表的寄存器的值傳給eax(為了和%1這種占位符區分,eax前面要求加兩個%號),然後把eax的值再傳給%0所代表的寄存器。"=r"(b)就表示把%0所代表的寄存器的值輸出給變數b。在執行這兩條指令的過程中,寄存器eax的值被改變了,所以把"%eax"寫在第四部分,告訴編譯器在執行這條__asm__語句時eax要被改寫,所以在此期間不要用eax保存其它值。
我們看一下這個程序的反彙編結果:
__asm__("movl %1, %%eax\n\t"
80483dc: 8b 55 f8 mov -0x8(%ebp),%edx
80483df: 89 d0 mov %edx,%eax
80483e1: 89 c2 mov %eax,%edx
80483e3: 89 55 f4 mov %edx,-0xc(%ebp)
"movl %%eax, %0\n\t"
:"=r"(b) /* output */
:"r"(a) /* input */
:"%eax" /* clobbered register */
);可見%0和%1都代表edx寄存器,首先把變數a(位於ebp-8的位置)的值傳給edx然後執行內聯彙編的兩條指令,然後把edx的值傳給b(位於ebp-12的位置)。
關於內聯彙編就介紹這麼多,本書不做深入討論。