gcc 优化、const 静态对象和限制

Tim*_*fer 5 c optimization gcc constants literals

我正在开发一个嵌入式项目,并尝试向某些代码添加更多结构,这些代码使用宏来优化对 USART 寄存器的访问。我想将预处理器 #define'd 寄存器地址组织到 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...);
     ...
}
Run Code Online (Sandbox Code Playgroud)

这里 UCSR0A、UCSR0B 和 c 被定义为 uart 寄存器的左值,例如

#define UCSR0A (*(uint8_t*)0xFFFF)
Run Code Online (Sandbox Code Playgroud)

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

A1:

movb $0, UCSR0A
movb $0, UCSR0B
movb $0, UCSR0C
Run Code Online (Sandbox Code Playgroud)

这会将值直接写入 USART 寄存器,无需将地址加载到机器寄存器中,因此根本不需要将结构文字生成到目标文件中。结构文字成为编译时结构,无需为抽象生成代码而产生任何成本。

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

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...);
     ...
}
Run Code Online (Sandbox Code Playgroud)

然而,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)
Run Code Online (Sandbox Code Playgroud)

这会将寄存器地址加载到机器寄存器中,并使用间接寻址写入寄存器。有谁知道我可以说服 gcc 为 C2 C 代码生成 A1 汇编代码吗?我尝试了 __restrict 修饰符的各种用途,但没有成功。

Tho*_*ews 2

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

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

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

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

volatile请记住在定义指向寄存器的指针时使用修饰符。

汇编语言的性能提升甚微

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

将 UART 功能隔离到单独的库中

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

  • 编译器可以添加它想要的所有填充;结构体的成员是指向寄存器的“指针”。该结构保存寄存器指针,并且“未”映射到 uart 寄存器。我不是在写汇编;我只是展示 gcc 生成的内容。 (2认同)