为什么 a*b/c 而不是 a*(b/c) 在 AVR 上给出大 3 倍的程序大小?

Why a*b/c instead of a*(b/c) gives 3x bigger program size on AVR?

最近我试图将我的代码打包到带有 1kB 闪存的小型 ATTiny13 中。在优化过程中,我发现了一些对我来说很奇怪的东西。让我们以示例代码为例:

#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使用了flooring division,所以这段代码简化为:

x = x * 20