gcc内嵌汇编详解
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
gcc内嵌汇编详解
有时候我们希望在C/C++代码中使⽤嵌⼊式汇编,因为C中没有对应的函数或语法可⽤。
⽐如我最近在ARM上写FIR程序时,需要对最后的结果进⾏饱和处理,但gcc没有提供ssat这样的函数,于是不得不在C代码中嵌⼊汇编指令。
1. ⼊门
在C中嵌⼊汇编的最⼤问题是如何将C语⾔变量与指令操作数相关联。
当然,gcc都帮我们想好了。
下⾯是是⼀个简单例⼦。
asm(“fsinx %1, %0”:”=f”(result):”f”(angle));
这⾥我们不需要关注fsinx指令是⼲啥的;只需要知道这条指令需要两个浮点寄存器作为操作数。
作为专职处理C语⾔的gcc编译器,它是没办法知道fsinx这条汇编指令需要什么样的操作数的,这就要求程序猿告知gcc相关信息,⽅法就是指令后⾯的”=f”和”f”,表⽰这是两个浮点寄存器操作数。
这被称为操作数规则(constraint)。
规则前⾯加上”=”表⽰这是⼀个输出操作数,否则是输⼊操作数。
constraint后⾯括号内的是与该寄存器关联的变量。
这样gcc就知道如何将这条嵌⼊式汇编语句转成实际的汇编指令了:
fsinx:汇编指令名
%1, %0:汇编指令操作数
“=f”(result):操作数%0是⼀个浮点寄存器,与变量result关联(对输出操作数,“关联”的意思就是说gcc执⾏完这条汇编指令后会把寄存器%0的内容送到变量result中)“f”(angle):操作数%1是⼀个浮点寄存器,与变量angle关联(对输⼊操作数,“关联”的意思是就是说gcc执⾏这条汇编指令前会先将变量angle的值读取到寄存器%1中)
因此这条嵌⼊式汇编会转换为⾄少三条汇编指令(⾮优化):
1> 将angle变量的值加载到寄存器%1
2> fsinx汇编指令,源寄存器%1,⽬标寄存器%0
3> 将寄存器%0的值存储到变量result
当然,在⾼优化级别下上⾯的叙述可能不适⽤;⽐如源操作数可能本来就已经在某个浮点寄存器中了。
这⾥我们也看到constraint前加”=”符号的意义:gcc需要知道这个操作数是在执⾏嵌⼊汇编前从变量加载到寄存器,还是在执⾏后从寄存器存储到变量中。
常⽤的constraints有以下⼏个(更多细节参见gcc⼿册):
m 内存操作数
r 寄存器操作数
i ⽴即数操作数(整数)
f 浮点寄存器操作数
F ⽴即数操作数(浮点)
从这个栗⼦也可以看出嵌⼊式汇编的基本格式:
asm(“汇编指令”:”=输出操作数规则”(关联变量):”输⼊操作数规则”(关联变量));
输出操作数必须为左值;这个显然。
2. 多个操作数,或没有输出操作数
如果某个指令有多个输⼊或输出操作数怎么办?例如arm有很多指令是三操作数指令。
这个时候⽤逗号分隔多个规则:
asm(“add %0, %1, %2”:”=r”(sum):”r”(a), “r”(b));
每条操作数规则按顺序对应操作数%0, %1, %2。
对于没有输出操作数的情况,在汇编指令后就没有输出规则,于是就出现两个连续冒号,后跟输⼊规则。
3. 输⼊-输出(或读-写)操作数
有时候⼀个操作数既是输⼊⼜是输出,⽐如x86下的这条指令:
add %eax, %ebx
注意指令使⽤AT&T格式⽽不是Intel格式。
寄存器ebx同时作为输⼊操作数和输出操作数。
对这样的操作数,在规则前使⽤”+”字符:
asm("add %1, %0" : "+r"(a) : "r"(b));
对应C语⾔语句a=a+b。
注意这样的操作数不能使⽤”=”符号,因为gcc看到”=”符号会认为这是⼀个单输出操作数,于是在将嵌⼊汇编转换为真正汇编的时候就不会预先将变量a的值加载到寄存器%0中。
另⼀个办法是将读-写操作数在逻辑上拆分为两个操作数:
asm(“add %2, %0” : “=r”(a) : “0”(a), “r”(b));
对“逻辑”输⼊操作数1指定数字规则”0”,表⽰这个逻辑操作数占⽤和操作数0⼀样的“位置”(占⽤同⼀个寄存器)。
这种⽅法的特点是可以将两个“逻辑”操作数关联到两个不同的C语⾔变量上:
asm("add %2, %0" : "=r"(c) : "0"(a), "r"(b));
对应于C程序语句c=a+b。
数字规则仅能⽤于输⼊操作数,且必须引⽤到输出操作数。
拿上例来说,数字规则”0”位于输⼊规则段,且引⽤到输出操作数0,该数字规则⾃⾝占⽤操作数计数1。
这⾥要注意,通过同名C语⾔变量是⽆法保证两个操作数占⽤同⼀“位置”的。
⽐如下⾯这样的写法是不⾏的:
(错误写法)asm(“add %2, %0”:”=r”(a):”r”(a), “r”(b));
4. 指定寄存器
有时候我们需要在指令中使⽤指定的寄存器;典型的栗⼦是系统调⽤,必须将系统调⽤码和参数放在指定寄存器中。
为了达到这个⽬的,我们要在声明变量时使⽤扩展语法:
register int a asm(“%eax”) = 1; // statement 1
register int b asm(“%ebx”) = 2; // statement 2
asm("add %1, %0" : "+r"(a) : "r"(b)); // statement 3
注意只有在执⾏汇编指令时能确定a在eax中,b在ebx中,其他时候a和b的存放位置是不可知的。
另外,在这么⽤的时候要注意,防⽌statement 2在执⾏时覆盖了eax。
例如statement 2改成下⾯这句:
register int b asm(“%ebx”) = func();
函数调⽤约定会将func()的返回值放在eax⾥,于是破坏了statement 1对a的赋值。
这个时候可以先⽤⼀条语句将func返回值放在临时变量⾥:
int t = func();
register int a asm(“%eax”) = 1; // statement 1
register int b asm(“%ebx”) = t; // statement 2
asm("add %1, %0" : "+r"(a) : "r"(b)); // statement 3
5. 隐式改变寄存器
有的汇编指令会隐含修改⼀些不在指令操作数中的寄存器,为了让gcc知道这个情况,将隐式改变寄存器规则列在输⼊规则之后。
下⾯是VAX机上的栗⼦:
asm volatile(“movc3 %0,%1,%2”
: /* no outputs */
:”g”(from),”g”(to),”g”(count)
:”r0”,”r1”,”r2”,”r3”,”r4”,”r5”);
(movc3是⼀条字符块移动(Move characters)指令)
这⾥要注意的是输⼊/输出规则中列出的寄存器不能和隐含改变规则中的寄存器有交叉。
⽐如在上⾯的栗⼦⾥,规则“g”中就不能包含r0-r5。
以指定寄存器语法声明的变量,所占⽤的寄存器也不能和隐含改变规则有交叉。
这个应该好理解:隐含改变规则是告诉gcc有额外的寄存器需要照顾,⾃然不能和输⼊/输出寄存器有交集。
另外,如果你在指令⾥显式指定某个寄存器,那么这个寄存器也必须列在隐式改变规则之中(有点绕了哈)。
上⾯我们说过gcc⾃⾝是不了解汇编指令的,所以你在指令中显式指定的寄存器,对gcc来说是隐式的,因此必须包含在隐式规则之中。
另外,指令中的显式寄存器前需要⼀个额外的%,⽐如%%eax。
6. volatile
asm volatile通知gcc你的汇编指令有side effect,千万不要给优化没了,⽐如上⾯的栗⼦。
如果你的指令只是做些计算,那么不需要volatile,让gcc可以优化它;除此以外,⽆脑给每个asm加上volatile或者是个好办法。