热门标签 | 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


推荐阅读
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • 如何使用PLEX播放组播、抓取信号源以及设置路由器
    本文介绍了如何使用PLEX播放组播、抓取信号源以及设置路由器。通过使用xTeve软件和M3U源,用户可以在PLEX上实现直播功能,并且可以自动匹配EPG信息和定时录制节目。同时,本文还提供了从华为itv盒子提取组播地址的方法以及如何在ASUS固件路由器上设置IPTV。在使用PLEX之前,建议先使用VLC测试是否可以正常播放UDPXY转发的iptv流。最后,本文还介绍了docker版xTeve的设置方法。 ... [详细]
  • 【技术分享】一个 ELF 蠕虫分析
    【技术分享】一个 ELF 蠕虫分析 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 海马s5近光灯能否直接更换为H7?
    本文主要介绍了海马s5车型的近光灯是否可以直接更换为H7灯泡,并提供了完整的教程下载地址。此外,还详细讲解了DSP功能函数中的数据拷贝、数据填充和浮点数转换为定点数的相关内容。 ... [详细]
  • SpringBoot整合SpringSecurity+JWT实现单点登录
    SpringBoot整合SpringSecurity+JWT实现单点登录,Go语言社区,Golang程序员人脉社 ... [详细]
  • 本文分析了Wince程序内存和存储内存的分布及作用。Wince内存包括系统内存、对象存储和程序内存,其中系统内存占用了一部分SDRAM,而剩下的30M为程序内存和存储内存。对象存储是嵌入式wince操作系统中的一个新概念,常用于消费电子设备中。此外,文章还介绍了主电源和后备电池在操作系统中的作用。 ... [详细]
  • 解决Sharepoint 2013运行状况分析出现的“一个或多个服务器未响应”问题的方法
    本文介绍了解决Sharepoint 2013运行状况分析中出现的“一个或多个服务器未响应”问题的方法。对于有高要求的客户来说,系统检测问题的存在是不可接受的。文章详细描述了解决该问题的步骤,包括删除服务器、处理分布式缓存留下的记录以及使用代码等方法。同时还提供了相关关键词和错误提示信息,以帮助读者更好地理解和解决该问题。 ... [详细]
  • 本文整理了Java中com.evernote.android.job.JobRequest.getTransientExtras()方法的一些代码示例,展示了 ... [详细]
  • python中安装并使用redis相关的知识
    本文介绍了在python中安装并使用redis的相关知识,包括redis的数据缓存系统和支持的数据类型,以及在pycharm中安装redis模块和常用的字符串操作。 ... [详细]
  • Mono为何能跨平台
    概念JIT编译(JITcompilation),运行时需要代码时,将Microsoft中间语言(MSIL)转换为机器码的编译。CLR(CommonLa ... [详细]
  • x86 linux的进程调度,x86体系结构下Linux2.6.26的进程调度和切换
    进程调度相关数据结构task_structtask_struct是进程在内核中对应的数据结构,它标识了进程的状态等各项信息。其中有一项thread_struct结构的 ... [详细]
  • Kali Linux 简介
    KaliLinux是世界渗透测试行业公认的优秀的网络安全审计工具集合,它可以通过对设备的探测来审计其安全性,而且功能完备,几乎包含了目前所 ... [详细]
  • 三、查看Linux版本查看系统版本信息的命令:lsb_release-a[root@localhost~]#lsb_release-aLSBVersion::co ... [详细]
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社区 版权所有