开门见山,汇编语言和C语言混合编程可分为两大类:
- 单独的汇编代码文件与单独的C语言分别编译成目标文件后,一起链接成可执性文件
- 在C语言中嵌入汇编代码,直接编译生成可执行程序
今天主要介绍第二种
内联汇编称为inline assembly, GCC支持在C代码中直接嵌入汇编代码,所以称为GCC inline assembly.
内联汇编按格式分为两大类,一类是最简单的基本内联汇编,另一类是复杂一些的扩展内联汇编.(内联汇编中所用的汇编语言是AT&T,并不是咋们熟悉的Intel语法,GCC只支持它, AT&T是汇编语言的一种语法风格,格式.在某一处理器平台上,无论汇编代码是什么语法,其编译出来的代码是一样的,所以不要误以为AT&T是一种新的机器语言.它仅仅是表达方式不同,意思是一样的)
基本内联汇编
格式: asm[volatile] ("assembly code")
- gcc有个优化选项 -O,可以指定优化级别.当用-O来编译时,gcc按照自己的意图优化代码,说不定就会把自己所写的代码修改了. 关键字volatile是可选项,它告诉gcc:“不要修改我的汇编代码,请原样保留”
- "assembly code"是咱们所写的汇编代码,他必须位于圆括号中,而且必须用双引号引起来.这是格式要求,可以为空.规则:
(1) 指令必须用双引号引起来,无论双引号中是一条指令或多条指令
(2) 一对引号不能跨行,如果跨行需要在结尾用反斜杠\
转义
(3) 指令之间用;
, 换行符\n
或 换行符加制表符\n\t
分隔
char *str = "hello world\n";
int count = 0;
void main()
{
asm("pusha; \
movl $4, %eax; \
movl $1, %ebx; \
movl str, %ecx; \
movl $12, %edx; \
int $0x80; \
mov %eax, count; \
popa \
");
}
扩展内联汇编
gcc本身是个C编译器, 要让其支持汇编语言,必然牵扯以下问题:
- 在内联汇编代码插入之前的C代码,其编译后也要被分配寄存器等资源,插入的汇编代码也要使用寄存器,这是否会造成资源冲突?
- 汇编语言如何访问C代码中的变量
可以看到在基本内联汇编中解决这些问题的方法,是在汇编开始用pusha
将所有的寄存器压栈 ,结束后出栈popa
,看起来还不错,但由用户来保证数据完整性简直就是灾难,而且,运行中有大量的压栈操作,访问内存本身就比较慢,不如在编译阶段由编译器优化,直接分配给寄存器或用寄存器缓存
最后编译器采取的做法是它提供一个模板,让用户在模板中提出要求,其余工作由它负责.
格式asm[volatile] ("assembly code" : output : input : clobber/modify)
- 寄存器约束
a : %eax, %ax, %al
b : %ebx, %bx, %bl
c : %ecx, %cx, %cl
d : %edx, %dx, %dl
S : %esi, %si
D : %edi, %di
// 基本内联汇编
int in_a = 1, in_b = 2, out_sum;
void main()
{
asm("pusha; \
movl in_a, %eax; \
movl in_b, %ebx; \
addl %ebx, %eax; \
movl %eax, out_sum; \
popa");
printf("%d\n", out_sum);
}
//扩展内联汇编
void main()
{
int in_a = 1, in_b = 2, out_sum;
asm("addl %%ebx, %%eax" : "=a"(out_sum) : "a"(in_a), "b"(in_b));
printf("%d\n", out_sum);
}
- 内存操作数约束(Memory operand constraint, m)
当我们不想通过寄存器中转,而是直接操作内存时,可以用"m"来约束。例如:
asm volatile ( “lock; decl %0” : “=m” (counter) : “m” (counter)); - 立即数约束(不太常用)
- 通用约束
为方便对操作数的引用,扩展内联汇编提供了占位符,它的作用是代表约束指定的操作数(寄存器, 内存, 立即数).
占位符分为序号占位符和名称占位符两种序号占位符是对output和input中的操作数,按照他们从左到右出现的次序从0开始编写.引用它的格式是%0~9
asm("addl %%ebx, %%eax" : "=a"(out_sum) : "a"(in_a), "b"(in_b));
等价于
asm("addl %2, %1" : "=a"(out_sum) : "a"(in_a), "m"(in_b));
可以通过gcc -S -o filename.S filename.c
cat filename.S
来查看寄存器的使用情况,你就会明白扩展内联汇编的具体优异之处