用 C 或 C++ 写入硬件时是否需要使用 volatile?

Is is necessary to use volatile when writing to hardware in C or C++?

在用 C 或 C++ 写入硬件(比如 FIFO)时是否有必要使用 volatile。从网上的文档很容易确认,读硬件需要volatile,但是写呢?我担心优化器可能会消除将值数组写入 FIFO 的循环,而只写入最后一个条目。

Is it necessary to use volatile when writing to hardware

通常是。

I am concerned that an optimiser could eliminate a loop to write an array of values to a FIFO, and just write the last entry.

您的担心是有道理的。给定 non-volatile 对象,优化器确实可以执行此类消除。事实上,如果它能证明写入的值永远不会被读取,那么它可能会完全消除所有写入。


这里引用 C++ 标准(最新草案):

[intro.abstract]

The semantic descriptions in this document define a parameterized nondeterministic abstract machine. This document places no requirement on the structure of conforming implementations. In particular, they need not copy or emulate the structure of the abstract machine. Rather, conforming implementations are required to emulate (only) the observable behavior of the abstract machine as explained below.

A conforming implementation executing a well-formed program shall produce the same observable behavior as one of the possible executions of the corresponding instance of the abstract machine with the same program and the same input. ...

The least requirements on a conforming implementation are:

  1. Accesses through volatile glvalues are evaluated strictly according to the rules of the abstract machine.
  2. At program termination, all data written into files shall be identical to one of the possible results that execution of the program according to the abstract semantics would have produced.
  3. The input and output dynamics of interactive devices shall take place in such a fashion that prompting output is actually delivered before a program waits for input.

What constitutes an interactive device is implementation-defined.

These collectively are referred to as the observable behavior of the program.

是的,你必须使用 volatile

来自 C11 标准,5.1.2.3 程序执行 - 第 4 段:

In the abstract machine, all expressions are evaluated as specified by the semantics. An actual implementation need not evaluate part of an expression if it can deduce that its value is not used and that no needed side effects are produced ....

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

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

A volatile declaration may be used to describe an object corresponding to a memory-mapped input/output port or an object accessed by an asynchronously interrupting function. Actions on objects so declared shall not be ‘‘optimized out’’ by an implementation or reordered except as permitted by the rules for evaluating expressions.

如果您正在使用兼容的 C 编译器,那么您假设在写入 memory-mapped 硬件时需要使用 volatile 是正确的。


根据您正在使用的特定机器和编译器,volatile volatile 的用法最多可能是多​​余的:

An implementation might define a one-to-one correspondence between abstract and actual semantics: at every sequence point, the values of the actual objects would agree with those specified by the abstract semantics. The keyword volatile would then be redundant.


根据 C++,according to the latest draft:

Note 5: volatile is a hint to the implementation to avoid aggressive optimization involving the object because the value of the object might be changed by means undetectable by an implementation. Furthermore, for some implementations, volatile might indicate that special hardware instructions are required to access the object. See [intro.execution] for detailed semantics. In general, the semantics of volatile are intended to be the same in C++ as they are in C. — end note

试试吧。

#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,可以优化写入。编译器决定如何以及何时决定这样做可能会有所不同。但假设它会发生并因此给您带来麻烦。