Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 的位置)。

关于内联汇编就介绍这么多,本书不做深入讨论。