我正在尝试理解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
组成。
int foo(const std::array<int, 4>& input)
{
auto sum = 0xD;
for (auto element : input)
{
sum += element;
}
return sum;
}
该阵列由volatile int
s组成:
int bar(const std::array<volatile int, 4>& input)
{
auto sum = 0xD;
for (auto element : input)
{
sum += element;
}
return sum;
}
当我查看生成的汇编代码时,SSE寄存器仅在普通int
s的情况下使用。据我所知,使用SSE寄存器的代码既没有优化读取,也没有在彼此之间重新排序它们。循环是展开的,所以也没有任何分支。我能解释代码生成不同的唯一原因是:易失性读取是否可以在累积发生之前重新排序?显然,sum
不是易失性的。如果这样的重新排序是不好的,是否有一个情况/例子可以说明这个问题?
使用Clang 9生成的代码:
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
1条答案
按热度按时间ecfdbz9o1#
C++中的
volatile
关键字是从C继承来的,在C中,它是一个通用的总括,用来指示编译器应该允许阅读或写入对象可能会产生它不知道的副作用的地方。由于可能引起的副作用的种类在不同的平台之间会有所不同,因此标准将如何补偿的问题留给编译器作者判断他们应该如何最好地服务于他们的客户。微软的8088/8086和后来的x86编译器几十年来一直被设计为支持使用
volatile
对象来构建一个保护“普通”对象的互斥体的实践。举个简单的例子:如果线程1执行如下操作:线程2周期性地执行如下操作:
那么对
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日]