了解C ++中的volatile关键字

Mut*_*ect 1 c++

我试图了解volatile关键字在C ++中的工作方式。

我看了一下“易失性”在C ++中可以防止哪些优化?。查看可接受的答案,看起来像volatile禁用了两种优化

  1. 防止编译器将值缓存在寄存器中。
  2. 在您的程序的POV中似乎不必要的时候,优化对该值的访问。

我在https://en.cppreference.com/w/cpp/language/as_if找到了类似的信息。

对易失性对象的访问(读取和写入)严格按照发生它们的表达式的语义来发生。特别是,它们不会相对于同一线程上的其他易失性访问而重新排序。

我编写了一个简单的C ++程序,该程序对数组中的所有值求和,以比较Plain ints和volatile int的行为。请注意,部分和不会不稳定。

数组由不合格的ints 组成。

int foo(const std::array<int, 4>& input)
{
    auto sum = 0xD;
    for (auto element : input)
    {
        sum += element;
    }
    return sum;
}
Run Code Online (Sandbox Code Playgroud)

阵列由挥发性的int小号

int bar(const std::array<volatile int, 4>& input)
{
    auto sum = 0xD;
    for (auto element : input)
    {
        sum += element;
    }
    return sum;
}
Run Code Online (Sandbox Code Playgroud)

当我查看生成的汇编代码时,仅在纯ints的情况下使用SSE寄存器。据我所知,使用SSE寄存器的代码既没有优化读取,也没有对读取进行重新排序。循环已展开,因此也没有分支。我能解释代码源为何不同的唯一原因是:在发生累积之前,可以对易失性读进行重新排序吗?显然,sum它不是挥发性的。如果这种重新排序不好,是否存在可以说明问题的情况/示例?

使用clang9生成的代码

foo(std::array<int, 4ul> const&):                # @foo(std::array<int, 4ul> const&)
        movdqu  (%rdi), %xmm0
        pshufd  $78, %xmm0, %xmm1       # xmm1 = xmm0[2,3,0,1]
        paddd   %xmm0, %xmm1
        pshufd  $229, %xmm1, %xmm0      # xmm0 = xmm1[1,1,2,3]
        paddd   %xmm1, %xmm0
        movd    %xmm0, %eax
        addl    $13, %eax
        retq
bar(std::array<int volatile, 4ul> const&):               # @bar(std::array<int volatile, 4ul> const&)
        movl    (%rdi), %eax
        addl    4(%rdi), %eax
        addl    8(%rdi), %eax
        movl    12(%rdi), %ecx
        leal    (%rcx,%rax), %eax
        addl    $13, %eax
        retq
Run Code Online (Sandbox Code Playgroud)

sup*_*cat 5

volatileC ++中的关键字是从C继承而来的,在C语言中,该关键字被用作一般通用方法,以指示编译器应在哪些地方允许读写对象可能具有它不知道的副作用。由于不同平台上可能引起的副作用的种类会有所不同,因此该标准的问题在于,应由哪些津贴来弥补编译器作者关于如何最好地为客户服务的判断。

Microsoft的8088/8086和更高版本的x86编译器经过数十年的设计,可支持使用volatile对象来构建可保护“普通”对象的互斥体的实践。举一个简单的例子:如果线程1做类似的事情:

ordinaryObject = 23;
volatileFlag = 1;
while(volatileFlag)
  doOtherStuffWhileWaiting();
useValue(ordinaryObject);
Run Code Online (Sandbox Code Playgroud)

线程2定期执行以下操作:

if (volatileFlag)
{
  ordinaryObject++;
  volatileFlag=0;
}
Run Code Online (Sandbox Code Playgroud)

那么对的访问volatileFlag将向Microsoft的编译器发出警告,即他们应避免对任何对象上的任何先前动作与以后的动作进行交互做出假设。volatile其他语言(例如C#)中的限定符也遵循此模式。

不幸的是,clang和gcc都不包含volatile以这种方式处理的任何选项,而是要求程序员使用特定于编译器的内在函数来产生与Microsoft仅使用volatile适合于此目的的Standard关键字才能实现的语义相同的语义。[根据该标准的作者,“ volatile对象也是在多个进程之间共享的变量的适当模型。”-请参阅http://www.open-std.org/jtc1/sc22/wg14/www/C99RationaleV5 .10.pdf第 76 ll。25-26]

  • 易失性对于诸如连续读取多次映射到IO的同一个地址至关重要。可以将IO设备设置为在每次读取时按顺序的数据块进行计时,并且程序不易失,可以假定从相同地址进行的读取(未经程序修改)不会更改值。它不应用于具有相关但不相同语义的多线程。 (3认同)
  • @supercat,是的,这不是 C++11 或更高标准。它是一个早期的 C 标准,我当时为其编写了大量代码。现代 C++ 必须解决由于缓存层和多个 CPU 造成的问题,而早期的仅使用易失性的 C 和 C++ 标准不足以在不影响效率的情况下解决这些问题。海事组织是一件好事。OTOH,我从未有过使用 C++11 或更高版本和多核硬件出现竞争条件问题的代码。在这些环境中,仅靠挥发性物质并不能解决问题。至少不会像微软默认那样影响性能。 (2认同)