内联汇编是指在 C/C++ 代码中嵌入的汇编代码,与全部是汇编的汇编源文件不同,它们被嵌入到 C/C++ 的大环境中。
gcc 内联汇编的格式如下:
asm ( 汇编语句 : 输出操作数 // 非必需 : 输入操作数 // 非必需 : 其他被污染的寄存器 // 非必需 );
我们通过一个简单的例子来了解一下它的格式(gcc_add.c):
#include <stdio.h> int main() { int a=1, b=2, c=0; // 蛋疼的 add 操作 asm( "addl %2, %0" // 1 : "=g"(c) // 2 : "0"(a), "g"(b) // 3 : "memory"); // 4 printf("现在c是:%d\n", c); return 0; }
内联汇编中:
第2行是输出操作数,都是 "=?"(var) 的形式, var 可以是任意内存变量(输出结果会存到这个变量中), ? 一般是下面这些标识符(表示内联汇编中用什么来代理这个操作数):
在汇编中用 %序号 来代表这些输入/输出操作数,序号从 0 开始。为了与操作数区分开来,寄存器用两个%引出,如:%%eax
上面这一段内联汇编的效果就是,把a与b的和存入了c。当然这只是一个示例程序,谁要真这么用就蛋疼了,内联汇编一般在不得不用的情况下才使用。
gcc 内联汇编被设计得很复杂,初学者看了往往头大,而 VC 的内联汇编就简单多了:
__asm{ 汇编语句 }
一个例子程序如下(vc_add.c):
#include <stdio.h> int main() { int a=1, b=2, c=0; // 蛋疼的 add 操作 __asm{ push eax // 保护 eax mov eax, a // eax = a; add eax, b // eax = eax + b; mov c, eax // c = eax; pop eax // 恢复 eax } printf("现在c是:%d\n", c); return 0; }
VC 的内联汇编中可以直接以变量名的形式使用局部变量,这就方便多了。但是, VC 内联汇编中有些变量名是保留的,比如:size,使用这些变量名就会报错(把b改成size,上面的程序就编译不通过了)。所以,起名字一定要小心!
因为 VC 没有输入/输出操作数列表,它也不看你的汇编代码(直接拿去用),所以它不知道你修改了哪些寄存器,这些要修改的寄存器可能保存着重要数据,所以用 push/pop 来 保护/恢复 要修改的寄存器。而 gcc 就不需要,它能从输入/输出列表中获得丰富的信息来调剂各个寄存器的使用,并进行优化,所以从效率上说 VC 完败!
用内联汇编的主要目的是为了提高效率:假设有一个比较文本差异的程序 diff,它花了 99% 的时间在 strcmp 这个函数上,如果用内联汇编实现的一个高效的 strcmp 比用 C 语言实现的快 1 倍,那么专家花在这个小小函数上的心思就能够将整个程序的效率提高差不多 1 倍,这是很值得去做的"斤斤计较"。
还有一个目的就是为了实现 C 语言无法实现的部分,比如说 IO 操作,还有我们上一篇中提到的自主修改 esp 寄存器也是必须用汇编才能实现的。
学 gcc 内联汇编最好的导师莫过于 linux 内核,有很多常用的小函数如 memcpy、strlen、strcpy、…… 其中都有短小精悍的内联汇编版本,如在 linux 2.6.37 中的 memcpy 函数:
// 位于 /arch/x86/boot/compressed/misc.c void *memcpy(void *dest, const void *src, size_t n) { int d0, d1, d2; asm volatile( "rep ; movsl\n\t" "movl %4,%%ecx\n\t" "rep ; movsb\n\t" : "=&c" (d0), "=&D" (d1), "=&S" (d2) : "0" (n >> 2), "g" (n & 3), "1" (dest), "2" (src) : "memory"); return dest; }
与 gcc_add.c 相比,这个函数要复杂不少:
rep ; movsl 的工作流程如下:
while(ecx) { movl (%esi), (%edi); esi += 4; edi += 4; ecx--; }
rep ; movsb 与此类似,只是每次拷贝的不是双字(4字节),而是字节。
memcpy 先复制一个一个的双字,到最后如果还有没复制完的(少于4个字节),再一个一个字节地复制。我最终实现的 d_printf 就模仿了这个函数。
深入研究:
gcc 内联汇编 HOWTO 文档
Linux Cross Reference——各版本 linux 内核函数检索