假设我们有一个指针
p
,类型为volatile int*
。假设这是一个当前有效的指针,即从 *p
读取执行实现定义的操作,并且不是 UB。
如果程序尝试评估类似
*(volatile char*)p
的内容,会发生什么?假设 int
是 4 个字节,所以这个表达式看起来像“读取 int
指向的 p
的第一个字节”。至少,如果 int
是非易失性的,它就会这样做。但在这种情况下,它是不稳定的,所以这是否使它有可能成为 UB?
对于位于常规内存中的非易失性
int
,执行 1 字节加载指令可能会在硬件级别读取所有 4 个字节,然后屏蔽其中 3 个字节。但这对于软件来说是不可见的,除了可能对性能产生影响之外。但我不确定在 4 字节内存映射寄存器的情况下是否总是“可能”发生类似的情况。是否有任何平台在执行大于 1 字节的内存映射寄存器的 1 字节加载指令时会导致错误,因为该设备根本不支持除寄存器大小之外的任何大小的 I/O ?
以下问题是相关的,但答案并没有明确解决此问题:使用 memcpy 和带有内存映射 I/O 的朋友
volatile
仅告诉编译器该对象容易产生副作用。这意味着编译器不能假设所有更改都是由可见代码路径进行的。因此,它在使用之前从其存储位置读取对象,并在进行任何更改后写入该对象。您可以访问任何对象(无论它是
volatile
还是不使用指向char
的指针都没有关系。
必须如果内存位置是内存映射寄存器,您需要按照硬件手册(数据表、参考手册等)中描述的方式访问。某些硬件不接受不同于 32 位大小的访问。您
以 32 位方式访问它。如果不这样做,硬件可能无法按预期运行(故障、重置、停顿、数据读/写不正确等)。 最简单的方法是使用临时变量。
volatile uint32_t *reg1 = (volatile uint32_t *)REG1ADDRESS;
uint8_t read_byte(int byteno)
{
uint32_t tmpreg = *reg1;
uint8_t *reg8b = (uint8_t *)&tmpreg;
return regb[byteno];
}
void write_byte(uint8_t val, int byteno)
{
uint32_t tmpreg = *reg1;
uint8_t *reg8b = (uint8_t *)&tmpreg;
regb[byteno] = val;
*reg1 = reg
}