内嵌汇编

为什么要使用内嵌汇编?

内嵌汇编通常用于在程序中实现一些高效、精确的操作。例如,在嵌入式平台上运行的程序,如果需要代码占用内存更小、程序运行的效率更高或需要准确地操作寄存器时,嵌入汇编会是不错的选择。

基本语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
asm("assembly code"        /* 汇编代码 */
:output_operand /* 输出参数列表 */
:input_operand /* 输入参数列表 */
:clobbered_operand /* 被改变的操作对象列表 */
);

// 举例
static int value_assignment(int input) {
int ret = 0;
asm volatile(
"movl %1, %0\n" // 超过一条指令就要用 \n 来分割,排版整齐还要加 \t
:"=r"(ret)
:"r"(input)
);
return ret;
}

被改变的操作对象列表

在被改变的参数列表 clobbered_operand 中有一个比较有用的标识符:memory。指定 memory,相当于对编译器形成了一个内存读写的屏障,保证在内联汇编执行前,编译器将某些寄存器里的值刷新进内存,同时在内联汇编执行后,编译器重新加载相关变量的值
所以我们可以见到这样的代码:

1
asm volatile ("" ::: "memory");

作为内存屏障,保证编译器的优化不会跨过这道屏障。加上 volatile 告诉编译器不要优化汇编。

修饰符

修饰符一般跟在参数列表前面。

修饰符 含义
= 只写,常用于修饰所有输出操作数
只读
+ 可读可写
r 可以是任意通用寄存器存储其值
m 一个有效的内存地址
i 是立即数
% 被修饰的操作数可以和下一个互换
& 只能做输出,一般和 “=” 一起使用,如 “=&r(val)”
x 只能做输入

占位符

%0 表示输入和输出列表合并的第 1 个操作数,%1 表示第 2 个,以此类推。