考虑这个 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;
}
memcpy()
调用必须发生在两个赋值之间。一般来说,由于编译器可能不知道被调用的函数会做什么,它无法将对函数的调用重新排序为在赋值之前或之后。然而,在这种情况下,编译器知道该函数将做什么(甚至可以插入一个内联内置替代项),并且它可以推断出 memcpy()
永远无法访问 hardware_reg
。在我看来,编译器在移动 memcpy()
调用时不会遇到任何问题,如果它想这样做的话。
所以,问题是:单独的函数调用是否足以发出一个内存屏障来防止重新排序,或者,在这种情况下,在调用 memcpy()< 之前和之后是否需要显式内存屏障
?
如果我误解了什么,请纠正我。
最佳答案
编译器无法重新排序 memcpy()
hardware_reg = 1
之前的操作或者在 hardware_reg = 0
之后- 就是这样 volatile
将确保——至少就编译器发出的指令流而言。函数调用不一定是“内存屏障”,但它是一个序列点。
C99 标准是关于 volatile
的(5.1.2.3/5 "程序执行"):
At sequence points, volatile objects are stable in the sense that previous accesses are complete and subsequent accesses have not yet occurred.
所以在 memcpy()
表示的序列点, volatile 访问 1
必须发生,并且写入的 volatile 访问0
不可能发生。
不过,有两点我想指出:
取决于什么
<something>
也就是说,如果目标缓冲区没有做任何其他事情,编译器可能能够完全删除memcpy()
手术。这就是微软提出SecureZeroMemory()
的原因功能。SecureZeroMemory()
在volatile
上运行防止优化写入的合格指针。volatile
不一定意味着内存屏障(这是硬件的东西,而不仅仅是代码排序的东西),所以如果你在多进程机器或某些类型的硬件上运行,你可能需要显式调用内存屏障(在 Linux 上可能是wmb()
)。从 MSVC 8 (VS 2005) 开始,Microsoft 记录了
volatile
关键字暗示适当的内存屏障,因此可能不需要单独的特定内存屏障调用:Also, when optimizing, the compiler must maintain ordering among references to volatile objects as well as references to other global objects. In particular,
A write to a volatile object (volatile write) has Release semantics; a reference to a global or static object that occurs before a write to a volatile object in the instruction sequence will occur before that volatile write in the compiled binary.
A read of a volatile object (volatile read) has Acquire semantics; a reference to a global or static object that occurs after a read of volatile memory in the instruction sequence will occur after that volatile read in the compiled binary.
关于c - 函数调用是内存屏障吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5693274/