如何解决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 重新加载。这没有意义,因为 Context
与 HWREGS
位于不同的内存地址。换句话说,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
中,这样编译器就不会将它保存到寄存器中,也不会执行任何类似的优化(例如假设如果代码不更改指向的值就保持不变)。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。