强制读取volatile变量

时间:2015-12-30 01:21:30

标签: c language-lawyer

我正在处理的嵌入式项目需要读取内存中的特定位置,但不需要该内存位置的值。目前我正在将volatile变量读入虚拟变量,如下面的foo1()所示,但我对foo2()中的方法感到好奇。

void foo1(void) {
    volatile uint32_t *a = (volatile uint32_t *)0xdeadbeef;
    volatile uint32_t discard = *a;
}
void foo2(void) {
    volatile uint32_t *a = (volatile uint32_t *)0xdeadbeef;
    *a;
}

参见dissassembly(使用gcc 4.7.2和-O3编译):

  foo1:
movl      0xdeadbeef, %eax
movl      %eax, -0x4(%rsp)
ret
  foo2:
movl      0xdeadbeef, %eax
ret

foo2()中的方法似乎有效,但我想知道它是否有效,并且不是我正在使用的编译器版本和优化的副作用。

3 个答案:

答案 0 :(得分:2)

它是C的标准,但不是C ++。 请参阅:https://gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Volatiles.html

但是,你当然不需要对discard进行易失性写操作。这些可能都会编译为foo2:

  • 使用foo1,但从volatile移除discard限定词;或
  • 使用foo2,但使用!*a或* a + 0而不是*a。必须访问该值才能评估表达式,即使在C ++
  • 中也是如此

答案 1 :(得分:2)

关键字volatile告诉编译器一个对象可能会在正常范围之外(即编译器可见)更改。因此,编译器performs此访问一般。最后一句指的是如何进行访问,例如字节读取,未对齐读取等。

此外,编译器必须按程序流给出的顺序执行对这些对象的所有访问。但请注意,它可能会自由地重新排序对非易失性对象的访问,而底层硬件也可能会有所不同(编译器可能不知道)。

编译器可能仍然优化对volatile对象的访问,这些对象存在并仅在可见代码中进行修改。对于未采用地址的局部变量(可能存在其他情况),这是正确的,因为这些变量无法在范围之外到达。对于您使用的指针,情况并非如此,因为编译器不知道它们指向的对象。

要删除没有编译器警告的表达式的结果,只需将其强制转换为void

volatile uint32_t *vi = ...;
(void)*vi;            // this still might be optimized if vi is local

(如果目标是只读的,请添加const。)

有关volatile次访问的详细信息,请参阅gcc documentation。符合C标准的实现必须提供此信息。

另请注意,除非内存区域使用严格排序/非缓存的访问策略,否则底层硬件仍可能重新排序访问。这是存储器映射外设寄存器的典型特征。如果使用缓存/ MMU,则可能必须相应地设置区域。

答案 2 :(得分:1)

无法保证取消引用易失性对象会导致读访问,请参阅ISO 9899:2011§6.7.3¶7:

  

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

     

134)易失性声明可用于描述与存储器映射的输入/输出端口或由异步中断功能访问的对象相对应的对象。对如此声明的对象的操作不应由实现“优化”或重新排序,除非评估表达式的规则允许。

实际上,C编程语言的实现通常定义一元*来构成对象的访问,从而保证*a导致对volatile变量a的读访问权。