单片机中volatile的应用
01、简述
一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。
如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。
简单来说,如果这变量很重要,且你不想它被编译器优化,就用volatile修饰。
02、用处
关于编译器优化
如果你在编译器器开了优化,那么就要小心了。以下代码使用IAR7.20,优化等级High,选Balanced。main函数的主循环如下
- while (1)
- {
- Delay_ms(500);
- LCD_refresh_flg = 0;
- Delay_ms(500);
- if(LCD_refresh_flg){
- LCD_refresh_flg = 0;
- LCD_ShowString(0,13,"receive_data");
- }
- }
其中LCD_refresh_flg变量在串口中断中
- void USART1_IRQHandler(void)
- {
- if(USART_GetFlagStatus(USART1, USART_FLAG_TC))
- {
- USART_ClearFlag(USART1, USART_FLAG_TC);
- }
- if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE))
- {
- LCD_refresh_flg = 1;
- USART_ClearFlag(USART1, USART_FLAG_RXNE);
- }
- }
其中延时函数如下
- /**
- * @brief Inserts a delay time.
- * @param nTime: specifies the delay time length, in 10 ms.
- * @retval None
- */
- void Delay_ms(uint32_t nTime)
- {
- TimingDelay = nTime;
- while(TimingDelay != 0);
- }
- /**
- * @brief Decrements the TimingDelay variable.
- * @param None
- * @retval None
- */
- void TimingDelay_Decrement(void)
- {
- if (TimingDelay != 0x00)
- {
- TimingDelay--;
- }
- }
- /**
- * @brief This function handles SysTick Handler.
- * @param None
- * @retval None
- */
- void SysTick_Handler(void)
- {
- TimingDelay_Decrement();
- }
这是简单的示例代码(为了说明问题,实际项目开发应该没有类似的代码),代码设计的意图是串口收到数据,LCD显示字符串。但debug时如下
在单步执行时,第85行代码被直接跳过了,且在85行也无法打断点。查看反汇编代码,确认第85,87,88,89代码直接被编译器优化没了。
可能编译器在想,在85行将LCD_refresh_flg变量清零,而86行代码量又不大,所以认为87行的if条件不成立,这全部优化掉了。编译器完全没有想到我们的延时长达500ms,且在串口中断中会将LCD_refresh_flg置1,这完全违背我们的设计意图。
如果我们将LCD_refresh_flg使用volatile修饰,volatileuint8_t LCD_refresh_flg;在编译器不变,优化等级不变时,运行如下:
虽然编译器和优化等级都是一样的,但是这个时候,不再优化LCD_refresh_flg变量。
当然这是编译优化的结果,并不是说,在main函数的主循环的先对变量清零,再后边判断变量就会被编译器优化。如下代码,LCD_refresh_flg没有使用volatile修饰。
此时LCD_ShowString函数是代码量很大的函数,这个时候编译器就不敢轻易优化LCD_refresh_flg变量了。希望大家能从这个例子中理解volatile对编译优化的影响。
关于访问硬件
存储器映射的硬件寄存器通常也要加voliate,因为每次对它的读写都可能有不同意义。例如:假设要对一个设备进行初始化,此设备的某一个寄存器为0xff800000。(例子仅为说明问题,不具有实战意义)
- int *output = (unsigned int *)0xff800000;//定义一个IO端口;
- int init(void)
- {
- int i;
- for(i=0;i< 10;i++){
- *output = i;
- }
- }
经过编译器优化后,编译器认为前面循环半天都是废话,对最后的结果毫无影响,因为最终只是将output这个指针赋值为9,所以编译器最后给你编译编译的代码结果相当于:
- int init(void)
- {
- *output =9;
- }
这明显是不符合我们的设计意图的。
不仅仅是写操作,读操作亦是如此。如果你需要反复读取一个寄存器的值,那么编译器在优化只有读取1次。这时候就该使用volatile通知编译器在遇到此变量时候不要优化。在这里多说一句,我们这里说的是编译器,且是嵌入式方面的,不同的编译器会有不同结果,所以上面的例子并非是在不同的编译器必现的。但是在嵌入式领域反复访问硬件寄存器,加上volatile是有必要的。
中断服务程序
在断服务程序中修改的供其它程序检测的变量,需要加volatile,中断是会突然发生的,当变量在触发某中断程序中修改,而编译器判断主函数里面没有修改该变量,因此可能只执行一次从内存到某寄存器的读操作,而后每次只会从该寄存器中读取变量副本,使得中断程序的操作被短路。加上volatile修饰,内核每次都会小心的从该变量存储的内存中重新读取数据,而不是从已经加载到内核寄存器中的值。
RTOS系统下需要注意
这个问题本质上和上一个是一样的,都是为了防止内核从寄存器中读取变量的值,而不是从变量存储的内存读取。特别是具有抢占属性的RTOS,本质上也是中断时,紧急的任务立即打断了正在执行的任务,“类似”于中断。如果有同学了解RTOS的话,可以想一下RTOS的临界区的概念,都是为了保护变量。这里不再对RTOS进行详述。如果有同学了解Linux的话,那就更好了,volatile的作用和linux的原子操作是差不过的。这里也不再详述,只是发散一下思维。
3、volatile问题
volatile在面试中是非常容易见到的,下面总结一下常见的
1.一个参数既可以是const还可以是volatile吗?
可以的,例如只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
2.一个指针可以是volatile吗?
可以,当一个中服务子程序修改一个指向buffer的指针时。
3.下面的函数有什么错误?
- int square(volatile int*ptr)
- {
- return*ptr * *ptr;
- }
该程序的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:
- int square(volatile int*ptr)
- {
- int a,b;
- a = *ptr;
- b = *ptr;
- return a * b;
- }
由于*ptr的值可能被意想不到地改变,因此a和b可能是不同的。结果,这段代码可能返回不是你所期望的平方值!正确的代码如下:
- long square(volatile int*ptr)
- {
- int a;
- a = *ptr;
- return a * a;
- }
注意:频繁地使用volatile很可能会增加可执行文件的大小和降低性能,因此要合理的使用volatile,不能滥用volatile。
4、总结
volatile 关键字是一种类型修饰符,它的总结如下
- 使用volatile关键字修饰的变量,可以避免编译器优化,遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。
- 使用volatile关键字修饰的变量,每次都是重新读取内存中的值,而不是使用保存在寄存器里的值了;在中断,RTOS,硬件访问多用到。
- 频繁地使用volatile很可能会增加可执行文件的大小和降低性能,因此要合理的使用volatile,不能滥用volatile。
嵌入式系统程序员经常同硬件、中断、RTOS等等打交道,这是区分C程序员和嵌入式系统程序员的最基本的问题,所以在嵌入式应用中这些要求使用volatile变量。不懂得volatile的话可能将会带来灾难。
点击查看:C语言进阶专辑
单片机中volatile的应用的更多相关文章
- 【转】单片机中volatile定义的作用详解
传送门:http://www.eeworld.com.cn/mcu/2011/0411/article_3928.html 一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译 ...
- Lua在单片机中的移植
Lua代码符合ANSI C标准,只要有C编译器的开发环境就能跑Lua. 虽说只要有C编译器就能跑Lua,但是单片机的环境太简单,有些C标准的内容仍旧无法支持. Lua的官网是:www.lua.org ...
- 从JAVA看C#中volatile和synchronized关键字的作用
最近一直在想C#中 volatile关键字到底是用来干什么的?查了很多.NET的文章都是说用volatile修饰的变量可以让多线程同时修改,这是什么鬼... 然后查到了下面这篇JAVA中关于volat ...
- 【转】java中volatile关键字的含义
java中volatile关键字的含义 在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字,在进行多线程并发处理的时候就可以万事大吉. Java语言 ...
- Java中Volatile关键字详解
一.基本概念 先补充一下概念:Java并发中的可见性与原子性 可见性: 可见性是一种复杂的属性,因为可见性中的错误总是会违背我们的直觉.通常,我们无法确保执行读操作的线程能适时地看到其他线程写入的值, ...
- 转:java中volatile关键字的含义
转:java中volatile关键字的含义 在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字,在进行多线程并发处理的时候就可以万事大吉. Java语言 ...
- Java中Volatile的作用
Java中Volatile的作用 看了几篇博客,发现没搞懂.可是简单来说,就是在我们的多线程开发中.我们用Volatile关键字来限定某个变量或者属性时,线程在每次使用变量的时候.都会读取变量改动后的 ...
- 一种单片机支持WiFi的应用——SimpleWiFi在单片机中的应用
一种单片机支持WiFi的应用——SimpleWiFi在单片机中的应用 先上图: 现在的智能控制都是基于微控制器,随着智能的手持终端的普及,基于智能终端的控制就会越来越普遍. WIFI便是其中的一种.W ...
- 转: 【Java并发编程】之十八:第五篇中volatile意外问题的正确分析解答(含代码)
转载请注明出处:http://blog.csdn.net/ns_code/article/details/17382679 在<Java并发编程学习笔记之五:volatile变量修饰符-意料之外 ...
随机推荐
- 【pwn】学pwn日记(堆结构学习)
[pwn]学pwn日记(堆结构学习) 1.什么是堆? 堆是下图中绿色的部分,而它上面的橙色部分则是堆管理器 我们都知道栈的从高内存向低内存扩展的,而堆是相反的,它是由低内存向高内存扩展的 堆管理器的作 ...
- 【问题排查过程】vm-backup的snapshots导致磁盘满
使用中发现,vm-storage节点仅仅过了6天,就占用了800GB的硬盘空间.很不正常.下面是排查过程: 1.查看磁盘占用情况: 先登录容器,执行: df -h /dev/vdb 1012.8G 8 ...
- 【笔记】golang中使用protocol buffers的底层库直接解码二进制数据
背景 一个简单的代理程序,发现单核QPS达到2万/s左右就上不去了,40%的CPU消耗在pb的decode/encode上面. 于是我想,对于特定的场景,直接从[]byte中取出字段,而不用完全的把整 ...
- 【解决了一个小问题】golang samara的kafka客户端中使用错误版本号导致初始化失败
发现在如下代码中存储kafka生产者初始化失败: config.Version = sarama.V0_10_2_1 //V2_2_0_0 producer, err := sarama.NewSyn ...
- QT控件之QSlider
singleStep:比如按下键盘的左右建,每次移动的距离 pageStep:比如用鼠标对准滑动条的前面按下,每次移动的距离 value:初始默认值 接下来看该控件拥有的信号: 重点看后面的四个,看字 ...
- 哪些是GET请求,哪些是POST请求
GET请求: 1,form标签 method=get 2,a标签 3,link标签引入css 4,Script标签引入js文件 5,img标签引入图片 6,iframe引入html页面 7,在浏览器地 ...
- jdk11+安装(win)
jdk11+安装(win) 官网下载 官网下载地址:https://adoptopenjdk.net/index.html 选择合适的版本 安装 运行下载的 MSI 包 下一步 选择安装位置,下一步 ...
- 运行项目时出现Sat May 15 20:00:19 CST 2021 WARN: Establishing SSL connection without server‘s identity veri
这时我们只需要在连接数据库的url上设置:useSSL=false就可以了.
- hashmap 实现 相同的key值时,value值叠加效果。
一,了解一些基础 package com.ohs.demo; /** * * 一.需求是:停止相同的key值,覆盖效果,将重复的value值,叠加起来. * * 二.hash? 什么是hash? * ...
- JavaCV的摄像头实战之五:推流
欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos 本篇概览 本文是<JavaCV的摄像头实战> ...