基本内联汇编
基本内联汇编的语法很简单,实现的功能也很简单,就是直接将汇编语句插入到编译后的C代码中。基本形式如下。
asm("assembly code");
例如。
asm("nop"); asm("cli");
可以通过加入换行\n\t
写多行内联汇编。
asm( "pushl %eax\n\t"
"movl $0,%eax\n\t"
"popl %eax"
);
但基本内联汇编有很大的问题,那就是编译器其实是不知道你写的内联汇编代码语义是什么,如果你在基本内联汇编里面对寄存器,或者内存做了什么修改它是不知道的,还是照常编译C代码,这就会出现冲突。
并且还有一个缺点就是没法和C代码中的变量进行交互,只能自己玩自己的,这样你就是想改变C代码中的一个变量也做不到。
扩展内联汇编
扩展内联汇编使用更复杂的语法,来指示编译器自己将对哪些寄存器或者内存做修改,并且还可以使用C代码中的变量。其基本语法如下。
asm ( assembler template
: output operands /* optional */
: input operands /* optional */
: clobbered list /* optional */
);
assembler template
汇编模板:就是要执行的汇编代码,并且其中可以使用占位符来指代后面的输出和输入变量,占位符从%0
开始,依次是%1
,%2
等等。output/input operands
输出和输入操作数,则可以将C代码中的变量和寄存器或者内存绑定起来,让编译器知道汇编模板中的代码会影响哪些寄存器和内存。clobbered list
改动列表,可以用来告诉编译器在输出和输入操作数中没用提及的,但是发生了改动的寄存器和内存。
例子如下。
int a=10, b;
asm ("movl %1, %%eax;
movl %%eax, %0;"
:"=r"(b) /* output */
:"r"(a) /* input */
:"%eax" /* clobbered register */
);
这里输入输出操作数和r
绑定起来,这里r
的意思就是让编译器自己决定用哪个寄存器来缓存,对输出操作数还加了=
表示赋值的意思。而在汇编模板中的%0
和%1
占位符,用来指代输出输入操作数,从左到右从0开始编号。
最后注意到clobbered list
还贴心地写上了%eax
,那是因为在输出输入操作数中没有提及该寄存器,但实际的汇编代码中又会改动这个寄存器,所以需要手动告诉编译器自己的代码也会影响到%eax
寄存器。
常用限制符
限制符就是在输出输入操作数中约束C变量与寄存器或者内存的关系的符号。
字母 | 含义 |
---|---|
r | 任何通用寄存器 |
q | 寄存器eax, ebx, ecx或edx |
a, b, c, d | 对应的寄存器eax,ebx,ecx,edx |
S | 寄存器esi |
D | 寄存器edi |
A | 把eax和edx合成一个64 位的寄存器 |
m | 内存变量 |
o | 内存变量,但是其寻址方式是偏移量类型 |
V | 内存变量,但寻址方式不是偏移量类型 |
I | 0-31之间的立即数(用于32位移位指令) |
J | 0-63之间的立即数(用于64位移位指令) |
N | 0-255之间的立即数(用于out指令) |
i | 立即数 |
n | 立即数 |
f | 浮点寄存器 |
t | 第一个浮点寄存器 |
u | 第二个浮点寄存器 |
G | 标准的80387浮点常数 |
常用clobber list
关键字 | 含义 |
---|---|
memory | 指令以一种不可预知的方式修改了内存,用memory告知编译器 |
cc | 标志寄存器发生了改动 |