我正在处理的嵌入式项目需要读取内存中的特定位置,但不需要该内存位置的值。目前我正在将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()
中的方法似乎有效,但我想知道它是否有效,并且不是我正在使用的编译器版本和优化的副作用。
答案 0 :(得分:2)
它是C的标准,但不是C ++。 请参阅:https://gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Volatiles.html
但是,你当然不需要对discard
进行易失性写操作。这些可能都会编译为foo2:
volatile
移除discard
限定词;或!*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
的读访问权。