强制执行C语句的顺序?

我遇到了MS C编译器重新排序某些语句的问题,这些语句在multithreading上下文中非常重要,并且处于高优化级别。 我想知道如何在特定的地方强制订购,同时仍然使用高水平的优化。 (在低优化级别,此编译器不重新排序语句)

以下代码:

ChunkT* plog2sizeChunk=... SET_BUSY(plog2sizeChunk->pPoolAndBusyFlag); // set "busy" bit on this chunk of storage x = plog2sizeChunk->pNext; 

产生这个:

  0040130F 8B 5A 08 mov ebx,dword ptr [edx+8] 00401312 83 22 FE and dword ptr [edx],0FFFFFFFEh 

其中pPoolAndBusyFlag的写入由编译器重新排序,在pNext fetch 之后发生。

SET_BUSY本质上是

  plog2sizeChunk->pPoolAndBusyFlag&=0xFFFFFFFeh; 

我认为编译器已经正确地决定重新排序这些访问是正确的,因为它们是同一结构的两个独立成员,并且这种重新排序对单线程执行的结果没有影响:

 typedef struct chunk_tag{ unsigned pPoolAndBusyFlag; // Contains pointer to owning pool and a busy flag natural log2size; // holds log2size of the chunk if Busy==false struct chunk_tag* pNext; // holds pointer to next block of same size struct chunk_tag* pPrev; // holds pointer to previous block of same size } ChunkT, *pChunkT; 

出于我的目的,必须在对此结构的其他访问在multithreading/多核上下文中有效之前设置pPoolAndBusyFlag。 我不认为这种特殊访问对我来说是有问题的,但编译器可以重新排序这一事实意味着我的代码的其他部分可能具有相同类型的重新排序,但在这些地方它可能是关键的。 (想象一下,这两个语句是对两个成员的更新,而不是一个写入/一个读取)。 我希望能够强制执行动作的顺序。

理想情况下,我会写一些类似于:

  plog2sizeChunk->pPoolAndBusyFlag&=0xFFFFFFFeh; #pragma no-reordering // no such directive appears to exist pNext = plog2sizeChunk->pNext; 

我已经通过实验validation我可以用这种丑陋的方式获得这种效果:

  plog2sizeChunk->pPoolAndBusyFlag&=0xFFFFFFFeh; asm { xor eax, eax } // compiler won't optimize past asm block pNext = plog2sizeChunk->pNext; 

  0040130F 83 22 FE and dword ptr [edx],0FFFFFFFEh 00401312 33 C0 xor eax,eax 00401314 8B 5A 08 mov ebx,dword ptr [edx+8] 

我注意到x86硬件可能会重新排序这些特定的指令,因为它们没有引用相同的内存位置,并且读取可能会通过写入; 要真正修复这个例子,我需要某种类型的内存屏障。 回到我之前的评论,如果它们都是写入,x86将不会重新排序它们,并且其他线程将按顺序看到写入顺序。 因此,在这种情况下,我认为我不需要内存屏障,只需强制订购。

我还没有看到编译器重新订购两个写(但是)但是我还没有看起来很难(还); 我只是绊倒了这个。 当然,仅仅因为你没有在这个编译中看到它而进行优化并不意味着它不会出现在下一个编译中。

那么,我如何强制编译器对它们进行排序呢?

我知道我可以声明结构中的内存槽是volatile。 它们仍然是独立的存储位置,因此我不知道这会如何阻止优化。 也许我错误地解释了挥发性意味着什么?

编辑(10月20日):感谢所有响应者。 我当前的实现使用volatile(用作初始解决方案),_ ReadWriteBarrier(用于标记编译器不应该发生重新排序的代码),以及一些MemoryBarriers(其中发生读写),这似乎解决了问题。

编辑:(11月2日):为了清洁,我最终为ReadBarrier,WriteBarrier和ReadWriteBarrier定义了一组宏。 有锁定前后锁定,解锁前后解锁以及一般用法。 其中一些是空的,一些包含_ReadWriteBarrier和MemoryBarrier,适用于x86和基于XCHG的典型自旋锁[XCHG包含一个隐式的MemoryBarrier,从而避免了锁定前/后集合中的需要)。 然后我将这些文件停放在适当的代码中,记录了基本(非)重新排序的要求。

据我所知, pNext = plog2sizeChunk->pNext发布块,以便其他线程可以看到它,你必须确保它们看到正确的忙标志。

这意味着发布它之前你需要一个单向的内存屏障(在另一个线程中读取它之前也是一个,尽管如果你的代码在x86上运行你可以免费获得它们)以确保线程实际上看到了这个变化。 在写入之前还需要一个,以避免在它之后重新排序写入。 不只是插入程序集或使用标准兼容的volatile(MSVC volatile提供了额外的保证,虽然这在这里有所作为)是不够的 – 是的,这会阻止编译器转移读取和写入,但CPU不受它的限制,可以做内部相同的重新排序。

MSVC和gcc都有内在函数/宏来创建内存屏障( 参见此处 )。 MSVC还为对您的问题有足够好的挥发物提供更强的保证。 最后C ++ 11primefaces也会起作用,但我不确定C本身是否有任何可移植的方法来保证内存障碍。

请参阅_ReadWriteBarrier 。 这是一个致力于您正在寻找的编译器内在。 请务必根据MSVC的精确版本检查文档(VS2012上的“已弃用”…)。 小心cpu重新排序(然后看看MemoryBarrier

该文档指出 _ReadBarrier,_WriteBarrier和_ReadWriteBarrier编译器内在函数(编译器重新排序)以及MemoryBarrier宏(CPU重新排序)都是从VS2012开始“弃用”的。 但我认为他们将继续工作一段时间……

新代码可能使用新的C ++ 11工具(MSDN页面中的链接)

我会使用volatile关键字。 它将阻止编译器重新排序指令。 http://www.barrgroup.com/Embedded-Systems/How-To/C-Volatile-Keyword