C 如果volatile对于线程来说是无用的,那么为什么原子操作需要指向volatile数据的指针呢?

C 如果volatile对于线程来说是无用的,那么为什么原子操作需要指向volatile数据的指针呢?,c,atomic,volatile,C,Atomic,Volatile,我从很多资料中都读到了这一点。然而,这个断言经常受到接受volatile指针的原子操作函数的挑战 例如,在Mac OS X上,我们有OSAtomic函数系列: SInt32 OSIncrementAtomic(volatile SInt32 *address); SInt32 OSDrecrementAtomic(volatile SInt32 *address); SInt32 OSAddAtomic(SInt32 amount, volatile SInt32 *address); // .

我从很多资料中都读到了这一点。然而,这个断言经常受到接受
volatile
指针的原子操作函数的挑战

例如,在Mac OS X上,我们有
OSAtomic
函数系列:

SInt32 OSIncrementAtomic(volatile SInt32 *address);
SInt32 OSDrecrementAtomic(volatile SInt32 *address);
SInt32 OSAddAtomic(SInt32 amount, volatile SInt32 *address);
// ...
对于
联锁
操作,Windows上似乎也有类似的
volatile
关键字用法:

LONG __cdecl InterlockedIncrement(__inout LONG volatile *Addend);
LONG __cdecl InterlockedDecrement(__inout LONG volatile *Addend);
在C++11中,原子类型似乎有带有
volatile
修饰符的方法,这一定意味着
volatile
关键字与原子性有某种关系


那么,我错过了什么?为什么操作系统供应商和标准库设计人员坚持使用
volatile
关键字进行线程处理,如果它没有用处的话?

那么,关键字“volatile”确保编译器每次变量出现在代码中时都从内存加载/存储变量值。
这可以防止某些优化,例如,只需将值加载到寄存器中一次,然后多次使用。
当您有多个线程可以修改线程之间的“共享”变量时,它非常有用。您必须确保始终从内存加载/存储该值,以便检查它的值是否可以被其他线程修改。如果未使用volatile,则另一个线程可能没有将新值写入内存(但将其放入寄存器或可能进行了其他类型的优化),并且第一个线程不会注意到值的任何更改


在您的例子中,“volatile SInt32*address”告诉编译器地址指向的内存可能会被任何源更改。因此需要一个原子操作。

Volatile对于多线程共享访问并不是无用的,只是它不一定足够:

  • 它不一定提供可能需要的内存障碍语义
  • 它不提供原子访问的保证(例如,如果易失性对象大于平台的本机内存字大小)
此外,您还应该注意到,示例中指向API的指针参数上的
volatile
限定符实际上只增加了API接收指向
volatile
对象的指针的能力,而无需抱怨-它不要求指针指向实际的
volatile
对象。该标准允许将非限定指针自动转换为限定指针。标准中没有提供自动转向(合格指针指向非合格指针)(编译器通常允许,但会发出警告)

例如,如果
InterlockedIncrement()
的原型为:

LONG __cdecl InterlockedIncrement(__inout LONG *Addend);  // not `volatile*`
API仍然可以实现,以便在内部正常工作。但是,如果用户有一个要传递给API的易失性对象,则需要强制转换来防止编译器抛出警告


由于(无论是否必要),这些API通常与
volatile
限定对象一起使用,因此在指针参数中添加
volatile
限定符可防止在使用API时生成无用的诊断,当API与指向非易失性对象的指针一起使用时,不会造成任何伤害。

我突然意识到,我只是误解了
易失性*
的含义。与
const*
表示指针对象不应更改类似,
volatile*
表示指针对象不应缓存在寄存器中。这是一个可以自由添加的附加约束:您可以将
char*
强制转换为
const char*
,也可以将
int*
强制转换为
volatile int*


因此,对指针对象应用
volatile
修饰符可以确保原子函数可以用于已经存在的
volatile
变量。对于非易失性变量,添加限定符是免费的。我的错误是将关键字在原型中的存在理解为使用它的动机,而不是为使用它的人提供便利。

C++11对
volatile
和非
volatile
变量都有原子


如果编译器内部函数采用指向
volatile int
的指针,这意味着您可以使用它,即使该变量是volatile的。它不会阻止您在非易失性数据上使用函数。

不是易失性没有用处,更重要的是易失性数据不能保证原子操作不是完全重复,但与您的答案重叠,这与Intel的线程构建块架构师几乎直接矛盾,我在我的问题中链接到它。@zneak实际上没有。我从来没有提到“代码重新排序”会被阻止。将'SInt32*address'标记为'volatile'很有意义,因为它可能会受到任何外部影响而更改。为了保持原子性,原子操作需要处理这个问题。因此volatile是标记此行为的正确方法。@zneak:这只是巧合-我还没有阅读其他答案。它还使
volatile
非常有用,可以防止您意外地通过引用一个不支持线程的函数来传递共享变量。