我看到了一个代码示例,演示了C++ volatile member functions问题答案中volatile
限定词的用法,引用如下:
volatile int x;
int DoSomething() {
x = 1;
DoSomeOtherStuff();
return x+1; // Don't just return 2 because we stored a 1 in x.
// Check to get its current value
}
我不知道volatile
限定符是否与上述代码有任何区别。 x
是一个全局变量,x
上的写入和读取之间存在函数调用,我们只读取x
一次。编译器不应该在x
上执行真正的读取(即使它不是volatile
)吗?
我认为这与以下案例不同:
volatile int x;
int DoSomething() {
x = 1;
while (x != 1)
break;
}
在这种情况下,我们在写入x
后立即反复阅读x
,因此需要volatile
才能获得x
写的最新值通过其他线程。
我对这些代码示例的理解不太自信,如果我错了,请纠正我。
编辑(回复评论):对不起,我没有说清楚我的问题。至于第一个代码段,我正在质疑代码是否是可能volatile
使用的有效示例(而不是保证使用volatile) 。我只是想知道,volatile
是否保证x
中DoSomeOtherStuff()
的任何可能更改都可以反映在return x+1
中,假设没有多线程或其他内存映射IO等非平凡的事情。因为如果保证在没有volatile
的情况下工作,那么这个示例是相关的,甚至没有提到volatile
的平台相关性质,正如一些评论指出的那样。但如果不能保证,那么我担心我现有的一些代码可能无法正常工作
(我可能根本不应该放第二个代码片段。)
答案 0 :(得分:3)
volatile
限定符从不对意义产生影响
代码本身。除非编译器能够证明这一点
DoSomeOtherStuff()
不会修改x
,必须重新阅读x
无论如何,volatile
或不。要使volatile
具有相关性,
x
必须像内存映射IO一样
可能会在计划之外改变。如果我们想象它是
一个寄存器,每微秒递增一次,例如:
int
MeasureExecutionTime()
{
x = 0;
DoSomeOtherStuff();
return x;
}
将返回DoSomeOtherStuff
中使用的时间量;该
编译器将被要求重新加载它,即使它是内联的
DoSomeOtherStuff
,看到它从未修改过x
。
当然,在典型的台式机上,可能没有
任何内存映射IO,如果有,它在受保护的内存中,
你无法访问它的地方。许多编译器并不是真的
无论如何,生成使其正常工作所需的代码。
因此,对于这种机器上的通用应用程序,有
每次使用volatile
都没有意义。
编辑:
关于您的第二个代码段:通常实现,
volatile
并不保证您获得最新的
x
。可以说,这不符合的意图
volatile
,但这是g ++,Sun CC和至少一些方式
VC ++版本的工作。编译器将发出一个加载
每次在循环中读取x
的指令,但硬件
可能会找到管道中已有的值,而不会传播
读取请求到内存总线。为了保证
一个新的读取,编译器将不得不插入一个围栏或
一个膜指令。
也许更重要的是(迟早会有事情发生 发生这样,以便该值不会在管道中),这种循环 机制将用于等待其他值写入 其他线程已稳定下来。除了挥发性没有 影响其他变量的读写时间。
要了解volatile
,了解这一点非常重要
引入它的意图。什么时候介绍,
内存管道等未知,以及(C)标准
忽略线程(或具有共享内存的多个进程)。
volatile
的目的是允许支持内存映射
IO,写入地址有外部后果,和
连续读取并不总是读取相同的东西。有
从来没有意图代码中的其他变量
同步。在线程之间进行通信时,通常是这样
读取和写入的顺序,所有共享变量
很重要如果我这样做:
globalPointer = new Xxx;
和其他线程可以访问globalPointer
,这很重要
Xxx
的构造函数中的所有写入都已成为
在globalPointer
的值发生变化之前可见
发生。为此,不仅globalPointer
会有。{1}}
是volatile
,也是Xxx
的所有成员,以及任何成员
Xxx
的变量成员函数可能使用或任何数据
可通过Xxx
中的指针访问。这根本不是
合理;你很快就会得到一切
计划volatile
。即便如此,它也需要这样做
编译器正确实现volatile
,断言围栏或
每个访问周围的membar指令。 (FWIW:围栏或
membar指令可以倍增内存所需的时间
访问量为10或更多。)
这里的解决方案不是易变的,而是要访问
指针(并且只访问指针)原子,使用
{C} 11添加了atomic_load
和atomic_store
个原语。
这些原语确实会导致必要的栅栏或膜
使用说明;他们还告诉编译器不要移动
任何内存访问它们。所以使用atomic_load
来
设置上面的指针,将导致所有先前的内存写入
在写入指针之前对其他线程可见
变得可见(在阅读线程使用的条件下)
当然,atomic_read
- atomic_write
可以确保所有人
以前的写入在所有的“共同”存储器中可用
线程和atomic_read
确保所有后续读取
会去“共同”的记忆,而不是拿起一些价值
已经在管道中。)
答案 1 :(得分:0)
编译器可以获得有关DoSomeOtherStuff()
函数的一些信息。例如:
static int DoSomeOtherStuff()
{
return 42;
}
volatile int x;
int DoSomething() {
x = 1;
DoSomeOtherStuff();
return x+1; // Don't just return 2 because we stored a 1 in x.
// Check to get its current value
}
带有-O3
选项的GCC完全删除了DoSomeOtherStuff()
函数(及其正文)的调用,但最后仍重新加载x
以返回x+1
。
答案 2 :(得分:0)
这是关于volatile的现有SO问题: What is the use of volatile keyword?
只有第一个代码片段是正确的,没有明显的理由让x声明为volatile。但是,如果您了解该关键字应该用于什么,您可以推断x是易失性的,因为此代码之外存在某些内容,这可能会改变它的值。例如,存储器可以附加到一些其他硬件或由另一个程序写入。因此,程序员正在指示编译器无法看到x的值发生变化的所有可能方式。因此,编译器可能无法以某种方式优化代码。
第二个代码片段本身并不需要volatile关键字。 Volatile用作编译器提示,指出内存可能会因当前程序之外的力而改变。它不应该用于线程通信。有些新的C ++类型应该用于这些情况。
我建议阅读Herb Sutter的this article以及this talk。
答案 3 :(得分:0)
这里真正确定的唯一方法是检查为目标平台生成的汇编代码。如果volatile以你想要的方式执行,那么任何对变量x的读取都将通过内存加载来完成。