c++ - 理解 C++ 中的 volatile 关键字
问题描述
我试图了解 volatile 关键字在 C++ 中的工作原理。
我看了一下C++ 中的“易失性”防止了哪些优化?. 查看接受的答案,看起来 volatile 禁用了两种优化
- 防止编译器将值缓存在寄存器中。
- 当您的程序的 POV 似乎不需要访问该值时,优化访问该值。
我在https://en.cppreference.com/w/cpp/language/as_if找到了类似的信息。
对 volatile 对象的访问(读取和写入)严格按照它们出现的表达式的语义进行。特别是,它们不会针对同一线程上的其他易失性访问重新排序。
我编写了一个简单的 C++ 程序,它将数组中的所有值相加,以比较 plain int
s 与 volatile int
s 的行为。请注意,部分总和不是易变的。
数组由不合格int
的 s 组成。
int foo(const std::array<int, 4>& input)
{
auto sum = 0xD;
for (auto element : input)
{
sum += element;
}
return sum;
}
数组由 volatileint
组成
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
不是易变的。如果这种重新排序不好,是否有可以说明问题的情况/示例?
使用 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
解决方案
C++ 中的volatile
关键字是从 C 继承而来的,在 C++ 中,它旨在作为一个通用的包罗万象的地方,以指示编译器应该允许读取或写入对象可能具有它不知道的副作用的可能性的地方。由于不同平台可能引起的副作用种类会有所不同,因此该标准留下了编译器编写者对如何最好地为客户服务的判断的问题。
微软的 8088/8086 和后来的 x86 编译器几十年来一直被设计为支持使用volatile
对象来构建保护“普通”对象的互斥锁的做法。举个简单的例子:如果线程 1 执行以下操作:
ordinaryObject = 23;
volatileFlag = 1;
while(volatileFlag)
doOtherStuffWhileWaiting();
useValue(ordinaryObject);
并且线程 2 定期执行以下操作:
if (volatileFlag)
{
ordinaryObject++;
volatileFlag=0;
}
那么对 的访问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]
推荐阅读
- node.js - Passport.js 将 azure-ad 身份验证链接视为相对链接,尝试登录时返回 404
- ruby-on-rails - Ruby on Rails - 在 OSX 上使用 Ruby 2.4.4 而不是 rails 5.1.6 的配置问题/异常
- asp.net - ASP .Net MVC Web 应用程序搜索过滤器不起作用?
- html - jQuery 每个函数都不能用于将查询字符串附加到 URL
- c# - c#图表内存不足异常
- google-chrome - 来自 localhost 的文件可以用作 web_accessible_resources 吗?
- android - HMS Location Kit,位置更新仅在用户选择“始终允许”时有效
- python - 根据运行时计算的值动态创建复杂的嵌套 JSON
- wordpress - 导航菜单可悬停,但不显示 WordPress
- amazon-web-services - 从 db 中检索 Redshift 集群指标