控制C语言中内存映射寄存器的读写访问宽度

控制C语言中内存映射寄存器的读写访问宽度,c,embedded,compiler-optimization,memory-mapping,C,Embedded,Compiler Optimization,Memory Mapping,我正在使用和基于x86的内核来操作32位内存映射寄存器。只有当CPU生成32位宽的读写到此寄存器时,我的硬件才能正常工作。寄存器在32位地址上对齐,在字节粒度上不可寻址 如何确保我的C(或C99)编译器在所有情况下都只生成完整的32位宽读写 例如,如果我执行如下读取-修改-写入操作: volatile uint32_t* p_reg = 0xCAFE0000; *p_reg |= 0x01; 我不想让编译器知道只有底部字节改变并生成8位宽的读/写操作。由于x86上的8位操作的机器代码通常更密集

我正在使用和基于x86的内核来操作32位内存映射寄存器。只有当CPU生成32位宽的读写到此寄存器时,我的硬件才能正常工作。寄存器在32位地址上对齐,在字节粒度上不可寻址

如何确保我的C(或C99)编译器在所有情况下都只生成完整的32位宽读写

例如,如果我执行如下读取-修改-写入操作:

volatile uint32_t* p_reg = 0xCAFE0000;
*p_reg |= 0x01;
我不想让编译器知道只有底部字节改变并生成8位宽的读/写操作。由于x86上的8位操作的机器代码通常更密集,因此我担心不必要的优化。通常禁用优化不是一个选项

-----编辑-------

一篇有趣且非常相关的论文:

一般来说,如果将寄存器键入为32位易失性,我不希望它优化出高阶字节。由于使用volatile关键字,编译器无法假定高阶字节中的值为0x00。因此,即使只使用8位的文字值,它也必须写入完整的32位。我从未在0x86或Ti处理器或其他嵌入式处理器上遇到过此问题。一般来说,volatile关键字就足够了。唯一让事情变得有点奇怪的是,如果处理器本机不支持您尝试写入的字大小,但对于32位数字,这在0x86上不应该是一个问题

虽然编译器可以生成使用4位写入的指令流,但在单个32位写入上,这不是处理器时间或指令空间的优化。

如果在访问硬件时不使用字节(无符号字符)类型,编译器更有可能不生成8位数据传输指令

volatile uint32_t* p_reg = 0xCAFE0000;
const uint32_t value = 0x01;  // This trick tells the compiler the constant is 32 bits.
*p_reg |= value;
您必须将端口读取为32位值,修改该值,然后写回:

uint32_t reg_value = *p_reg;
reg_value |= 0x01;
*p_reg = reg_value;

保证编译器正确运行的唯一方法是在汇编程序中编写加载和存储例程,并从C中调用它们。我多年来使用的编译器中,100%都会出错(包括GCC)

有时候,优化器会让你,例如,你想把编译器认为是一个小数字0x10的常数存储到一个32位寄存器中,这是你特别要求的,我也看到过好的编译器试图做的事情。一些编译器会认为8位写入比32位写入更便宜,并更改指令。可变指令长度目标会使情况变得更糟,因为编译器试图节省程序空间,而不仅仅是在它可能假定的总线上节省内存周期。(xor ax,ax代替mov eax,例如0)

对于像gcc这样不断发展的东西,今天运行的代码不能保证明天运行(你甚至不能用当前版本的gcc编译某些版本的gcc)。同样地,在你办公桌上的编译器上运行的代码可能并不适用于其他编译器

去掉猜测和实验,创建加载和存储函数


这样做的另一个好处是,如果/当您希望以某种方式模拟代码,或者让代码在应用程序空间而不是金属上运行,或者反之亦然,您可以创建一个很好的抽象层,汇编程序功能可以用模拟目标替换,也可以用跨网络到目标(设备在目标上)的代码替换,等等。

volatile限定符涵盖了您的问题

6.7.3/6“类型限定符”表示:

具有volatile限定类型的对象可能会以实现未知的方式进行修改,或者具有其他未知的副作用。因此,应严格按照5.1.2.3中所述的抽象机器规则对涉及此类对象的任何表达式进行评估。此外,在每个序列点上,最后存储在对象中的值应与抽象机器规定的值一致,除非由前面提到的未知因素修改。对具有volatile限定类型的对象的访问由实现定义

5.1.2.3“程序执行”包括以下内容:

在抽象机器中,所有表达式都按照语义指定的方式进行计算

这之后是一个通常被称为“仿佛”规则的句子,如果最终结果相同,该规则允许实现不遵循抽象机器语义:

如果实际实现可以推断表达式的值没有被使用,并且没有产生所需的副作用(包括调用函数或访问易失性对象引起的副作用),则不需要计算表达式的一部分


但是,6.7.3/6本质上说,表达式中使用的volatile限定类型不能应用“仿佛”规则——必须遵循实际的抽象机器语义。因此,如果指向易失性32位类型的指针被取消引用,则必须读取或写入完整的32位值(取决于操作)。

因为针对硬件的读-修改-写操作在多个指令中总是存在巨大的风险,大多数处理器提供一条指令,用一条不可中断的指令操作寄存器/内存

根据您正在操作的寄存器类型,它可能在修改阶段发生更改,然后您将写回一个假值

如dwelch所建议的,我建议您在汇编中编写自己的读-修改-写函数(如果这很关键)

我从来没有听说过一个编译器会优化一个类型(为了优化而进行类型转换)。如果声明为int32,则它始终是int32,并且将始终是int32