volatile限定符告知计算机,其他agent(而不是变量所在的程序)可以改变该变量的值。通常它被用于硬件地址以及在其他程序或同时运行的线程中共享数据。要求编译器不要对其描述的对象作优化处理,对它的读写都需要从内存中访问。

使用volatile关键字声明变量,可以让编译器不对该变量进行优化操作。如果在一些需要使用volatile关键字的地方而没有使用它时,那么编译器可能会优化对该变量的访问,并生成意外的代码或达不到预期的功能。

目录

1 volatile意味着什么

2 什么时候该使用volatile

3 不使用volatile可能出现的问题

4 强制使用特定指令来访问内存

5 使用volatile和不使用volatile的汇编对比

6 总结

编译器会优化什么

作用

特点


1 volatile意味着什么

将变量声明为volatile,是在告诉编译器,该变量的值可能随时会被其他实体(比如操作系统或者硬件)改变。该声明可以保证编译器不会优化对该变量的使用,即使该变量未被使用或者未被更改。

2 什么时候该使用volatile

对于可能在定义它们的作用域之外被修改的变量,使用volatile关键字。比如:

  • 会被多个子程序读写的全局变量。如果程序在某些计算中使用到了全局变量,由于需要经常对该变量进行读写操作,编译器为了加速读写操作,会生成代码将该变量的值加载到寄存器中,以执行该计算。如果相同的全局变量随后在另一个子程序中被使用,编译器可能会直接读取寄存器中的现有值,而不是去内存当中加载(寄存器中的是最新的值,还没有更新到内存中,而该子程序的本意是使用内存中的值),这可能导致计算错误。这种直接读取寄存器里的值的做法,是因为优化器认为该变量是non-volatile的,不能从外部被修改,而这种假设对于内存映射的外设是不正确的。
  • 被用作定时(sleep或者timer)函数的延时变量。如果这种变量从未被使用过,编译器可能会将整个延时函数代码移除,除非将该变量声明为volatile。
  • 被中断服务子程序调用的变量。如下示例代码中,中断子程序async_interrupt在类中声明定义,但是它可能被硬件异步调用。被调用后会改变一个名为buffer_full的变量,如果不把buffer_full声明为volatile,check_stream函数可能会读不到buffer_full被async_interrupt更改后的值,导致程序错误。
    • class myclass
      {public:int check_stream();void async_interrupt();private:bool buffer_full;  // must be declared as volatile
      };
      int myclass::check_stream()
      {int count = 0;while (!buffer_full){count++;}return count;
      }
      void myclass::async_interrupt()
      {buffer_full = !buffer_full;
      }

此外:

  • 访问一些内存映射的外设时,必须使用volatile关键字声明。就算编译时采用 -O0(默认不优化),仍然不能保证每个变量都被当作volatile。
  • volatile 并不意味着内部线程通信或者同步(inter-thread communication or synchronization),要达到这个目的,需要使用原子性操作,而不是volatile。
  • 中断或者信号处理器必须使用原子性或者volatile sig_atomic_t 类型的变量,但不是任意类型的volatile变量,以确保多线程执行下的同步。
  • 在内联汇编代码前,也可以使用volatile修饰。

3 不使用volatile可能出现的问题

如果需要使用volatile的时候而没有使用,编译器就会认为该变量不会被当前作用域外的其他实体修改。因此,编译器可能会进行一些用户意想不到的优化操作,可能会导致:

  • 在轮询硬件时,代码可能会陷入循环。
  • 优化可能会导致删除实现故意定时延迟的代码。

4 强制使用特定指令来访问内存

使用volatile关键字声明变量,并不能保证使用特定的机器指令来访问它。比如Cortex®-R7 and Cortex-R8的AXI外设端口是一个64-bit的外设寄存器。这个寄存器必须使用STM(多寄存写入)来写入,而不是STRD或者一对STR指令。不能保证编译器在响应相关变量或指针类型上的volatile修饰时选择该寄存器所需的访问方法。因此还可以使用volatile关键字来修饰内联汇编代码。

__asm__ volatile("stm %1,{%Q0,%R0}" : : "r"(val), "r"(ptr));
__asm__ volatile("ldm %1,{%Q0,%R0}" : "=r"(val) : "r"(ptr));

5 使用volatile和不使用volatile的汇编对比

有如下两段代码:

test1:

int buffer_full;
int read_stream(void)
{int count = 0;while (!buffer_full){count++;}return count;
}

test2:

volatile int buffer_full;
int read_stream(void)
{int count = 0;while (!buffer_full){count++;}return count;
}

test1和test2唯一的区别就是buffer_full变量是否被声明为volatile。buffer_full是一个结束while循环的flag,当buffer_full==true时,停止计数,跳出循环并返回count。我们先看编译结果,再进行分析:

使用 armclang --target=arm-arm-none-eabi -march=armv8-a -Os -S 命令对这两段程序进行编译:

test1:

1 read_stream:
2         movw    r0, :lower16:buffer_full
3         movt    r0, :upper16:buffer_full
4         ldr     r1, [r0]
5         mvn     r0, #0
6 .LBB0_1:
7         add     r0, r0, #1
8         cmp     r1, #0
9         beq     .LBB0_1     ; infinite loop
10         bx      lr

test2:

1 read_stream:
2         movw    r1, :lower16:buffer_full
3         mvn     r0, #0
4         movt    r1, :upper16:buffer_full
5 .LBB1_1:
6         ldr     r2, [r1]     ; buffer_full
7         add     r0, r0, #1
8         cmp     r2, #0
9         beq     .LBB1_1
10         bx      lr

先看test1的汇编程序,第6行到第9行为while循环语句,寄存器r1里的值为buffer_full,进入.LBB0_1的循环后,通过第8行的cmp指令不断比较r1是否为0,以此判断是否要跳出循环。由于test1没有使用volatile关键字声明变量buffer_full,所以在.LBB0_1的循环内一直读取的是寄存器r1的值,如果在轮询的过程中,有其他子程序改变了buffer_full的值,test1也不会跳出循环,因为它并不知道buffer_full已经被更新了。

而在test2程序中,寄存器r1中存储的是buffer_full的地址,r2中存储的是它的值。由于使用了volatile声明,编译器认为该变量是易变的,有被其他子程序更改的风险,所有在第5行到第9行的循环中,每次读取buffer_full的值都是使用 ldr r2, [r1] ,直接加载内存当中的值,而不是如test1中的,读取寄存器的值。所以当外部程序更改了buffer_full的值,test2就能立马读取到真实的buffer_full,从而跳出循环。

6 总结

智能的(进行优化的)编译器可能会把变量的值临时储存在寄存器上,便于下次读取,以节约时间,这个过程被称为高速缓存。但是有一些agent在内存上改变了变量的值,寄存器上的还是旧数据,这样就出错了。如果被volatile 关键字修饰,编译器不会进行高速缓存,直接去内存中读取该变量的数据。

编译器会优化什么

  • 将内存变量缓存到寄存器中。
  • 调整指令顺序,充分利用CPU指令流水线,进行指令重新排序读写指令。

作用

告诉编译器该变量值是不稳定的,可能被更改,需要去内存中读取该值而不是读取寄存器中的备份

  • 多个线程都要用到的某个变量,而且变量的值会被改变
  • 中断服务子程序中访问到的非自动变量
  • 并行设备硬件寄存器的变量(如状态寄存器)

特点

  • 易变的
  • 不可优化,告诉编译器不要对volatile声明的变量进行各种优化保证程序员写在代码中的指令一定会被执行
  • volatile int a;a = 1; 如果未声明为volatile两条代码会合并成一条。
  • 顺序执行的(原子性):保证volatile变量间的顺序性,不会被编译器进行乱序优化
  • 能否和const一起用:可以,const是只读,volatile是去内存中读取
  • 指针可以是volatile
  • 可以修饰函数参数

参考文章:

【基础】C语言中关于关键字volatile的用法 (qq.com)https://mp.weixin.qq.com/s?__biz=MzA3OTM2NzUxOA==&mid=403275343&idx=2&sn=bad67f8825c09561d701388895fb4091&chksm=0249427e353ecb68feed0ed21c2967522996d26e8e337c601f8c19dcdccac31d688e586f9c77&scene=27

Effect of the volatile keyword on compiler optimizationhttps://developer.arm.com/documentation/100748/0620/Writing-Optimized-Code/Effect-of-the-volatile-keyword-on-compiler-optimization?lang=en#a48-effect-of-the-volatile-keyword-on-compiler-optimization__c-code-for-nonvolatile-and-volatile-buffer-loops

ARM嵌入式编译器-volatile关键字对编译器优化的影响相关推荐

  1. volatile关键字对编译器优化的影响

    关注.星标公众号,不错过精彩内容 初学的朋友可能不怎么关心优化选项,但对于经验丰富的工程师来说,掌握代码优化是必备技能. 今天讲述的话题就是关于代码优化中,关键字volatile在优化过程中起到的作用 ...

  2. 【转】volatile关键字。编译器不优化,多线程会改。防止随时变动的

    [转]volatile关键字.编译器不优化,多线程会改.防止随时变动的 来自:http://www.cnblogs.com/yc_sunniwell/archive/2010/07/14/177743 ...

  3. volatile关键字及编译器指令乱序总结

    本文简单介绍volatile关键字的使用,进而引出编译期间内存乱序的问题,并介绍了有效防止编译器内存乱序所带来的问题的解决方法,文中简单提了下CPU指令乱序的现象,但并没有深入讨论. 以下是我搭建的博 ...

  4. ARM 嵌入式系统开发 - 软件设计与优化

    ARM 的非 RISC 特征 允许特定指令的执行周期可变 增加桶形移位器 使用 16 位 Thumb 指令 使用条件执行指令 使用增强 DSP 指令 Jazelle Jazelle 是ARM 引进的第 ...

  5. volatile关键字

    [转自:http://hi.baidu.com/limp_t/blog/item/176bf10e0d65e0c97bcbe187.html] volatile关键字是一种类型修饰符,用它声明的类型变 ...

  6. [C#.NET 拾遗补漏]10:理解 volatile 关键字

    要理解 C# 中的 volatile 关键字,就要先知道编译器背后的一个基本优化原理.比如对于下面这段代码: public class Example {public int x;public voi ...

  7. C语言volatile关键字—最易变的关键字

    volatile 是易变的.不稳定的意思.很多人根本就没见过这个关键字,不知道它的存在.也有很多程序员知道它的存在,但从来没用过它.我对它有种"杨家有女初长成,养在深闺人未识" 的 ...

  8. 《嵌入式linux内存使用与性能优化》读书笔记

    <嵌入式linux内存使用与性能优化>读书笔记 前言 本书的重点分为系统内存和性能优化,前4章着重内存使用,尽量减少进程的内存使用量,定位和发现内存泄漏:后5章着重与如何让系统性能优化,加 ...

  9. java 中violate_Java中的volatile关键字及Cache更新

    Volatile [ˈvɑːlətl],中文解释:反复无常的,易变的,不稳定的. volatile的本意是告诉编译器,此变量的值是易变的,每次读写该变量的值时务必从该变量的内存地址中读取或写入,不能为 ...

最新文章

  1. python画图matplotlib基础笔记
  2. java利用poi读取excel_java利用POI 读取EXCEL
  3. Java用JSONObject-lib来解析json串
  4. linux脚本提示,linux开机提示信息脚本
  5. 如何找到SAP support package名称
  6. 好代码是管出来的——C#的代码规范
  7. html java对象_Java遇见HTML——JSP篇之JSP内置对象(下)
  8. 容器技术Docker K8s 27 容器服务ACK基础与进阶-监控管理
  9. 趣谈网络协议(二)传输层
  10. 我的AI之路(51)--用自己的UCF101数据集训练3D识别模型video-caffe
  11. PySide6精简教程
  12. 使用JSONRPC操作附带token(secret)的aria2
  13. AAAI17最佳论文:Label-Free Supervision of Neural Networks with Physics and Domain Knowledge
  14. [转]高分一号的落后与特色
  15. TeamTalk源码分析之win-client
  16. POJO、JavaBean、EJB的区别
  17. android 布局 字体大小,移动端页面布局及字体大小该如何设置
  18. OSChina 周一乱弹 —— 济南源创会特刊
  19. 前端js实现本地模糊搜索
  20. fabric-java-sdk1.3.0 测试End2endIT

热门文章

  1. seq生成序列-f参数_使用seq()函数在R中生成序列
  2. 达梦使用常见问题解析
  3. 绝版珍藏---.net2.0框架下载
  4. 如何用css实现div高度自适应占满屏幕
  5. Vuex的作用、使用、核心概念(State、Mutations、Getters、Actions、Modules)、文件抽离
  6. ULAM /区块链未来将颠覆的9个行业
  7. 【Day16】移动端布局
  8. 机器学习算法知识总结
  9. python pandas rename_Python3 pandas库(10) 修改列名和索引rename()
  10. CentOS7 mysql启动失败且无log的原因分析