用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
的位置)。
關於內聯彙編就介紹這麼多,本書不做深入討論。