C关键字volatile总结
做嵌入式C开发的相信都使用过一个关键字volatile,特别是做底层开发的。假设一个GPIO的数据寄存器地址是0x50000004,我们一般会定义一个这样的宏:
#define GDATA *((volatile unsigned int*)0x50000004)
在面试的时候也会被问到过volatile关键字起什么作用?
网络上的回答一般是防止被编译器优化,或者还会加一点就是访问被volatile修饰的变量时,强制访问内存中的值,而不是缓存中的。
我对上面的回答一直存在误解,以为是:因为被编译器优化,所以导致存取变量时是存取变量在cache中的缓存。
volatile官方说明
volatile
Indicates that a variable can be changed by a background routine.
Keyword volatile is an extreme opposite of const. It indicates that a variable may be changed in a way which is absolutely unpredictable by analysing the normal program flow (for example, a variable which may be changed by an interrupt handler). This keyword uses the following syntax:
volatile data-definition;
Every reference to the variable will reload the contents from memory rather than take advantage of situations where a copy can be in a register.
volatile
表明变量能被后台程序修改
关键字volatile和const是完全相反的。它表明变量可能会通过某种方式发生改变,而这种方式是你通过分析正常的程序流程完全预测不出来的。(例如,一个变量可能被中断处理程序修改)。关键字使用语法如下:
volatile data-definition;
每次对变量内容的引用会重新从内存中加载而不是从变量在寄存器里面的拷贝加载。
我的理解:以中断处理程序修改变量解释可能不太合适,以GPIO为例最合适。首先什么是变量?变量就是一块编了地址的内存区域。GPIO的数据寄存器有一个地址,大小一般为32bit,所以这个数据寄存器可以认为就是一个变量,我们可以读写它。如果GPIO设置为输入,修改GPIO数据寄存器这个变量的就是这个GPIO的引脚,不管你如何分析你的程序,你不可能知道这个GPIO数据寄存器里面值是多少,你得读它。你此刻读到数据和下一刻读到的完全可能是不一样的。简单的说就是你要的数据不同步。使用volatile修饰后,会强制你每次引用GPIO寄存器对应的变量时都会去它的寄存器里面读。
为了搞清楚volatile到底是怎么影响编译器行为的,需要以具体的例子来说明:
防止编译器优化掉操作变量的语句
下面是a.c文件:
int main(void)
{
volatile char a;
a = 5;
a = 7;
return 0;
}
下面是b.c文件:
int main(void)
{
char a;
a = 5;
a = 7;
return 0;
}
进行编译,分别得到它们对应的汇编文件:
$ make
arm-linux-gcc -S a.c -o a.s
arm-linux-gcc -S b.c -o b.s
对比a.s、b.s:
vimdiff a.s b.s

可以看到无任何差异,为什么呢?因为我们gcc的优化等级是默认等级,现在把优化等级调至-O3。再看对比
$ make
arm-linux-gcc -O3 -S a.c -o a.s
arm-linux-gcc -O3 -S b.c -o b.s

可以看到未加volatile修饰的文件b.c,在优化后,汇编对应的a=5;a=7;这两个语句直接优化没了。a=1;a=0;假设a是控制GPIO的语句,原来打算是让GPIO先拉高,再拉低,实现某种时序,结果优化一开,这两句直接废了。这让你在调试硬件的时候会感到莫名其妙。所以这种情况得像a.c那样用volatile来修饰。
这里是防止编译器优化掉相关语句,而不是优化变量的存取对象(memory or register)。
防止编译器优化变量的存取对象(memory or register)
a.c
int main(void)
{
int b;
int c;
volatile int* a = (int*)0x30000000;
b = *a;
c = *a;
return c + b;
}
b.c
int main(void)
{
int b;
int c;
int* a = (int*)0x30000000;
b = *a;
c = *a;
return c + b;
}

a.s
mov r3, #805306368
ldr r2, [r3] @ b = *a;
ldr r0, [r3] @ c = *a;
add r0, r0, r2 @ b + c;
bx lr
b.s
mov r3, #805306368
ldr r0, [r3] @ b = *a;
mov r0, r0, asl #1 @ b << 2; 也就是 b * 2;也就是 b + b;也就是 add r0, r0, r0(可能这句汇编不合法)
bx lr
可以看到b.s被优化后,第一次取*a的值时,是从地址0x30000000取出来的(ldr r0, [r3]),第二次就直接没取了,是直接使用了r0的值。这里的r0就是*a的缓存。我之前在这里的理解存在很大的错误:
访问被volatile修饰的变量时,强制访问内存中的值,而不是缓存中的。
以为这里的缓存指cache。
毕竟从汇编指令优化着手怎么可能控制变量的一定从cache里面存取。
你不能假定一个共享(GPIO引脚和你的程序共享了GPIO数据寄存器)的变量只会被你修改。就好比你读一个文件的内容,过10秒后,你不能假定文件内容没变,必须要重新读取文件内容。
注意事项
volatile关键词影响编译器编译的结果,用volatile声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不要进行编译优化,以免出错
一个例子:
int main(void)
{
int b;
volatile int* a = (int*)0x30000000;
b = (*a) * (*a);
return b;
}
汇编后
mov r3, #805306368
ldr r2, [r3] ①
ldr r0, [r3] ②
mul r0, r2, r0
bx lr
程序本意是要计算平方。如果这段代码在运行至①这行汇编时,被调度开了,过了一阵调度回来继续运行②行,此时完全有可能 R2 != R0。那么计算出来的结果R0必然不等于那个平方值。
C关键字volatile总结的更多相关文章
- [CareerCup] 13.5 Volatile Keyword 关键字volatile
13.5 What is the significance of the keyword "volatile" in C 这道题考察我们对于关键字volatile的理解,顾名思义, ...
- C语言中关键字volatile的含义【转】
本文转载自:http://m.jb51.net/article/37489.htm 本篇文章是对C语言中关键字volatile的含义进行了详细的分析介绍,需要的朋友参考下 volatile 的意思是“ ...
- C语言关键字-volatile
1.C语言关键字volatile C 语言关键字volatile(注意它是用来修饰变量而不是上面介绍的__volatile__)表明某个变量的值可能在外部被改变,因此对这些变量的存取 不能缓存 ...
- Java 并发 关键字volatile
Java 并发 关键字volatile @author ixenos volatile只是保证了共享变量的可见性,不保证同步操作的原子性 同步块 和 volatile 关键字机制 synchroniz ...
- JAVA关键字Volatile的特性
一.简述: 关键字Volatile是JAVA虚拟机提供的最轻量级的同步机制,但是它并不容易完全被正确.完整的理解,以致于许多程序员在遇到需要处理多线程数据竞争的时候一律使用synchronized来进 ...
- 2.3.1关键字volatile与死循环
关键字volatile的主要作用是使变量在多个线程间可见. 测试如下 package com.cky.test; /** * Created by edison on 2017/12/9. */ pu ...
- 话说C语言的关键字volatile
最近搞NVMe驱动需求分析,对volatile这个单词实在是再熟悉不过了. 而在C语言中,有一个关键字就叫做volatile, 其字面意思是"挥发性的, 不稳定的,可改变的". 那 ...
- Java并发—— 关键字volatile解析
简述 关键字volatile可以说是Java虚拟机提供的最轻量级的同步机制,当一个变量定义为volatile,它具有内存可见性以及禁止指令重排序两大特性,为了更好地了解volatile关键字,我们可以 ...
- java多线程关键字volatile的使用
java多线程关键字volatile的作用是表示多个线程对这个变量共享. 如果是只读的就可以直接用,写数据的时候要注意同步问题. 例子: package com.ming.thread.volatil ...
随机推荐
- python 银行系统
目前代码只写到这 主要部分已经实现 功能部分展现 首先我们需要五个类 用户类 : 成员属性 name id 以及 card 卡类: 成员属性 卡号 密码 余额 锁 界面类: 管理员界 ...
- less-7
题目是要求导出文件GET字符型注入 看看代码 这里可以使用报错注入 先按要求用导出文件做 导出文件就是可以向服务器写入文件,但是利用的时候要知道数据库,网站的路径 我们现在less-1查看 www目录 ...
- 2016-2017-2 20155322 实验三 敏捷开发与XP实践
2016-2017-2 20155322 实验三 敏捷开发与XP实践 实验内容 XP基础 XP核心实践 相关工具 实验知识点 敏捷开发(Agile Development)是一种以人为核心.迭代.循序 ...
- [arc068E]Snuke Line-[树状数组]
Description 传送门 Solution 假如想直接YY对于每一个d会有多少种商品满足条件,em反正我搞不定. 然后大佬的题解告诉我说:搞不定?那就不搞它啊,反过来不就得了? 好吧.我们来考虑 ...
- RHCE模拟考试
真实考试环境说明: 你考试所用的真实物理机器会使用普通账号自动登陆,登陆后,桌面会有两个虚拟主机图标,分别是system1和system2.所有的考试操作都是在system1和system2上完成.S ...
- OpenStack入门篇(三)之KVM介绍及安装
1.什么是虚拟化? 虚拟化是云计算的基础.简单的说,虚拟化使得在一台物理的服务器上可以跑多台虚拟机,虚拟机共享物理机的 CPU.内存.IO 硬件资源,但逻辑上虚拟机之间是相互隔离的. 物理机我们一般称 ...
- Entity Framework中执行Sql语句
如果想在EF框架中执行Sql语句,其实很简单,EF里面已经提供了相关的方法(此处使用的EF为EF4.1版本). EF中提供了两个方法,一个是执行查询的Sql语句SqlQue ...
- SQL注入--显注和盲注中过滤逗号绕过
SQL注入逗号绕过 1.联合查询显注绕过逗号 在联合查询时使用 UNION SELECT 1,2,3,4,5,6,7..n 这样的格式爆显示位,语句中包含了多个逗号,如果有WAF拦截了逗号时,我们的联 ...
- 获取安卓app的appPackage和appActivity
1.需要配置好android的开发环境后,打开cmd命令窗口 2.在命令窗口中输入,adb logcat>D:/log.log,抓取日志 3.运行启动app 4.查看日志log 5.搜索日志的关 ...
- 学习笔记之ubuntu修改固定IP脚本
一.shell脚本编程 二.正则表达式 三.linux修改IP的方法 #!/bin/bash cd /etc/network/ stty erase '^?' write_interfaces() { ...