Arm汇编学习笔记(三)——GCC内联汇编

时间:2021-04-15 04:02:29

之前每次看内联汇编都有一些地方看不明白,"=r","r","%0","%1"这些符号看不明白,本次总结一下这些内容吧,虽然很简单,但是手不能懒!


首先,C语言中为什么要内联汇编以及其带来的好处这个我就不说了。C语言中使用汇编要通过函数asm(),即__asm__()的别名,两者是一样的。

常见的内联汇编有下面两种形式:

asm(  
"mov     r0, r0\n\t"  
"mov     r0, r0\n\t"  
"mov     r0, r0\n\t"  
"mov     r0, r0"  
);  

例1


asm(
"mov %[result], %[value], ror #1" 
: [result] "=r" (y) 
: [value] "r" (x)
:
); 

例2


第一种和一般汇编文件中的汇编程序是一样的,这里不多说了。着重看第二种,为什么要有第二种形式呢?这就和为什么要内联汇编有很大关系了,一般都是在一个C的函数中会使用内联汇编,汇编代码一般会与C函数的代码有数据交换,也就是说通过汇编代码来操作C代码中的一些变量数据,而C代码中的数据存放在内存还是寄存器,或者存放在哪个寄存器我们在写C代码的时候并不知道,所以不可能使用第一种内联汇编形式,那就必须要用第二种形式了。下面是第二种汇编形式的格式定义:

asm(
code    /*汇编指令*/
: output operand list     /*输出操作数列表*/
: input operand list      /*输入操作数列表*/
: clobber list               /*被改变资源列表*/
);  
可以内联汇编通过冒号将内容分成了四个部分,内联汇编和C操作数之前的关联性体现在上面的input和out操作数上。下面我们针对例2进行分析:

1. 汇编指令

mov %[result], %[value], ror #1

2. 输出操作数列表,可选,每个输出数的符号名用方括号包围,后面跟一个约束串,然后再加上一个括号包围的C表达式,这个括号里的符号就是C语言代码中的变量。

[result] "=r" (y) /*result:符号名   "=r":约束串*    (y):C表达式/  

3. 输入操作数列表,可选,语法上与输入操作数列表一样。括号中的x是C语言代码中的变量。

[value] "r" (x)
4. 被改变资源列表,这里是空的,它主要是告诉编译器哪些资源发生了改变,需要去更新。

那么上面的"r"和"=r"是什么意思呢?请看下面两个图:

Arm汇编学习笔记(三)——GCC内联汇编                              Arm汇编学习笔记(三)——GCC内联汇编

上面描述的很清楚,这里不多说了。

如果你曾经读过一些别人写的内联汇编代码就会发现与我们上面写的略有不同,是下面这种形式的:

asm(
"mov %0, %1, ror #1" 
: "=r" (result) 
: "r" (value)
:
);  

例3

实际上是同一种形式,只不过例2的那种形式是从GCC 3.1版本开始才支持的,而再次之前一直是例3的形式。我之前对"%0" "%1"一直很迷惑,操作数用一个带百分号的数字来表示,上述0%和1%分别表示第一个、第二个操作数。GCC的最新版本仍然支持上述语法,但明显,上述语法更容易出错,且难以维护:假设你写一个较长的内联汇编,然后需要在某个位置插入一个新的输出操作数,此时,之后的操作数都需要重新编号。

另外还有一点就是,你会经常看见asm volatile()这样的内联汇编,在asm后面会跟上一个volatile关键字,这是因为编译器会去优化你的代码,不同的C编译器优化的方式还不一样,而这些优化有时候会适得其反,或者运行的过程和你希望的不一样,甚至将你的汇编代码给优化掉。针对这一问题的解决方法是增加volatile属性,这一属性告诉编译器不要对本代码段进行优化。