5. C內聯彙編

用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對應%0a對應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的位置)。

關於內聯彙編就介紹這麼多,本書不做深入討論。