【问题标题】:gcc optimization, const static object, and restrictgcc 优化、const 静态对象和限制
【发布时间】:2011-01-12 09:22:47
【问题描述】:

我正在开发一个嵌入式项目,我正在尝试为一些代码添加更多结构,这些代码使用宏来优化对 USART 寄存器的访问。我想将预处理器#define 的寄存器地址组织成 const 结构。如果我将结构定义为宏中的复合文字并将它们传递给内联函数,则 gcc 已经足够聪明,可以绕过生成的程序集中的指针并直接在代码中硬编码结构成员值。例如:

C1:

struct uart {
   volatile uint8_t * ucsra, * ucsrb, *ucsrc, * udr;
   volitile uint16_t * ubrr;
};

#define M_UARTX(X)                  \
    ( (struct uart) {               \
        .ucsra = &UCSR##X##A,       \
        .ucsrb = &UCSR##X##B,       \
        .ucsrc = &UCSR##X##C,       \
        .ubrr  = &UBRR##X,          \
        .udr   = &UDR##X,           \
    } )


void inlined_func(const struct uart * p, other_args...) {
    ...
    (*p->ucsra) = 0;
    (*p->ucsrb) = 0;
    (*p->ucsrc) = 0;
}
...
int main(){
     ...
     inlined_func(&M_UART(0), other_parms...);
     ...
}

这里UCSR0A、UCSR0B、&c,定义为uart寄存器为l值,如

#define UCSR0A (*(uint8_t*)0xFFFF)

gcc 能够完全消除结构字面量,并且像 inlined_func() 中所示的所有赋值都直接写入寄存器地址,无需将寄存器地址读入机器寄存器,也无需间接寻址:

A1:

movb $0, UCSR0A
movb $0, UCSR0B
movb $0, UCSR0C

这会将值直接写入 USART 寄存器,无需将地址加载到机器寄存器中,因此根本不需要将结构字面量生成到目标文件中。 struct 字面量变成了编译时结构,生成的抽象代码没有成本。

我想摆脱宏的使用,并尝试使用在标头中定义的静态常量结构:

C2:

#define M_UART0 M_UARTX(0)
#define M_UART1 M_UARTX(1)

static const struct uart * const uart[2] = { &M_UART0, &M_UART1 };
....
int main(){
     ...
     inlined_func(uart[0], other_parms...);
     ...
}

但是,gcc 不能在这里完全删除结构:

A2:

movl __compound_literal.0, %eax
movb $0, (%eax)
movl __compound_literal.0+4, %eax
movb $0, (%eax)
movl __compound_literal.0+8, %eax
movb $0, (%eax)

这会将寄存器地址加载到机器寄存器中,并使用间接寻址来写入寄存器。有谁知道无论如何我可以说服 gcc 为 C2 C 代码生成 A1 汇编代码?我尝试了 __restrict 修饰符的各种用法,但均无济于事。

【问题讨论】:

  • 不要使用struct 在结构成员和 USART 寄存器之间进行 1:1 映射。允许编译器在 structclassunion 的成员之间添加填充(在您不知情的情况下)。我认为将struct 映射到 USART 寄存器没有任何好处。另一方面,如果您想使用类建模 USART,那就是另一个话题了...
  • 如果将结构本身存储在数组中而不是指向它们的指针(即(未经测试)static const struct uart uart[2] = { M_UART0, M_UART1 };
  • @Christoph:我想到了这一点并尝试了,但没有帮助。
  • 您使用哪些编译器/优化级别 - 我的 gcc-4.0.0 确实在 -O1...
  • @Christoph:我试过 -O1-3。我正在使用 gcc 4.2.3。

标签: c optimization gcc constants literals


【解决方案1】:

经过多年使用 UART 和 USART 的经验,我得出以下结论:

不要使用 struct 与 UART 寄存器进行 1:1 映射。

编译器可以在您不知情的情况下在 struct 成员之间添加填充,从而弄乱 1:1 对应关系。

最好直接或通过函数写入 UART 寄存器。

在定义寄存器指针时记得使用volatile修饰符。

汇编语言的性能提升很小

只有在通过处理器端口而不是内存映射访问 UART 时才应使用汇编语言。 C 语言不支持端口。通过指针访问 UART 寄存器非常有效(生成汇编语言列表并验证)。有时,汇编代码和验证可能需要更多时间。

将 UART 功能分离到一个单独的库中

这是一个很好的候选人。此外,一旦代码经过测试,就让它去吧。库不必一直(重新)编译。

【讨论】:

  • 编译器可以添加它想要的所有填充;结构的成员是指向寄存器的指针。该结构保存寄存器指针,并且没有被映射到 uart 寄存器。而且我不是在写汇编;我只是展示 gcc 生成的内容。
【解决方案2】:

在我的书中,“跨编译域”使用结构是一个大罪。基本上使用结构来指向某些东西,任何东西,文件数据,内存等。原因是它会失败,它不可靠,无论编译器如何。为此有许多编译器特定的标志和编译指示,更好的解决方案是不这样做。您想指向地址加 8,指向地址加 8,使用指针或数组。在这种特定情况下,我有太多的编译器也无法做到这一点,我编写了汇编程序 PUT32/GET32 PUT16/GET16 函数来保证编译器不会弄乱我的寄存器访问,比如结构,你有一天会被烧毁并且花时间弄清楚为什么你的 32 位寄存器只有 8 位写入它。跳转到函数的开销值得安心,代码的可靠性和可移植性。这也使您的代码非常便携,您可以将包装器放入 put 和 get 函数以跨网络,在 hdl 模拟器中运行您的硬件并进入模拟以读取/写入寄存器等,只需一段代码即可从模拟到嵌入式到操作系统设备驱动到应用层功能都没有变化。

【讨论】:

  • Thomas 对其他答案的评论也适用于此处 - 您误读了他的代码...
猜你喜欢
  • 1970-01-01
  • 2013-09-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-03-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多