热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

VC++,x86上的/volatile:ms

如何解决《VC++,x86上的/volatile:ms》经验,为你挑选了1个好方法。

上的文档volatile说:

当使用/ volatile:ms编译器选项时(默认情况下,以ARM以外的体系结构为目标时),编译器将生成额外的代码,以维护对易失对象的引用之间的顺序,并保持对其他全局对象的引用的顺序。

哪些确切的代码可以使用/volatile:ms和进行不同的编译/volatile:iso



1> Cody Gray..:

对此有一个完整的了解需要一些历史课程。(谁不喜欢历史?......说谁是历史专业的人。)/volatile:ms语义首先加入到与Visual Studio 2005编译器与该版本开始,标志着变量volatile自动采集征收语义上读取,并在释放语义通过该变量进行写入。

这是什么意思?它与内存模型有关,特别是与允许编译器对内存访问操作进行重新排序的积极程度有关。具有获取语义的操作可防止后续的内存操作挂在其上方;具有释放语义的操作可防止之前的内存操作延迟到之后。顾名思义,获取语义通常在获取资源时使用,而释放语义通常在释放资源时使用。MSDN对获取和释放语义有更完整的描述 ; 它说:

如果其他处理器在任何后续操作生效之前总能看到其效果,则该操作具有语义。如果其他处理器将在操作本身的效果之前看到每个先前操作的效果,则该操作具有 释放语义。考虑以下代码示例:

a++;
b++;
c++;

从另一个处理器的角度来看,前面的操作可能以任何顺序发生。例如,另一个处理器可能会在的增量b之前看到的增量a

例如,InterlockedIncrementAcquire例程使用获取语义来增加变量。如果您重写了前面的代码示例,如下所示:

InterlockedIncrementAcquire(&a);
b++;
c++;

其他处理器总是看到增量a的增量之前bc

同样,InterlockedIncrementRelease例程使用释放语义来增加变量。如果再次重写代码示例,如下所示:

a++;
b++;
InterlockedIncrementRelease(&c);

其他处理器将始终看到ab的增量c

现在,就像MSDN所说的那样,原子操作既具有获取语义又具有释放语义。而且,实际上,在x86上,没有办法只给一条指令获取或释放语义,因此,即使要实现其中之一,也必须使该指令成为原子的(编译器通常会通过发出LOCK CMPXCHG指令来做到这一点)。

在Visual Studio 2005增强volatile语义之前,要编写正确的代码的开发人员需要使用Interlocked*功能家族,如MSDN文章中所述。不幸的是,许多开发人员未能做到这一点,并且得到的代码大多是偶然地起作用(或根本不起作用)。但是,有一个很好的机会,这的确是偶然的工作,考虑到86的相对严格的内存模型。您经常可以免费获得所需的语义,因为在x86上,大多数加载和存储已经具有获取/释放语义。,因此您甚至不需要使任何原子化。(非临时性存储是明显的例外,但是在这种情况下,这些都不重要。)我怀疑在x86上实现这种简便性,再加上程序员通常无法理解并做正确的事情,说服微软加强volatileVS 2005中的语义。

进行此更改的另一个潜在原因是多线程代码的重要性日益提高。2005年大约是带有HyperThreading的 Pentium 4芯片开始流行的时候,有效地将同步多线程带入了每个用户的桌面。可能并非巧合,VS 2005还删除了链接到C运行时库的单线程版本的选项。只有当您具有多线程代码并可能在多个处理器上执行时,您才真正开始担心正确的内存访问语义。

在VS 2005及更高版本中,您只需将指针参数标记为volatile,即可获得所需的获取语义。易变性暗示/强加了获取语义,这使得在多处理环境中运行的多线程代码安全。在2011年之前,这非常重要,因为C和C ++语言标准绝对没有关于线程的内容,也没有给您提供编写正确代码的可移植方式。

这使我们有权回答您的问题。如果您的代码采用的这些扩展语义volatile,那么您需要传递此/volatile:ms开关以确保编译器继续应用它们。如果您编写了使用现代原语进行原子,线程安全操作的C ++ 11风格代码,则无需volatile具有这些扩展的语义并且可以安全地传递/volatile:iso。换句话说,作为manni66打趣说,如果你的代码“误用volatilestd::atomic”,然后你会看到在行为和需求的差异/volatile:ms,以保证volatile 不会有相同的效果std::atomic

事实证明/volatile:iso,与相比,我很难找到实际更改所生成代码的示例/volatile:ms。实际上,Microsoft的优化程序在重新排序指令方面非常保守,这是获取/发布语义应该避免的类型。

这是一个简单的示例(其中您正在使用volatile全局变量来保护关键部分,就像您在一个简单的“无锁”实现中可能会发现的那样),该示例证明两者之间的区别:

volatile bool CriticalSection;
int           Data[100];

void FillData(int i)
{
   Data[i] = 42;              // fill data item at index 'i'
   CriticalSection = false;   // release critical section
}

如果您使用GCC在编译此-O2代码,它将生成以下机器代码:

FillData(int):
    mov     eax, DWORD PTR [esp+4]             // retrieve parameter 'i' from stack
    mov     BYTE PTR [CriticalSection], 0      // store '0' in 'CriticalSection'
    mov     DWORD PTR [Data+eax*4], 42         // store '42' at index 'i' in 'Data'
    ret

即使您不太熟练使用汇编语言,您也应该能够看到优化器已对存储进行了重新排序,从而在(CriticalSection = false)数据被填充之前释放()关键部分,这Data[i] = 42恰好与汇编语言相反。语句在原始C代码中出现的顺序。,volatile它对这种重新排序没有影响,因为GCC遵循ISO语义,就像/volatile:iso在理论上一样。

顺便说一下,请注意这种排序的方式……嗯……易变。如果我们-O1在GCC 中进行编译,我们将获得指令,这些指令以与原始C代码相同的顺序执行所有操作:

FillData(int):
    mov     eax, DWORD PTR [esp+4]             // retrieve parameter 'i' from stack
    mov     DWORD PTR [Data+eax*4], 42         // store '42' at index 'i' in 'Data'
    mov     BYTE PTR [CriticalSection], 0      // store '0' in 'CriticalSection'
    ret

当您开始向其中扔出更多指令以重新编译时,尤其是如果要内联此代码时,您可以想象保留原始顺序的可能性很小。

但是,就像我说的那样,MSVC在重新排序指令方面实际上非常保守。无论我指定/volatile:ms还是/volatile:iso,我都会得到完全相同的机器代码:

FillData, COMDAT PROC
    mov      eax, DWORD PTR [esp+4]
    mov      DWORD PTR [Data+eax*4], 42
    mov      BYTE PTR [CriticalSection], 0
    ret
FillData ENDP

商店是按原始顺序完成的。我玩过各种不同的排列,引入了其他变量和操作,所有这些都无法找到导致MSVC重新排序商店的神奇序列。因此,很可能当前在实践中,/volatile:iso在针对x86体系结构时,开关设置不会有很大的不同。至少可以这样说,但这是一个非常宽松的保证。

请注意,这种经验观察与Alexander Gutenev的推测一致,即仅在ARM上观察到语义上的差异,并且引入这些开关的全部原因是为了避免在此新支持的平台上损失性能。同时,在x86方面,由于基本上没有成本,因此生成的代码中的语义没有实际更改。(除了一些极其琐碎的优化可能性之外,但这还要求其优化器具有两个完全独立的调度程序,这可能不是很好地利用开发人员时间。)

关键是,通过/volatile:iso允许 MSVC 像GCC一样对商店进行重新排序。使用/volatile:ms,可以确保不会,因为volatile暗示该变量的获取/释放语义。


奖金阅读:那么,什么是volatile 应该用于在符合ISO标准严格的代码(,当/volatile:iso开关使用)?好吧,volatile基本上是用于内存映射的I / O。这就是它最初引入时的原始含义,并且仍然是其主要目的。我曾开玩笑说过这volatile是为了读/写磁带机。基本上,标记指针volatile是为了防止编译器优化读写操作。例如:

volatile char* pDeviceIOAddr = ...;

void Wait()
{
    while (*pDeviceIOAddr)
    { }
}

使用参数限定参数的类型volatile可防止编译器假定后续读取返回相同的值,从而迫使其每次在循环中都进行新读取。换一种说法:

  mov  eax, DWORD PTR [pDeviceIoAddr]  // get pointer
Wait:
  cmp  BYTE PTR [eax], 0               // dereference pointer, read 1 byte,
  jnz  Wait                            //  and compare to 0

如果pDeviceIoAddr不是volatile,整个循环可能会被消除。优化程序在实践中肯定会这样做,包括MSVC。或者,您可以获得以下病理代码:

  mov  eax, DWORD PTR [pDeviceIoAddr]  // get pointer
  mov  al, BYTE PTR [eax]              // dereference pointer, read 1 byte
Wait:
  cmp  al, 0                           // compare it to 0
  jnz  Wait

指针在循环外被解引用一次,将字节缓存在寄存器中。循环顶部的指令仅测试已注册的值,而不创建循环或无限循环。哎呀。

但是请注意,volatile在ISO标准C ++中使用并不能消除对关键节,互斥锁或其他类型锁的需求。如果另一个线程可能进行修改pDeviceIOAddr,则即使上述代码的正确版本也无法正常工作,因为该地址/指针的读取没有获取语​​义。获取语​​义看起来像这样:

Wait:
  mov  eax, DWORD PTR [pDeviceIoAddr]  // get pointer (acquire semantics)
  cmp  BYTE PTR [eax], 0               // dereference pointer, read 1 byte,
  jnz  Wait                            //  and compare to 0

而要得到它,您将需要C ++ 11的std::atomic


推荐阅读
  • 兆芯X86 CPU架构的演进与现状(国产CPU系列)
    本文详细介绍了兆芯X86 CPU架构的发展历程,从公司成立背景到关键技术授权,再到具体芯片架构的演进,全面解析了兆芯在国产CPU领域的贡献与挑战。 ... [详细]
  • 2020年9月15日,Oracle正式发布了最新的JDK 15版本。本次更新带来了许多新特性,包括隐藏类、EdDSA签名算法、模式匹配、记录类、封闭类和文本块等。 ... [详细]
  • 高端存储技术演进与趋势
    本文探讨了高端存储技术的发展趋势,包括松耦合架构、虚拟化、高性能、高安全性和智能化等方面。同时,分析了全闪存阵列和中端存储集群对高端存储市场的冲击,以及高端存储在不同应用场景中的发展趋势。 ... [详细]
  • 数字图书馆近期展出了一批精选的Linux经典著作,这些书籍虽然部分较为陈旧,但依然具有重要的参考价值。如需转载相关内容,请务必注明来源:小文论坛(http://www.xiaowenbbs.com)。 ... [详细]
  • 应用链时代,详解 Avalanche 与 Cosmos 的差异 ... [详细]
  • 本文详细介绍了如何解决DNS服务器配置转发无法解析的问题,包括编辑主配置文件和重启域名服务的具体步骤。 ... [详细]
  • 本文总结了一些开发中常见的问题及其解决方案,包括特性过滤器的使用、NuGet程序集版本冲突、线程存储、溢出检查、ThreadPool的最大线程数设置、Redis使用中的问题以及Task.Result和Task.GetAwaiter().GetResult()的区别。 ... [详细]
  • 秒建一个后台管理系统?用这5个开源免费的Java项目就够了
    秒建一个后台管理系统?用这5个开源免费的Java项目就够了 ... [详细]
  • 在CentOS 7环境中安装配置Redis及使用Redis Desktop Manager连接时的注意事项与技巧
    在 CentOS 7 环境中安装和配置 Redis 时,需要注意一些关键步骤和最佳实践。本文详细介绍了从安装 Redis 到配置其基本参数的全过程,并提供了使用 Redis Desktop Manager 连接 Redis 服务器的技巧和注意事项。此外,还探讨了如何优化性能和确保数据安全,帮助用户在生产环境中高效地管理和使用 Redis。 ... [详细]
  • 在《Cocos2d-x学习笔记:基础概念解析与内存管理机制深入探讨》中,详细介绍了Cocos2d-x的基础概念,并深入分析了其内存管理机制。特别是针对Boost库引入的智能指针管理方法进行了详细的讲解,例如在处理鱼的运动过程中,可以通过编写自定义函数来动态计算角度变化,利用CallFunc回调机制实现高效的游戏逻辑控制。此外,文章还探讨了如何通过智能指针优化资源管理和避免内存泄漏,为开发者提供了实用的编程技巧和最佳实践。 ... [详细]
  • 您的数据库配置是否安全?DBSAT工具助您一臂之力!
    本文探讨了Oracle提供的免费工具DBSAT,该工具能够有效协助用户检测和优化数据库配置的安全性。通过全面的分析和报告,DBSAT帮助用户识别潜在的安全漏洞,并提供针对性的改进建议,确保数据库系统的稳定性和安全性。 ... [详细]
  • 深入解析C语言中结构体的内存对齐机制及其优化方法
    为了提高CPU访问效率,C语言中的结构体成员在内存中遵循特定的对齐规则。本文详细解析了这些对齐机制,并探讨了如何通过合理的布局和编译器选项来优化结构体的内存使用,从而提升程序性能。 ... [详细]
  • B站服务器故障影响豆瓣评分?别担心,阿里巴巴架构师分享预防策略与技术方案
    13日晚上,在视频观看高峰时段,B站出现了服务器故障,引发网友在各大平台上的广泛吐槽。这一事件导致了连锁反应,大量用户纷纷涌入A站、豆瓣和晋江等平台,给这些网站带来了突如其来的流量压力。为了防止类似问题的发生,阿里巴巴架构师分享了一系列预防策略和技术方案,包括负载均衡、弹性伸缩和容灾备份等措施,以确保系统的稳定性和可靠性。 ... [详细]
  • 线程能否先以安全方式获取对象,再进行非安全发布? ... [详细]
  • 在Ubuntu系统中配置Python环境变量是确保项目顺利运行的关键步骤。本文介绍了如何将Windows上的Django项目迁移到Ubuntu,并解决因虚拟环境导致的模块缺失问题。通过详细的操作指南,帮助读者正确配置虚拟环境,确保所有第三方库都能被正确识别和使用。此外,还提供了一些实用的技巧,如如何检查环境变量配置是否正确,以及如何在多个虚拟环境之间切换。 ... [详细]
author-avatar
cecillalurw_689
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有