考虑这个C代码:
extern volatile int hardware_reg;
void f(const void *src, size_t len)
{
void *dst = <something>;
hardware_reg = 1;
memcpy(dst, src, len);
hardware_reg = 0;
}
Run Code Online (Sandbox Code Playgroud)
该memcpy()呼叫必须在两个任务之间发生.通常,由于编译器可能不知道被调用函数将执行什么操作,因此它无法将对函数的调用重新排序为在赋值之前或之后.但是,在这种情况下,编译器知道函数将执行什么操作(甚至可以插入内联内置替换),并且它可以推断出memcpy()永远不能访问的内容hardware_reg.在我看来,编译器在移动memcpy()调用时会遇到麻烦,如果它想这样做的话.
所以,问题是:单独的函数调用是否足以发出阻止重新排序的内存屏障,或者在这种情况下,在调用之前和之后需要显式内存屏障memcpy()?
如果我误解了事情,请纠正我.
Mic*_*urr 11
编译器无法在memcpy()之前hardware_reg = 1或之后重新排序操作hardware_reg = 0- 这volatile将确保 - 至少就编译器发出的指令流而言.函数调用不一定是"内存屏障",但它是一个序列点.
C99标准说明了这一点volatile(5.1.2.3/5"程序执行"):
在序列点处,易失性对象在先前访问完成且后续访问尚未发生的意义上是稳定的.
因此,在由此表示的序列点处memcpy(),必须发生1写入的易失性访问,0并且不能发生写入的易失性访问.
但是,有两件事我想指出:
根据具体<something>情况,如果目标缓冲区没有其他任何操作,编译器可能能够完全删除该memcpy()操作.这就是微软推出该SecureZeroMemory()功能的原因.SecureZeroMemory()对volatile合格的指针进行操作以防止优化写入.
volatile并不一定意味着内存屏障(这是一个硬件的东西,而不仅仅是代码排序的东西),所以如果你在多进程机器或某些类型的硬件上运行,你可能需要显式调用内存屏障(也许wmb()在Linux上).
从MSVC 8(VS 2005)开始,Microsoft记录该volatile关键字暗示了适当的内存屏障,因此可能不需要单独的特定内存屏障调用:
此外,在优化时,编译器必须维护对易失性对象的引用之间的顺序以及对其他全局对象的引用.特别是,
对volatile对象的写入(volatile write)具有Release语义; 对在指令序列中写入易失性对象之前发生的全局或静态对象的引用将在编译二进制文件中的易失性写入之前发生.
读取volatile对象(volatile read)具有Acquire语义; 在读取编译二进制文件中的易失性读取之后,将在读取指令序列中的易失性存储器之后发生对全局或静态对象的引用.