在这种情况下,volatile限定符是否重要?

时间:2014-02-19 17:14:52

标签: c++ c volatile

我看到了一个代码示例,演示了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是否保证xDoSomeOtherStuff()的任何可能更改都可以反映在return x+1中,假设没有多线程或其他内存映射IO等非平凡的事情。因为如果保证在没有volatile的情况下工作,那么这个示例是相关的,甚至没有提到volatile的平台相关性质,正如一些评论指出的那样。但如果不能保证,那么我担心我现有的一些代码可能无法正常工作 (我可能根本不应该放第二个代码片段。)

4 个答案:

答案 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_loadatomic_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的读取都将通过内存加载来完成。