InterlockedExchange() 类型函数/内在函数的 MSDN 文档不一致?

Posted

技术标签:

【中文标题】InterlockedExchange() 类型函数/内在函数的 MSDN 文档不一致?【英文标题】:Inconsistent MSDN documentation for InterlockedExchange() type functions/intrinsics? 【发布时间】:2012-06-06 10:56:26 【问题描述】:

首先,我们有 InterlockedExchange64();

http://msdn.microsoft.com/en-us/library/windows/desktop/ms683593%28v=vs.85%29.aspx

LONGLONG __cdecl InterlockedExchange64( __inout LONGLONG volatile *Target, __in LONGLONG Value );

其次,我们有编译器内在函数_InterlockedExchange64(),注意没有volatile;

http://msdn.microsoft.com/en-us/library/1s26w950%28v=vs.80%29.aspx

__int64 _InterlockedExchange64( __int64 * Target, __int64 Value );

接下来,我们有 InterlockedExchangePointer(),它和 InterlockedExchange64() 一样,使用 volatile。

http://msdn.microsoft.com/en-us/library/windows/desktop/ms683609%28v=vs.85%29.aspx

PVOID __cdecl InterlockedExchangePointer( __inout  PVOID volatile *Target, __in PVOID Value );

但是现在我们来看看指针交换的内在函数,_InterlockedExchangePointer(),这里我们看到使用了 volatile!

http://msdn.microsoft.com/en-us/library/853x471w.aspx

void * _InterlockedExchangePointer( void * volatile * Target, void * Value );

底层指令在所有情况下都是相同的,那么给出了什么?文档错误?

GCC 内部没有提到 volatile 用于交换,但他们也没有提到 CAS !所以这没有帮助。

我的观点是 CAS 目标是易变的,因为你只能在运行时知道交换是否会发生;但原子交换不应该是易变的,因为目标总是更新的(即使它的值没有改变),所以编译器没有不确定性。

我还看到 InterlockedIncrement() 的函数是 volatile 的,但 instrincs 不是。 CAS 的内在函数对其目的地具有 volatile。

【问题讨论】:

【参考方案1】:

MSDN 充斥着大多数小的文档错误(例如,__readfsdword 仅在 VS 2005 文档下被标记为内核),您真正应该注意的是编译器使用的定义,在这种情况下,定义在intrin.h(取自VS2010 Ultimate SP1):

__MACHINEI(long _InterlockedExchange(long volatile *, long)) 
__MACHINEIA64(__int64 _InterlockedExchange64(__int64 volatile *, __int64))

正如我们所见,它们确实是需要的 volatile 指针。

最后要注意的是,您的所有链接都是 VS 2005 文档(谷歌默认链接到旧的内部函数),因此请确保您使用页面顶部的下拉菜单切换到最新版本。

【讨论】:

不得不说我很困惑。为什么交易所需要一个不稳定的目标?手术的结果总是可以预测的。 @BlankXavier:IIRC 它需要一个 volatile 目标,以便它可以提供编译器级别的内存屏障,而实际的 LOCK 前缀指令提供了 CPU 级别的内存屏障。 嗯,是的 - 我想我记得 volatile 从 VS 2005(我认为)开始发生变化,因此它具有内存屏障语义,即使在 C 中也是如此。但我可能弄错了 :-) 不是指向 volatile 的指针是必需的,而是它是允许的。也就是说,如果参数被声明为 long* 而不是 long volatile*,那么传递 volatile 变量的地址会出现此错误: cannot convert argument 1 from 'volatile LONGLONG *' to 'LONGLONG *'【参考方案2】:

这些函数并不是需要指向volatile的指针,而是允许。也就是说,如果参数被声明为 long* 而不是 long volatile*,那么传递 volatile 变量的地址会出现此错误:

cannot convert argument 1 from 'volatile LONGLONG *' to 'LONGLONG *'

这可以通过这个简单的代码看出:

LONGLONG a;
volatile LONGLONG b;

void DoSomething(LONGLONG* p) 

int main() 
  DoSomething(&a);
  DoSomething(&c); // Error!
  return 0;

在 C/C++ 中有一个悠久的传统,即误用 volatile 来指示变量可能被其他线程修改。这是被误导的,因为 volatile 实际上并没有为多线程提供有意义的有用语义,但是当 C++ 不承认多线程开发人员绝望时。使用 volatile 的问题在于它不会阻止编译器或 CPU 重新排序,因此在多线程代码中 99% 的时间都是错误的。

如果没有 C++11,安全的做法是只使用 Interlocked* 函数引用这些线程共享变量。如果你这样做,那么 volatile 是不必要的。或者,像理智的人一样使用锁。

但是,由于许多开发人员使用 atomic 标记他们的线程共享变量,因此 Interlocked* 函数必须接受这些变量。这就是为什么它们都采用指向易失性指针的类型。

【讨论】:

看到问题被标记为 MS 特定的东西,你需要考虑到 VS 允许 volatile 给变量获取和释放语义(通过可能发出额外的代码;除了手臂);请参阅volatile 页面末尾的 MS 特定部分:msdn.microsoft.com/en-us/library/12a04hfd(v=vs.140).aspx True - 从 VS 2005 开始, volatile 被赋予了 MS 特定的语义。但是 VS 现在正试图回滚这些 - /volatile 关键字支持这一点,并且它的文档说“我们强烈建议您使用 /volatile:iso”所以是的,记住 volatile 的 VC++ 特定含义对于这个问题是合理的。但是,如果您将 volatile 用于多线程,则应该停止。它是一种非标准用法,它需要一整段来解释它在什么条件下有效(VC++ 2005+,如果 VC++ 2012 或更高版本在 ARM 上必须使用 /volatile:ms)。

以上是关于InterlockedExchange() 类型函数/内在函数的 MSDN 文档不一致?的主要内容,如果未能解决你的问题,请参考以下文章

找不到名为“interlockedexchange”的入口点

移植 InterlockedExchange,仅使用 GCC 内在函数

同步线程 - InterlockedExchange

InterlockedExchange Visual Studio 2010 内在

泛函编程(15)-泛函状态-随意数产生器

推断函子返回类型的通用方法?