【问题标题】:Optimization of #define vs static const (in avr-gcc)#define 与静态 const 的优化(在 avr-gcc 中)
【发布时间】:2015-03-05 13:36:31
【问题描述】:

虽然我欢迎在一般范围内回答这个问题,但我主要要求 avr-gcc 使其不要太宽泛。

我查看了一些问题,尤其是thisthis 之一。他们主要关注语义差异,例如static const 不能用来代替常量表达式。但是,虽然一般都在谈论内存分配,但他们并没有谈论优化。

我们来看一个例子:

typedef struct {
    GPIO_TypeDef *port;
    uint8_t  pin;
} gpio_pin_t;

static inline void gpio_pin_set(gpio_pin_t gpio, bool set) {
    if(set) GPIO_SetBits  (gpio.port, 1 << gpio.pin);
    else    GPIO_ResetBits(gpio.port, 1 << gpio.pin);
}

//elsewhere including above definitions

static const gpio_pin_t gpio_pin = {GPIOE, 8};

void some_function(bool enable) {
    gpio_pin_set(gpio_pin, enable);
}

如你所见,我使用的是结构体,所以第三种既定方式(枚举)在这里不是一个选项

我可以期望 gcc 优化内联函数中的 gpio.portgpio.pin 访问吗?如果不是,为什么会这样,gcc 在看到 const 时会应用其他优化吗?

总的来说,使用static const 变量而不是定义会在优化方面失去什么,尤其是超越简单的整数常量?

【问题讨论】:

    标签: c gcc optimization macros constants


    【解决方案1】:

    这取决于编译器的实现。

    如果你从不使用它的地址并且没有导出符号(对于 C,默认情况下是这样,因此你必须使用static),那么优化器应该启动并优化它。对于简单类型(intfloat),您可以合理地期望它可以全面工作,但对于structs - 最好检查一下编译器的功能。对于像我的 GCC 这样的简单结构进行优化,消除结构并直接传递它的值,将常量加载到 CPU 寄存器。对于较大的结构,它可能会认为不值得。

    您可以生成要检查的代码的汇编列表:

    avr-gcc -O&lt;opt&gt; -S somefile.c

    gcc -O&lt;opt&gt; -S somefile.c

    不要忘记优化级别!

    使用#define 很糟糕,因为预处理器真的很笨——它只是在编译之前替换代码。使用const 可以为您带来更好的类型安全性。考虑这个例子:

    #define BYTE_VALUE 257
    static const uint8_t sc_value = 257; // at least will show warning
    int my_byte = BYTE_VALUE; // no warning, but obviously it's a bug!
    

    【讨论】:

    • 类型安全点没有实际意义(尽管经常声称):#define BYTE_VALUE ((uint8_t)257)sc_value 一样类型安全。而int my_byte = BYTE_VALUE; 既不是明显错误,也不一定会产生警告。
    【解决方案2】:

    关于抽象:

    你为什么要把这么简单的东西淹没在大量抽象层中?您可以放心地假设每个 C 程序员都知道

    的含义
    PORT |= (1<<pin);
    PORT &= ~(1<<pin);
    

    就 C 程序员而言,这与代码的可读性一样高。通过将此代码隐藏在抽象层中,您的代码对 C 程序员来说可读性降低,尽管它很可能对非程序员来说变得更具可读性。但是您希望前者阅读您的代码,而不是后者。

    就效率而言,上述方法也是最快的。编译器很可能会将此类代码直接转换为单个位设置/位清除汇编指令。

    所以我的建议是抛弃所有的抽象。您不想将硬件隐藏在嵌入式 C 程序员之外。您需要向他们隐藏的是特定的硬件实现,这样他们就不必为每个新项目重新发明轮子。

    其中一个抽象是在编写独立于硬件的 API 层时。比如void led_set (bool lit);。此功能将点亮您板上的 LED。您可以将它放在抽象文件led.h 中,该文件没有对应的.c 文件。因为 .c 文件仅针对您的特定项目实现:在my_led.c 您将拥有实际实现,直接访问 GPIO 寄存器、设置数据方向和拉电阻寄存器、处理信号极性等。


    关于您的具体问题:

    无法保证 GCC 会按照您的预期内联该函数:inline 关键字已经过时了,因为在决定何时内联函数时,现在的编译器比程序员聪明得多。我会说这很有可能,因为您在编译时启用了最大优化。找出答案的唯一方法就是尝试。

    但是编译器是否内联这个函数并不重要。您可能永远不会有如此极端的实时要求,以至于函数调用开销会影响您的程序。我们在这里谈论的是几十纳秒:即使是您板上的数字电子集成电路也无法以足够快的速度响应这些额外的 CPU 滴答声以产生影响。

    我每天都在使用基于 MCU 的实时嵌入式系统,即使在这些系统中,您也很少会遇到像这样的极端代码优化很重要的情况。如果你这样做了,你将使用 DSP,而不是普通的 MCU,当然也不是 AVR。

    更重要的是,您的 static const 将常量的范围缩小到本地文件,因此其他人不需要关心它,您也不会弄乱全局命名空间。这是良好的编程习惯,良好的编程习惯在 10 次中有 9 次战胜手动代码优化。

    【讨论】:

    • 公平地说,如果他使用 avr-gcc,他很可能会开始使用微控制器。在 1MHz 时,这确实开始产生影响,尤其是在您经常更改引脚状态的情况下。如果您正在处理超低功耗的东西,这会产生很大的不同(保持更长时间而不是睡觉的东西)。
    • @BWalker 也许,但如果您想降低 I/O 引脚的功耗,您宁愿考虑尽量减少引脚切换的数量。大部分功耗发生在引脚开启/关闭时的信号边沿。
    猜你喜欢
    • 2011-01-12
    • 2015-01-20
    • 1970-01-01
    • 2011-11-14
    • 2013-09-04
    • 1970-01-01
    • 2015-12-01
    • 2018-11-09
    • 2011-01-27
    相关资源
    最近更新 更多