C中volatile关键字在程序操作变量时,强制读写变量所在内存,以阻止编译器对某些特殊变量的错误优化。反过来,只有靠程序员用volatile过滤一些特殊情况后,编译器才能大胆优化。volatile作用可总结为:阻止三种情形下的两种编译器优化。

两种编译器优化

a. 数据流分析优化:编译器分析程序中变量在哪里赋值、哪里使用、哪里失效,根据分析结果消除多余的变量读取和赋值步骤,如:

int a = 10;

......//其他代码,里面没有对a的读操作

a = 20;

开启了优化选项的编译器能够根据a赋值和使用情况,推断a=10无效,直接忽略这条语句。

b. 寄存器缓存技术:把频繁访问的变量缓存到某寄存器,之后就通过此寄存器访问该变量,而不再通过内存总线。由于寄存器比内存快很多,这种智能优化可显著提高性能。

三种不能优化的情形

某些变量会在可见代码外而不是被程序本身赋值改变,这时编译器根据显式代码采取的上面两种优化可能导致错误结果,这些特殊变量主要出现在以下三种情况:

a. MMIO(Memory Mapped IO)操作,某些CPU把外设I/O端口映射到内存空间统一编址,之后就象访问普通内存那样访问MMIO,不需要专门I/O指令。MMIO内存的值对应于IO寄存器,会随外部信号而改变,不完全依赖于代码里的显式内存读写操作,很明显这类内存就不能用上文两种技术优化。这种volatile典型应用多出现在嵌入式驱动程序中。

b. 变量被代码内的内嵌汇编改变,而编译器无法知道内嵌汇编里变量的改变。

c. 被中断服务子程序访问到的,或者在多线程应用中被几个任务共享的全局变量。

这三种情况,必须用volatile阻止编译器“想当然”的优化,下面举例说明:

例1:

volatile int *p = get_io_addr();

int a, b;

a = *p;

......//其他代码,里面没有对p的操作

b = *p;

p是指向MMIO的指针,例1中两次读取信号,赋给a和b。如果p不声明为volatile,编译器会”自作聪明”认为两次*p值一样(普通内存的确如此,因为中间没赋值),b=*p时无需通过p指针读取真实外设IO值,可用a=*p时保存在某寄存器的值代替。但外界信号可能随时变化,一旦在a=*p和b=*p间变化,这种用寄存器代替内存的优化就会出错。

volatile同样也用于阻止MMIO写操作的优化,如果给变量赋值但后面没使用,编译器一般会忽略这次赋值操作,但MMIO赋值不同,因为CPU通过MMIO设到硬件寄存器的数据,总有意义(如驱动LED/马达等),必须用volatile以强制执行此类MMIO写操作。

volatile int *p = set_io_addr();   //对外输出控制信号的IO寄存器映射的内存地址

int j;                       //普通变量

*p = 1; //不被优化 i=1

*p = 3; //不被优化 i=3

j = 1; //被优化掉

j = 3; //j = 3

例2:

void main()

{

int i=10;

int a = i;

printf("i1= %d\n",a);

__asm {   mov dword ptr [ebp-4], 20h  }   //改变内存中i的值为20h即32,而优化器并不知道

int b = i;

printf("i2= %d\n",b);

}

以上代码在VC debug模式运行,输出i1=10,i2=32。release重生之大文豪模式输出:i1 = 10,i2 = 10。因为release模式下编译器默认开启优化,在b=i时取之前a=i时读到寄存器缓存值,而内嵌汇编操作不被编译器注意。如果定义volatile int i=10,其他不变,debug和release都输出:i1=10,i2=32。这说明volatile能阻止release模式下编译器“自以为是”的优化。

例3:

多任务/中断环境下,某线程中的变量可能被其他线程或中断改变,而编译器无从获知这种改变,于是导致错误优化,如:

int i=0;

void main(void)

{...

while (1)

{

if (i) do_xxx();

}

}

void ISR_XX(void)   /* Interrupt service routine. */

{

i=1;

}

程序本意希望中断发生时,main调用do_xxx函数,但编译器不知道i会被ISR_XX修改,它通过本地代码判断main函数里i从没修改,因此只执行一次从内存i到寄存器的读操作,之后每次if判断都用寄存器里i的“副本”,而副本值永远是初始值0,即使i在ISR_XX里被改为1,do_xxx也不会被调用。强制从内存读取i,就要定义volatile int i;。

不过即使用volatile避免了错误优化,也不能像上例那样用volatile全局变量去同步线程。因为volatile变量不满足原子性和顺序性,除非加锁保护,而加锁就不需要volatile了:锁可以保证临界区串行,也可以实现内存屏障(barrier),保证临界区内的全局变量为最新值而不是寄存器缓存,和volatile作用相同。关于volatile和线程同步超出范围,不详述。

转载于:https://www.cnblogs.com/jiangye/p/3496435.html

补遗篇之volatile相关推荐

  1. MUD教程--巫师入门教程3

    1. 指令格式为:edit <档名>,只加文件名,默认为当前目录,加here,表示编辑你当前所处的房间, 回车后即进入线上编辑系统.  2. 如果这是一个已经有的档案,你可以使用 z 或 ...

  2. 多图 | 搞懂volatile和synchronized的区别

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 来源 | 公众号「日拱一兵」 之前写了几篇 Java并发 ...

  3. Java高阶代码_Java高阶语法---Volatile

    背景:听说Volatile Java高阶语法亦是挺进BAT的必经之路. Volatile: volatile同步机制又涉及Java内存模型中的可见性.原子性和有序性,恶补基础一波. 可见性: 可见性简 ...

  4. devc代码补全没效果_从零开始写文本编辑器(二十八):自动补全(上)

    前言 我本没打算这么早就写"自动补全"功能的. 但是在写XML资源编辑时,为了实现自动引用已有资源@string/xxx,需要一个合适的列表来让我选择.这样能防止拼写错误. 也就是 ...

  5. 93后阿里P7晒出工资单:原来是狠补了这个~真香

    前段时间在某知名程序员论坛上看到一位95年阿里P7老哥分享自己跳槽的经历,跳到一个独角兽新公司一个月后发了工资,月入5万多,表示很满足!税后这薪资,税前估计超6万了.细想一下,是真的酸...只能狠补一 ...

  6. Java宣言的时候,Java基础恶补——宣言及访问控制

    Java基础恶补--声明及访问控制 [SCJP Sun Certified Programmer for Java 6 Study Guide (Exam 310-065)]  chapter 1 一 ...

  7. android 自动补全方法,# AndroidStudio代码块的自动补全

    AndroidStudio代码块的自动补全 settings --> Editor --> Live Templates * 定义一个私有的内部类,在第一次用这个嵌套类时,会创建一个实例. ...

  8. Java并发编程之volatile关键字

    大概是因为项目.业务的原因,工作上几乎还没有使用过多线程相关的功能,相关知识差不多都忘了,所以最近补一下基础. volatile用来修饰共享变量,volatile变量具有 synchronized 的 ...

  9. c补week1(linux c基本操作及C语言部分基础知识)

    一.linux常见命令 清屏:ctrl+l 在终端输入clear 补全键:tab键 上下箭头:查找输入的历史命令 打开一个和当前路径相同的终端:ctrl+shift+n 1.ls显示当前目录 ls   ...

最新文章

  1. 一个“复制/删除”方式的滚动
  2. 【BZOJ3073】[Pa2011]Journeys 线段树+堆优化Dijkstra
  3. 这都2021年了,还不会Feign性能调优?Feign性能调优之gzip压缩实现-自娱自乐篇
  4. clob和blob是不是可以进行模糊查询_为省几十元买假内存条?金士顿内存条真伪查询与辨别方法...
  5. Site24x7 为Teams提供可智能 DevOps
  6. 【模式识别】K均值聚类算法应用实验报告及MATLAB仿真
  7. ArcGIS API for Silverlight 调用GP服务准备---GP模型建立、发布、测试
  8. 线程控制 12 | 线程属性
  9. 鸿蒙系统的变化,鸿蒙系统没变化的背后
  10. 树莓派 python_树莓派笔记08—Python流水灯
  11. getconf 取系统配制 --CPU
  12. eclipse配置java环境_java环境搭建(Eclipse)
  13. SQL Server 数据库之索引
  14. 【totti】sun和IBM虚拟机堆大小设置分析
  15. java获取当天开始,结束时间
  16. iOS 给控件View添加角标BadgeValue
  17. Ubuntu16.04中python升级到3.6版本后Terminal打不开的解决方法
  18. iPhone手机屏幕三种故障及维修方法
  19. 2016年2月西部数码.wang域名注册量及份额报告
  20. 高版本CAD如何降低版本?来看这种降低版本方法

热门文章

  1. 小马儿随笔(三)——小标签 大学问
  2. 单例对象会被jvm的gc时回收吗_【PHP设计模式】单例模式
  3. resource android:attr/dialogCornerRadius not found
  4. mac vscode远程服务器
  5. 摩根溪创始人:特斯拉资产负债表有8%是比特币
  6. Bounce宣布关于BOT代币迁移的链上治理提案已通过
  7. SAP License:SAP系统合规性审计介绍
  8. while(scanf(%d,n)!=EOF) / while(cinn)终止问题
  9. adb shell top 使用
  10. uiautomatorviewer 查看元素新思路