我在别人的代码中发现了以下几行:
uint16_t someConstantFactorVariable = ...;
asm("" : "+r" (someConstantFactorVariable));
someConstantFactorVariable
位于一个经常使用的函数中,我猜测代码的作者希望它将变量永久加载到寄存器中。我对吗?使用这种方法代替变量定义中的 register
关键字有什么好处?
在GCC中意味着什么?asm("" : "+r" (var))
它剥夺了关于
var
中包含什么价值的知识。
例如,采用 avr-gcc 的以下代码,该代码在静态存储中递增 64 位整数变量:
uint64_t var;
void inc_var (void)
{
++var;
}
使用
-Os
编译为
00000010 <inc_var>:
10: 20 91 00 01 lds r18, 0x0100 ; 0x800100 <var>
14: 30 91 01 01 lds r19, 0x0101 ; 0x800101 <var+0x1>
18: 40 91 02 01 lds r20, 0x0102 ; 0x800102 <var+0x2>
1c: 50 91 03 01 lds r21, 0x0103 ; 0x800103 <var+0x3>
20: 60 91 04 01 lds r22, 0x0104 ; 0x800104 <var+0x4>
24: 70 91 05 01 lds r23, 0x0105 ; 0x800105 <var+0x5>
28: 80 91 06 01 lds r24, 0x0106 ; 0x800106 <var+0x6>
2c: 90 91 07 01 lds r25, 0x0107 ; 0x800107 <var+0x7>
30: a1 e0 ldi r26, 0x01 ; 1
32: 49 d0 rcall .+146 ; 0xc6 <__adddi3_s8>
34: 20 93 00 01 sts 0x0100, r18 ; 0x800100 <var>
38: 30 93 01 01 sts 0x0101, r19 ; 0x800101 <var+0x1>
3c: 40 93 02 01 sts 0x0102, r20 ; 0x800102 <var+0x2>
40: 50 93 03 01 sts 0x0103, r21 ; 0x800103 <var+0x3>
44: 60 93 04 01 sts 0x0104, r22 ; 0x800104 <var+0x4>
48: 70 93 05 01 sts 0x0105, r23 ; 0x800105 <var+0x5>
4c: 80 93 06 01 sts 0x0106, r24 ; 0x800106 <var+0x6>
50: 90 93 07 01 sts 0x0107, r25 ; 0x800107 <var+0x7>
54: 08 95 ret
由于使用直接寻址,该代码消耗 70 个字节。间接寻址会产生更紧凑的代码(为什么 avr-gcc 不首先使用间接寻址超出了本答案的范围)。
所以让我们在 C 级别使用间接寻址:
void inc_var2 (void)
{
uint64_t *pvar = &var;
++ *pvar;
}
结果是生成的代码与
inc_var
中的代码相同。 编译器知道 var
正在被访问,并且仍然使用冗长的直接寻址。 我们必须使用 asm 才能从编译器中获取这些知识:
void inc_var3 (void)
{
uint64_t *pvar = &var;
__asm ("" : "+r" (pvar));
++ *pvar;
}
现在代码符合预期,只消耗 42 个字节:
0000009c <inc_var3>:
9c: e0 e0 ldi r30, 0x00 ; 0
9e: f1 e0 ldi r31, 0x01 ; 1
a0: 20 81 ld r18, Z
a2: 31 81 ldd r19, Z+1 ; 0x01
a4: 42 81 ldd r20, Z+2 ; 0x02
a6: 53 81 ldd r21, Z+3 ; 0x03
a8: 64 81 ldd r22, Z+4 ; 0x04
aa: 75 81 ldd r23, Z+5 ; 0x05
ac: 86 81 ldd r24, Z+6 ; 0x06
ae: 97 81 ldd r25, Z+7 ; 0x07
b0: a1 e0 ldi r26, 0x01 ; 1
b2: 09 d0 rcall .+18 ; 0xc6 <__adddi3_s8>
b4: 20 83 st Z, r18
b6: 31 83 std Z+1, r19 ; 0x01
b8: 42 83 std Z+2, r20 ; 0x02
ba: 53 83 std Z+3, r21 ; 0x03
bc: 64 83 std Z+4, r22 ; 0x04
be: 75 83 std Z+5, r23 ; 0x05
c0: 86 83 std Z+6, r24 ; 0x06
c2: 97 83 std Z+7, r25 ; 0x07
c4: 08 95 ret
顺便说一句,GCC 知道尽管存在 ABI,但
__adddi3_s8
不会破坏 Z 寄存器,因此 Z 在函数调用中保持不变。
代码的作者希望将变量永久加载到寄存器中
事实并非如此。扩展上面的例子;当编译器了解变量的内容时,它可能会使用它。 没有任何要求该值始终保存在寄存器中:
void inc_var4 (void)
{
uint64_t *pvar = &var;
__asm ("" : "+r" (pvar));
pvar = (uint64_t*) 42;
++ *pvar;
}
编译器将使用指针指向地址 42 的知识(在具有
__AVR_SFR_OFFSET__=0x20
的设备上):
inc_var4:
in r18,0xa
in r19,0xb
in r20,0xc
...
ldi r26,lo8(1)
call __adddi3_s8
out 0xa,r18
out 0xb,r19
out 0xc,r20
...