在C或C++中,当写入硬件(如FIFO)时,是否有必要使用volatile
?从在线文档中可以轻松确认,在读取硬件时使用volatile
是必要的,但写入时呢?我担心优化器可能会消除一个循环来向FIFO写入一组值,并且只写入最后一个条目。
试一试。
#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,写操作可能会被优化掉。编译器如何决定何时进行优化可能会有所不同。但是请假设它可能会发生,并因此引起问题。
是的,你必须使用volatile
。
根据C11标准,5.1.2.3程序执行-第4段:
在抽象机器中,所有表达式都按语义规定进行评估。实际实现不需要评估表达式的一部分,如果它可以推断出其值未被使用且不产生所需的副作用....
当你不使用volatile
时,编译器可能会假设不会有有用的副作用,并删除写入操作。
如果您正在使用符合标准的C编译器,则可以正确地认为在写入内存映射硬件时使用
volatile
声明可以用于描述与内存映射的输入/输出端口相对应或由异步中断函数访问的对象。 因此声明的对象上的操作不得被实现“优化掉”或重新排序,除非符合评估表达式的规则。
volatile
是必要的。
根据您所使用的具体机器和编译器,volatile
的使用可能最多是多余的:
一个实现可以定义抽象语义与实际语义之间的一一对应关系:在每个序列点上,实际对象的值将与抽象语义指定的值相符。此时,关键字
volatile
将是多余的。
注意5:
volatile
是对实现的提示,避免涉及该对象的激进优化,因为该对象的值可能会被不可检测的手段更改。此外,对于某些实现,volatile
可能表示需要特殊的硬件指令才能访问该对象。有关详细语义,请参见[intro.execution]。总的来说,在C++中,volatile
的语义旨在与C中相同。 —注解
-O3
且没有volatile
时,gcc会删除除最后一个写入之外的所有写入,当你在循环中写入相同地址并且不从中读取时。编译器允许这样做,但我不能百分之百确定gcc是否这样做。 - 12431234123412341234123