C99:访问全局变量和别名内存指针时的编译器优化

问题描述

我正在为嵌入式系统编写 C 代码。在这个系统中,在内存映射中的某个固定地址有内存映射寄存器,当然还有我的数据段/堆所在的一些 RAM。

当我的代码混合访问数据段中的全局变量和访问硬件寄存器时,我发现生成最佳代码的问题。这是一个简化的片段:

#include <stdint.h>

uint32_t * const restrict HWREGS = 0x20000;

struct {
    uint32_t a,b;
} Context;

void example(void) {
    Context.a = 123;
    HWREGS[0x1234] = 5;
    Context.b = Context.a;
}

这是在 x86 上生成的代码(另见 godbolt):

example:
        mov     DWORD PTR Context[rip],123
        mov     DWORD PTR ds:149712,5
        mov     eax,DWORD PTR Context[rip]
        mov     DWORD PTR Context[rip+4],eax
        ret

如您所见,在写入硬件寄存器后,Context.a 在存储到 Context.b 之前从 RAM 重新加载。这没有意义,因为 ContextHWREGS 位于不同的内存地址。换句话说,HWREGS 指向的内存和&Context 指向的内存没有别名,但看起来没有办法告诉编译器。

如果我将 HWREGS 定义更改为:

extern uint32_t * const restrict HWREGS;

也就是我向编译器隐藏了固定内存地址,我得到了这个:

example:
        mov     rax,QWORD PTR HWREGS[rip]
        mov     DWORD PTR [rax+18640],5
        movabs  rax,528280977531
        mov     QWORD PTR Context[rip],rax
        ret
Context:
        .zero   8

现在对 Context 的两次写入进行了优化(甚至合并为一次写入),但另一方面,直接内存访问不再发生对硬件寄存器的访问,而是通过指针间接访问。

>

有没有办法在这里获得最佳代码?我想让 GCC 知道 HWREGS 位于固定的内存地址,同时告诉它它没有别名 Context

解决方法

如果你想避免编译器定期从内存区域重新加载值(可能是由于别名),那么最好不要使用全局变量,或者至少不要使用对全局变量的直接访问.对于 GCC 和 Clang 的全局变量(尤其是 register 上的此处),HWREGS 关键字似乎被忽略了。在函数参数上使用 restrict 关键字可以解决这个问题:

#include <stdint.h>

uint32_t * const HWREGS = 0x20000;

struct Context {
    uint32_t a,b;
} context;

static inline void exampleWithLocals(uint32_t* restrict localRegs,struct Context* restrict localContext) {
    localContext->a = 123;
    localRegs[0x1234] = 5;
    localContext->b = localContext->a;
}

void example() {
    exampleWithLocals(HWREGS,&context);
}

这是结果(另见关于 godbolt):

example:
        movabs  rax,528280977531
        mov     DWORD PTR ds:149712,5
        mov     QWORD PTR context[rip],rax
        ret
context:
        .zero   8

请注意,严格别名规则在这种情况下无济于事,因为读取/写入的变量/字段的类型始终为 uint32_t

除此之外,根据其名称,变量 HWREGS 看起来像一个硬件寄存器。请注意,它应该放在 volatile 中,这样编译器就不会将它保存到寄存器中,也不会执行任何类似的优化(例如假设如果代码不更改指向的值就保持不变)。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...