理解C++中的'volatile'关键字

vbopmzt1  于 2023-06-25  发布在  其他
关注(0)|答案(1)|浏览(214)

我正在尝试理解volatile关键字在C中是如何工作的。
我看了一下 * What kinds of optimizations does 'volatile' prevent in C++? *。查看已接受的答案,看起来volatile禁用了两种优化
1.防止编译器缓存寄存器中的值。
1.当从程序的Angular 看不需要访问该值时,进行优化。
我在 * The as-if rule * 找到了类似的信息:
对volatile对象的访问(读和写)严格按照它们所在的表达式的语义进行。特别地,它们不相对于同一线程上的其他易失性访问被重新排序。
我写了一个简单的C
程序,它对数组中的所有值求和,以比较普通int s与int s的行为。挥发性的注意,部分和不是易变的。
数组由非限定的int组成。

  1. int foo(const std::array<int, 4>& input)
  2. {
  3. auto sum = 0xD;
  4. for (auto element : input)
  5. {
  6. sum += element;
  7. }
  8. return sum;
  9. }

该阵列由volatile int s组成:

  1. int bar(const std::array<volatile int, 4>& input)
  2. {
  3. auto sum = 0xD;
  4. for (auto element : input)
  5. {
  6. sum += element;
  7. }
  8. return sum;
  9. }

当我查看生成的汇编代码时,SSE寄存器仅在普通int s的情况下使用。据我所知,使用SSE寄存器的代码既没有优化读取,也没有在彼此之间重新排序它们。循环是展开的,所以也没有任何分支。我能解释代码生成不同的唯一原因是:易失性读取是否可以在累积发生之前重新排序?显然,sum不是易失性的。如果这样的重新排序是不好的,是否有一个情况/例子可以说明这个问题?
使用Clang 9生成的代码:

  1. foo(std::array<int, 4ul> const&): # @foo(std::array<int, 4ul> const&)
  2. movdqu (%rdi), %xmm0
  3. pshufd $78, %xmm0, %xmm1 # xmm1 = xmm0[2,3,0,1]
  4. paddd %xmm0, %xmm1
  5. pshufd $229, %xmm1, %xmm0 # xmm0 = xmm1[1,1,2,3]
  6. paddd %xmm1, %xmm0
  7. movd %xmm0, %eax
  8. addl $13, %eax
  9. retq
  10. bar(std::array<int volatile, 4ul> const&): # @bar(std::array<int volatile, 4ul> const&)
  11. movl (%rdi), %eax
  12. addl 4(%rdi), %eax
  13. addl 8(%rdi), %eax
  14. movl 12(%rdi), %ecx
  15. leal (%rcx,%rax), %eax
  16. addl $13, %eax
  17. retq
ecfdbz9o

ecfdbz9o1#

C++中的volatile关键字是从C继承来的,在C中,它是一个通用的总括,用来指示编译器应该允许阅读或写入对象可能会产生它不知道的副作用的地方。由于可能引起的副作用的种类在不同的平台之间会有所不同,因此标准将如何补偿的问题留给编译器作者判断他们应该如何最好地服务于他们的客户。
微软的8088/8086和后来的x86编译器几十年来一直被设计为支持使用volatile对象来构建一个保护“普通”对象的互斥体的实践。举个简单的例子:如果线程1执行如下操作:

  1. ordinaryObject = 23;
  2. volatileFlag = 1;
  3. while(volatileFlag)
  4. doOtherStuffWhileWaiting();
  5. useValue(ordinaryObject);

线程2周期性地执行如下操作:

  1. if (volatileFlag)
  2. {
  3. ordinaryObject++;
  4. volatileFlag=0;
  5. }

那么对volatileFlag的访问将作为对微软编译器的警告,它们应该避免假设任何对象上的任何先前操作将如何与随后的操作交互。其他语言(如C#)中的volatile限定符也遵循了这种模式。
不幸的是,clang和gcc都没有包含任何以这种方式处理volatile的选项,而是选择要求程序员使用特定于编译器的intrinsic来产生相同的语义,而Microsoft仅使用标准关键字volatile就可以实现相同的语义,该标准关键字旨在适用于这种目的[根据标准的作者,“volatile对象也是多个进程共享变量的合适模型。”--参见http://www.open-std.org/jtc1/sc22/wg14/www/C99RationaleV5.10.pdf p。[第76条,11月25日至26日]

展开查看全部

相关问题