emOsprey  鱼鹰谈单片机 2月21日

预计阅读时间: 4 分钟

和 const 不同(关于 const 可以看 const 小节),当一个变量声明为 volatile,说明这个变量会被意想不到的改变。最为典型的就是 I/O 的输入寄存器了,因为这个变量的值和外部电平有关系,一旦外部电平发生了变化,那么这个变量也就跟着变。当然还有其它寄存器也是如此,比如各种状态寄存器、定时计数器等,他们的改变是靠硬件来改变的,你的程序只能读取数据,所以一定要申明为 volatile 才行,这样当你的优化级别提高的时候,你的程序也就能保证不会因为过度优化而出现问题。

那么申明为 volatile 的变量对编译器有什么影响呢?我们知道编译器是有优化功能的,很多时候,有些变量的值在运行过程可能是不变的,如果每次访问这个变量都要从原来的内存获得变量值,那是很浪费时间的,如果你让你的的编译器不做优化,那么它每次访问这个变量都会从内存读出数据,这样不仅效率不高,代码量也会比较大,而一旦进行优化了,编译器就会把它认为不变的量保存在内部寄存器中,每次访问这个变量的时候就访问这个寄存器就可以了,这样运行效率将大大提高。所以一般写代码的时候都会有两个版本,一个是 Debug 版本,一个是 Release 版本。 Debug 版本和 Release 版本其中的一个区别就是优化级别的不同,当然他们的不同不仅仅表现在这两个方面。

首先来看看两个不同优化等级的情况下的代码情况吧:

可以看到效果还是很明显的。

首先是 Code 减少了 24.1%,RO-data 不变,RW-data 减小了 8,这是因为我在程序中申明了两个指针在程序中并没有使用,所以被优化掉了。

就是这两货占用了 8 个字节空间,现在因为优化等级提高,被优化掉了。然后ZI-data 保持不变。

这样看来,优化效果还是很明显的。但是代码运行会不会出问题,很大程度上就是volatile 的问题了。

现在看看库函数的 GPIO 结构体声明:

可以看到每个寄存器声明都是 __IO,而 __IO 最终可以看到就是 volatile:

所以每次编译器碰到申明为 volatile 的变量就不敢用寄存器中的备份了,而是从原来的内存中访问数据。

除了硬件寄存器,还有多线程共享的变量和中断服务程序的使用变量,它们都是类似的道理,都是在一个函数中可能不改变,而在其他函数(中断处理函数或者其他线程的函数)可能改变的情况,如果不声明 volatile,那么编译器看到这个变量在一个函数中不改变,就可能会只访问一次内存,然后保存到寄存器中,使用时可能都只用寄存器的副本,而一旦其他程序也对这个变量进行了写操作,它是察觉不出来的,还可能破坏这次写操作,详细内容可以看信号量部分。

说到这里,就想到一个朋友遇到的情况。当时他有一个变量在中断处理程序中进行自加,而在 main 函数中进行读取,然后调试发现这个变量读出始终为 0,根本没有改变,但能确定已经进入中断处理程序了。当时我也知道这么个情况,但是我有其他事情,就没有深究。后来他调了半天加上了 volatile 关键字才解决,之后我问他是否是编译优化级别太高了,他说优化级别根本没动过,所以他只是怀疑,并没有真正去看他编译器的级别。等我叫他再去验证的时候发现真的是优化等级高了,根本不是 level 0,而是  level 3。

当然了,如果说你懂一点汇编语言,这个问题应该比较好解决,因为不管编译器怎么优化,最终都会在汇编层面得到体现,如果发现汇编代码始终使用寄存器的副本,那么就可以判定优化基本太高了,这个时候将优化级别设置为 level 0 就没有问题了,但是最好的办法还是加上 volatile,因为以后你可能会进行优化处理也说不定呢!

-THE END-


如果觉得文章对你有帮助,欢迎转发、分享给朋友,感谢你的支持!

C语言之volatile的更多相关文章

  1. C语言关键字-volatile

    1.C语言关键字volatile     C 语言关键字volatile(注意它是用来修饰变量而不是上面介绍的__volatile__)表明某个变量的值可能在外部被改变,因此对这些变量的存取 不能缓存 ...

  2. 从C语言的volatile关键字,了解C#的volatile机制(转载)

    C#中有一个关键字volatile,一直不太明白到底什么时候才用它,只知道在多线程操作同一个变量的时候要使用volatile关键字,下面看到了一篇C语言关于volatile关键字的介绍,写的很不错,其 ...

  3. (转)c语言学习volatile

    原文网址:http://www.cnblogs.com/chio/archive/2007/11/24/970632.html 参考网址:http://www.embedu.org/Column/Co ...

  4. c语言,volatile

          一.意义: 该关键字的意义就是表示定义的变量值随时都会改变,必须从变量的地址处读取值,所以只有这个变量在使用过程中可能被改变(比如中断程序),就需要用这个关键字说明. )volatile, ...

  5. 面试问题之C++语言:volatile关键字的作用

    volatile的作用 volatile关键字是防止在共享的空间发生读取的错误.只保证其可见性,不保证原子性:使用volatile指每次从内存中读取数据,而不是从编译器优化后的缓存中读取数据,简单来讲 ...

  6. [汇编与C语言关系]5. volatile限定符

    现在研究一下编译器优化会对生成的指令产生什么影响,在此基础上介绍C语言的volatile限定符.首先看下面的C程序: /* artificial device registers */ unsigne ...

  7. C语言volatile关键字

    volatile提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据.如果没有volatile关键字,则编译器可能优化读取和存储 ...

  8. zz剖析为什么在多核多线程程序中要慎用volatile关键字?

    [摘要]编译器保证volatile自己的读写有序,但由于optimization和多线程可以和非volatile读写interleave,也就是不原子,也就是没有用.C++11 supposed会支持 ...

  9. C++ 中 volatile 的使用

    一.作用 volatile的作用是: 作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值. 简单地说就是防止编译器对代码进行优化.比如如下程序:XBYTE[2]=0x55;XBY ...

随机推荐

  1. 使用nginx构建限频、限速、限并发的应用保护层

    使用nginx构建限频.限速.限并发的应用保护层 nginx本身提供了基础的限频.限速.限并发连接等能力. 限频 基于uri等限制某一个客户端,某类客户端持续时间段内建立连接的次数. 限速 限制客户端 ...

  2. js 中json遍历 添加 修改 类型转换

    <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8&quo ...

  3. GrapeCity Documents (服务端文档API组件) V3.0 正式发布

    近日,葡萄城GrapeCity Documents(服务端文档API组件)V3.0 正式发布! 该版本针对 Excel 文档.PDF 文档和 Word 文档的 API 全面更新,加入了用于生成 Exc ...

  4. SQL SERVER MONTH函数

    定义: MONTH函数返回指定日期的月的部分 语法: MONTH(date) 参数: ①date参数是合法的日期表达式. 返回值: int型数据 例:  声明:本文是本人查阅网上及书籍等各种资料,再加 ...

  5. ubuntu 18.04 LTS 安装ROS系统

    不同的ubuntu系统对应着不同的ROS版本,如果装错了就会提示 E:无法定位软件包 ROS有Melodic.Lunar.Kinetic不同的种类对应着不同的ubuntu版本 Melodic主要对应: ...

  6. python学习-8 用户有三次机会登陆

    用户登陆(三次机会) count = 0 while count < 3: user = input('请输入账号:') pwd = input('请输入密码:') ': print(" ...

  7. 数据分析—win7+ipython+notebook安装

    先安装python 3.x 然后 cmd 执行 pip3 ipython 然后 cmd 执行 pip3 install jupyter notebook 然后 cmd 执行 jupyter noteb ...

  8. 正则表达式BREs,EREs,PREs的比较

    目录 正则表达式BREs,EREs,PREs的比较 正则表达式分类: Linux 中常用文本工具与正则表达式的关系 grep , egrep 正则表达式特点: sed 正则表达式特点 Awk(gawk ...

  9. Spring Boot:上传文件大小超限制如何捕获 MaxUploadSizeExceededException 异常

    Spring Boot 默认上传文件大小限制是 1MB,默认单次请求大小是 10MB,超出大小会跑出 MaxUploadSizeExceededException 异常 spring.servlet. ...

  10. Java 字符串比较

    1.字符串比较 compareTo() 方法用于两种方式的比较: 字符串与对象进行比较. 按字典顺序比较两个字符串. 返回值 返回值是整型,它是先比较对应字符的大小(ASCII码顺序),如果第一个字符 ...