内嵌汇编介绍
内嵌汇编可用于将汇编指令直接插入到C或C++函数,一般在以下这些情况比较有用:
l 在C语言中无法访问的硬件资源,换而言之没有定义SFR,或者没有本征函数可供使用;
l 编写一个时间关键的代码序列,如果用C编写无法确保正确的时间;
l 编写速度关键的代码序列,如果用C编写会太慢。
内嵌汇编语句类似于C函数,因为它可以接受输入参数(输入操作数)、有返回值(输出操作数)和读或写C语言中定义的符号(通过操作数)。IAR Embedded Workbench的内嵌汇编具体会因不同的处理器架构版本有所差异,本文以ARM为例。
内嵌汇编的局限性
大多数可以在普通汇编中做的操作也可以在内嵌汇编中进行,有以下不同之处:
l 不能控制字节对齐,例如,DC32指令可能是非对齐的;
l 只能接受SP(R13),LR(R14),PC(R15)这几个寄存器别名;
l 通常汇编指令会导致错误或者无意义,但是数据定义指令会按预期工作;
l C编译器所使用的资源(寄存器、内存等)必须声明为操作数或破坏性资源(clobbered resources);
l 如果想避免内嵌汇编器语句被编译器优化掉的风险,则必须将其声明为volatile;
l 访问C符号或使用常量表达式需要使用操作数;
l 操作数的表达式之间的依赖关系可能会导致错误;
l 伪指令LDR Rd,=expr不能在内嵌汇编中使用。
使用内嵌汇编的风险
没有使用操作数和破坏资源,内嵌汇编与周围C源码没有接口,这使得内嵌汇编程序代码很脆弱,如果将来更新编译器,还可能成为维护问题。没有使用操作数和破坏资源,在使用内嵌汇编时还有几个限制:
l 编译器的各种优化措施会忽略内嵌汇编的影响,这些语句根本不会被优化;
l 没有声明副作用的情况下,将使用汇编程序语句对函数进行内嵌;
l 内嵌汇编语句是volatile的,并且默认不会破坏memory。这意味着编译器不会删除汇编语句,它被简单的插入程序流中给定的位置。插入对周围代码可能产生的后果或副作用没有考虑在内,例如,如果寄存器或内存位置被改变,它们可能必须在内嵌汇编指令序列中被恢复,以便代码的其余部分正常工作。
内嵌汇编参考
语法
关键字asm和__asm都用于插入内嵌汇编指令,但是当C代码编译时使用--strict选项,asm关键字就不可用了,但__asm任何时候都是可用的。
内嵌汇编语句的语法跟GNU GCC类似:
asm [volatile]( string [assembler-interface])
一个string包含了一个或多个操作数,由“\n”隔开。每个操作可以是一个有效的汇编指令,也可以是一个前缀为可选标签的数据定义汇编指令。标签前不能有空格,标签后必须跟“:”,例如:
asm("label:nop\n"
"b label");
注意:任何在内嵌汇编语句中定义的标签都是该语句的本地标签,你可以将其用于循环或条件码。
如果在内嵌汇编语句中定义一个标签,使用两个列,例如,“label:: nop\n”,这个标签将是公共的,不仅在内嵌汇编语句中,而且在模块中也是。此特性仅用于测试。
没有声明副作用的汇编语句将被视为volatile汇编语句,这意味着它根本不能被优化。编译器将为此类汇编语句发出备注。
assembler-interface是:
: 以逗号分隔的输出操作数列表 /* 可选的 */
: 以逗号分隔的输入操作数列表 /* 可选的 */
: 以逗号分隔的破坏性资源列表 /* 可选的 */
操作数
内嵌汇编语句可以有一个输入和一个输出操作数(以逗号分隔)列表。每个操作数由一个可选的符号名称(方括号)、一个用引号括起来的约束和一个用括号括起来的C表达式组成。
[[ 符号名称 ]] "[修饰符]约束条件" (表达式)
示例:
int Add(int term1, int term2)
{
int sum;
asm("add %0,%1,%2"
: "=r"(sum)
: "r" (term1), "r" (term2));
return sum;
}
在本例中,汇编指令使用一个输出操作数sum,两个输入操作数,term1和term2,并且没有破坏资源。
列表可以为空,例如:
int matrix[M][N];
void MatrixPreloadRow(int row)
{
asm volatile ("pld [%0]" : : "r" (&matrix[row][0]));
}
操作数的约束条件
表格 SEQ 表格 \* ARABIC 1 内嵌汇编操作数约束条件
约束修饰符
约束修饰语可以和约束一起使用来修饰其含义。下面的表列出了支持的约束修饰符:
表格 SEQ 表格 \* ARABIC 2 支持的约束修饰符
汇编指令通过在操作数的序号前面加上%来引用操作数,第一个操作数的序号为0,由%0引用。如果操作数有符号名称,可以使用% (operand.name)语法引用它。符号操作数名称位于与C/C++代码不同的命名空间中,可以与C/C++变量名相同。不过,每个操作数名称在每个汇编语句中必须是唯一的。例如:
int Add(int term1, int term2)
{
int sum;
asm("add %[Rd],%[Rn],%[Rm]"
: [Rd]"=r"(sum)
: [Rn]"r" (term1), [Rm]"r" (term2));
return sum;
}
输入操作数
输入操作数不能有任何约束修饰符,但它们可以有任何有效的C表达式,只要表达式的类型符合寄存器。C表达式将在内嵌汇编语句中的任何汇编指令之前计算,并分配给约束,例如一个寄存器。
输出操作数
输出操作数必须使用=作为约束修饰符,C表达式必须为l值并指定可写位置。例如,=r表示只写通用寄存器。在内联汇编器语句中的最后一条汇编器指令之后,约束将被分配给计算过的C表达式(左值的形式)。假设输入操作数在产生输出之前被使用,编译器可以为输入和输出操作数使用相同的寄存器。为了防止这种情况发生,可以在输出约束前面加上&,使其成为一个早期的破坏性资源,例如=&r。这将确保在与输入操作数不同的寄存器中分配输出操作数。
输入/输出操作数
既可用于输入又可用于输出的操作数必须作为输出操作数列出,并具有“+”修饰符。C表达式必须是左值,并且指定一个可写的位置。该位置将在任何汇编指令之前立即被读取,并将在最后一条汇编指令之后被写入。下面是一个使用读-写操作数的例子:
int Double(int value)
{
asm("add %0,%0,%0" : "+r"(value));
return value;
}
在上面的例子中,输入值的值将被放置在一个通用寄存器中。在汇编语句之后,来自ADD指令的结果将被放在同一个寄存器中。
破坏性资源(Clobbered resources)
内嵌汇编语句可以包含一系列破坏性资源,"resource1", "resource2", ......指定破坏的资源,以通知编译器内嵌汇编语句破坏哪些资源。任何存在于被破坏资源中的值将在内嵌汇编语句执行之后重新被加载。
破坏性资源不会被用作输入或输出操作数。这是一个如何使用破坏性资源的例子:
int Add(int term1, int term2)
{
int sum;
asm("adds %0,%1,%2"
: "=r"(sum)
: "r" (term1), "r" (term2)
: "cc");
return sum;
}
在本例中,条件代码将由add指令修改,因此,“cc”必须列在破坏名单中。
下表列出了所有破坏性资源:
表格 SEQ 表格 \* ARABIC 3 有效的破坏性资源
操作数修饰符
操作数修饰符是%和操作数之间的单个字母,用于转换操作数。在下面的例子中,修饰符L和H分别用于访问一个立即操作数的最小和最大有效16位:
int Mov32()
{
int a;
asm("movw %0,%L1 \n"
"movt %0,%H1 \n" : "=r"(a) : "i"(0x12345678UL));
return a;
}
一些操作数修饰符可以组合使用,在这种情况下,每个字母将转换前一个修饰符的结果。此表描述了每个有效修饰符执行的转换:
表格 SEQ 表格 \* ARABIC 4 操作修饰符和转换
一个使用破坏内存的例子:
int StoreExclusive(unsigned long * location, unsigned long value)
{
int failed;
asm("strex %0,%2,[%1]"
: "=&r"(failed)
: "r"(location), "r"(value)
: "memory");
/* 注意: “strex”要求 Armv6 (Arm) 或 Armv6T2 (THUMB) */
return failed;
}