在使用C或C++编写与硬件交互的代码时,是否有必要使用volatile关键字?

16

在C或C++中,当写入硬件(如FIFO)时,是否有必要使用volatile?从在线文档中可以轻松确认,在读取硬件时使用volatile是必要的,但写入时呢?我担心优化器可能会消除一个循环来向FIFO写入一组值,并且只写入最后一个条目。


当你说“写入硬件”时,你是指像MMIO这样的东西吗?也就是说,它总是在相同的地址上?这就是为什么你担心连续的写操作可能会被优化为只有最后一个的原因吗? - Marco Bonelli
1
是的,这是必要的。否则编译器可能会优化掉所有内容,甚至不保留最后一次写入。 - 12431234123412341234123
@MarcoBonelli:我确实是指向FPGA中的单个地址写入,这就像一个信箱。我将连续的值写入此地址,FPGA会解释并执行相应操作。许多其他微处理器连接的I/O芯片也是以同样的方式工作,现代处理器和DSP中集成了许多外围设备也是如此。 - Nigel Davies
你测试过了吗?我认为当你使用-O3且没有volatile时,gcc会删除除最后一个写入之外的所有写入,当你在循环中写入相同地址并且不从中读取时。编译器允许这样做,但我不能百分之百确定gcc是否这样做。 - 12431234123412341234123
C11 5.1.2.3 程序执行 - 4:_在抽象机器中,所有表达式都按照语义规定进行评估。如果实际的实现可以推断出其值未被使用且不会产生所需的副作用,则无需评估表达式的部分内容。_编译器假定当您不使用volatile时不会产生副作用。 - 12431234123412341234123
显示剩余4条评论
4个回答

12

试一试。

#define MYFIFOV (*((volatile unsigned char *)0x1000000))
#define MYFIFO (*((unsigned char *)0x1000000))

void funv ( void )
{
    MYFIFOV=0;
    MYFIFOV=0;
}
void fun ( void )
{
    MYFIFO=0;
    MYFIFO=0;
}
00000000 <funv>:
   0:   e3a03401    mov r3, #16777216   ; 0x1000000
   4:   e3a02000    mov r2, #0
   8:   e5c32000    strb    r2, [r3]
   c:   e5c32000    strb    r2, [r3]
  10:   e12fff1e    bx  lr

00000014 <fun>:
  14:   e3a03401    mov r3, #16777216   ; 0x1000000
  18:   e3a02000    mov r2, #0
  1c:   e5c32000    strb    r2, [r3]
  20:   e12fff1e    bx  lr

strb代表存储字节。如果没有使用volatile,其中一个写操作可能会被优化掉。因此,没有使用volatile,写操作可能会被优化掉。编译器如何决定何时进行优化可能会有所不同。但是请假设它可能会发生,并因此引起问题。


4
在与硬件通信时,通常需要使用volatile关键字。您的担忧是合理的。如果对象不是volatile,则优化器可能会执行这种循环消除,并仅写入最后一个条目。实际上,如果可以证明写入的值永远不会被读取,则它可能会完全消除所有写入。
这里是来自C ++标准的一段引用(最新草案):
引言: 此文档中的语义描述定义了参数化的非确定性抽象机。 本文件对符合要求的实现结构没有要求。 特别是,它们不需要复制或模拟抽象机的结构。 相反,符合要求的实现需要模拟抽象机的可观察行为(仅限于下面解释的内容)。
符合要求的执行良好程序的实现应产生与具有相同程序和相同输入的相应抽象机实例的可能执行之一相同的可观察行为。
最少要求符合要求的执行良好程序的实现是: 1.通过volatile glvalues访问按照抽象机的规则严格评估。 2.在程序终止时,写入文件的所有数据都必须与根据抽象语义执行程序的可能结果之一相同。 3.交互式设备的输入和输出动态应以这样的方式进行,即在程序等待输入之前实际传递提示输出。 构成交互式设备的内容由实现定义。 这些合称为程序的可观察行为。

3

是的,你必须使用volatile

根据C11标准,5.1.2.3程序执行-第4段:

在抽象机器中,所有表达式都按语义规定进行评估。实际实现不需要评估表达式的一部分,如果它可以推断出其值未被使用且不产生所需的副作用....

当你不使用volatile时,编译器可能会假设不会有有用的副作用,并删除写入操作。


1
根据C标准(C99 §6.7.3 脚注106,第109页here):

volatile声明可以用于描述与内存映射的输入/输出端口相对应或由异步中断函数访问的对象。 因此声明的对象上的操作不得被实现“优化掉”或重新排序,除非符合评估表达式的规则。

如果您正在使用符合标准的C编译器,则可以正确地认为在写入内存映射硬件时使用volatile是必要的。

根据您所使用的具体机器和编译器,volatile 的使用可能最多是多余的:

一个实现可以定义抽象语义与实际语义之间的一一对应关系:在每个序列点上,实际对象的值将与抽象语义指定的值相符。此时,关键字 volatile 将是多余的。


根据C++,根据最新的草案

注意5:volatile是对实现的提示,避免涉及该对象的激进优化,因为该对象的值可能会被不可检测的手段更改。此外,对于某些实现,volatile可能表示需要特殊的硬件指令才能访问该对象。有关详细语义,请参见[intro.execution]。总的来说,在C++中,volatile的语义旨在与C中相同。 —注解


请注意,注释不是规范性的,技术上与它们相矛盾并不会使语言实现非符合性。 - eerorika

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接