使用内联汇编主要目的是为了提高效率,同时还是为了实现 C 语言无法实现的部分。
GUN内联汇编的基本格式:
asm volatile("汇编语句"
: 输出部分
: 输入部分
: 会被修改的部分);
ANSI C规范的关键字(前后都有两个下划线连接,中间没有空格) :
__asm__ __volatile__("汇编语句"
: 输出部分
: 输入部分
: 会被修改的部分);
各部分使用“:”格开,汇编语句必不可少,其他三部分可选,如果使用了后面的部分,而前面部分为空,也需要用“:”格开,相应部分内容为空。例如:
__asm__ __volatile__("asm code":output :input :changed)
第一部分是汇编语句,其中 “asm” 是内联汇编语句关键词。
"汇编语句"是你写汇编指令的地方,其格式和汇编语言程序中使用的基本相同。这一部分是必须要有的。后面带冒号的行若不使用就都可以省略。语句之间使用“;”、“\n” 或 “\n\t” 分开。
在汇编语句中,数字加前缀 %,如 %0、%1 等,表示需要使用寄存器的样板操作数。可以使用的此类操作数的总数取决于 CPU 中通用寄存器的数量。由于这些样板操作数也是用%前缀,因此,在涉及到具体的寄存器时就要在寄存器前面加上2个%,以免混淆。
“输出部分”表示当这段嵌入汇编执行完之后,对输出变量的规定,也即是目标操作数如何结合的约束条件。每个这样的条件成为一个“约束”。必要时“输出部分”可以有多个约束,互相以逗号分隔。每个输出约束以“=” 号开头,然后是一个字母表示对操作数类型的说明,然后是关于变量结合的约束。
“输入部分”表示在开始执行汇编代码时,这里指定的一些寄存器中应存放的输入值,它们也分别对应着一 C 变量或常数值。输入约束的格式和输出约束相似,但不带“=”号。当“输出部分”为空,也即没有输出约束时,若有输入约束存在,则必须保留分隔标记 “:” 号。
“会被修改的部分”表示你已对其中列出的寄存器中的值进行了改动,gcc 编译器不能再依赖于它原来对这些寄存器加载的值。如果必要的话,gcc 需要重新加载这些寄存器。因此我们需要把那些没有在输出/输入寄存器中的部分列出,但是在汇编语句中明确使用到或隐含使用到的寄存器明列在这个部分。
操作数的编号从输出部分的第一个约束(序号为0)开始,顺序数下来,每个约束计数一次。在“汇编语句”中引用这些操作数或分配这些操作数的寄存器时,就在序号前面加上一个 “%” 号。
表示约束条件的字母有很多。主要有:
字母 | 约束条件 |
---|---|
“m”, “v” 和“o” | 表示内存单元 |
“r” | 表示任何寄存器 |
“q” | 表示寄存器 eax、ebx、ecx、edx之一 |
“i” 和 “h” | 表示直接操作数 |
“E” 和 “F” | 表示浮点数 |
“g” | 表示“任意” |
“a”,“b”,“c”,“d” | 分别表示要求使用寄存器 eax、ebx、ecx或edx |
“S”, “D” | 分别表示要求使用寄存器 esi 或 edi |
“I” | 表示常数(0至31) |
__asm__ __volatile__("movl %1,%0" : "=r" (result) : "m" (input));
asm 表示后面的代码为内嵌汇编,asm 是 asm 的别名。
volatile 表示编译器不要优化代码,后面的指令保留原样,volatile 是它的别名。
movl %1,%0是指令模板,%0和%1代表指令的操作数,称为占位符,内嵌汇编靠它们将C 语言表达式与指令操作数相对应,他们按照出现的顺序分别与指令操作数 %0 、%1 对应(先依次排完输出的操作数,再依次排输入操作数)。
指令模板后面用小括号括起来的是 C 语言表达式,本例中只有两个:result和input
int a = 100,b= 200;
int result;
__asm__
__volatile__(
"mov %0,%3\n\t" //%0代表result,%3代表123,汇编器自动加#号
"ldr r0,%1\n\t” //%1代表a的地址
"ldr r0,%2\n\t” //%2代表b的地址
"str r0,%2\n\t" //是地址所以只能用ldr或str指令
: "=r"(result),"+m"(a),"+m"(b) //*out1 是%0,out2是%1,..,outN
:"i"(123) //in1是%N,in2是%N+1,...
);