最近我试着把我的代码打包到小ATTiny13上,用1kB的闪存。在优化过程中,我发现了一些奇怪的东西。让我们举个例子。
#include <avr/interrupt.h>
int main() {
TCNT0 = TCNT0 * F_CPU / 58000;
}
当然,它没有任何意义,但有趣的是它的输出大小--它产生的结果是 248字节.
代码的快速解释: F_CPU
是常数,定义为 -DF_CPU=...
开关 avr-gcc
, TCNT0
是8位寄存器(在ATTiny13上)。在实际程序中,我将方程结果赋值给了 uint16_t但仍然观察到同样的行为。
如果将表达式的一部分用括号包起来。
TCNT0 = TCNT0 * (F_CPU / 58000);
输出文件大小是 70字节. 巨大的差异,但这些操作的结果是一样的(对吗?)。
我看了一下生成的汇编代码,尽管我对ASM不是很了解,但我看到无括号版本增加了一些标签,比如。
00000078 <__divmodsi4>:
78: 05 2e mov r0, r21
7a: 97 fb bst r25, 7
7c: 16 f4 brtc .+4 ; 0x82 <__divmodsi4+0xa>
7e: 00 94 com r0
80: 0f d0 rcall .+30 ; 0xa0 <__negsi2>
82: 57 fd sbrc r21, 7
84: 05 d0 rcall .+10 ; 0x90 <__divmodsi4_neg2>
86: 14 d0 rcall .+40 ; 0xb0 <__udivmodsi4>
88: 07 fc sbrc r0, 7
8a: 02 d0 rcall .+4 ; 0x90 <__divmodsi4_neg2>
8c: 46 f4 brtc .+16 ; 0x9e <__divmodsi4_exit>
8e: 08 c0 rjmp .+16 ; 0xa0 <__negsi2>
还有更多 我只学过一段时间的x86汇编器,但在我印象中,除法有一个简单的符号。为什么呢?avr-gcc
在第一个例子中增加了这么多代码?
另一个问题是,如果在编译时两个数字都是已知的,为什么编译器不内联公式的正确部分。
我们有这个。
x = x * 1200000 / 58000
请注意... 1200000/58000 = 20.69...
不是一个整数,所以必须以先乘后除的方式计算。你的架构没有这个数据类型的原生整数除法,所以必须模拟它,导致代码很多。
然而这个。
x = x * (1200000 / 58000)
我们发现 1200000 / 58000 = 20
, 因为C使用了地板分割,所以这段代码简化为只。
x = x * 20