C语言类型限定符(type specifier)(一)——volatile详细教程

前言:C语言有几个类型限定符(type specifier),如C90中的const和volatile,C99中的restrict,C11中的_Atomic,除了const以外,很多书籍对于其他几个修饰符现有提示,即便是有也是一笔带过,当然不是说他们不重要可有可无,主要是我们平时的学习可能很少使用的到,本次系列文章就来专门说一说这几个类型限定符的含义和使用。本文是第一篇,解释volatile的作用。

一、C程序的高速缓存机制

首先我们看一段比较正常的代码

int x=100;
int val1=x;
//这是一段其他代码,但是这段代码没有改变x的值
int val2=x;

我们的编译器是非常智能的,当他遇到上面的代码的时候会进行优化,怎么优化呢?

编译器注意到上面有两次使用了变量x,但是在这两次使用期间,并没有改变它的值,于是编译器会将x的值临时存储在寄存器中,当我们在val2使用x的时候,直接从寄存器中取出x的值,注意,这里不再是从内存中取出x的值给val2,而是直接从寄存器中取出,由于寄存取得读取速度比内存更快,这样可以节约时间。

这样的一个过程称之为“高速缓存(caching)”,高速缓存是一个非常不错的优化方案。

但是,需要注意的是,如果上面在注释部分程序改变了x的值,则不能通过这种方案了,因为如果在val1之后x的值改变了,还从寄存器中读取之前的那个x的值,则不再是改变之后的那个x的值了,而是改变之后的x。但是遗憾的是,我们的编译器虽然很智能,但是也有缺陷,他不知道在这个中间过程x是否改变了。那该怎么做呢?就是下面的volatile关键字的作用了。

二、volatile类型限定符

volatile的意思很简单,是“容易改变”的意思,顾名思义,他的意思就是说这个变量是容易改变的,具体看后面。

2.1 volatile的语法

这个修饰符和const修饰符的使用语法是一样的,请参考const

如下一些常见的用法:

volatile int a; //声明一个易变化的变量a,
int volatile a; //两者等价
volatile int *p; //声明一个指向易变化变量的指针
int volatile *p; //与上面等价
p=&a;

由于类似于const的使用,上面的代码如果将volatile替换成const,称之为“常量指针”,常量指针可以指向常量,也可以指向非常量;同样的道理,我把这个地方称之为“易变量指针”(我个人称谓),指向易改变的变量的指针。

同样的道理,const里面还有一个“指针常量”的概念,即指针是一个常量,不能指向其他的值,也可以有类似的使用,如下:

int * const p;  //指针常量
int * volatile p; //类似于上者,但是很少见。

2.2 为什么要使用volatile呢

在C/C++嵌入式代码中,你是否经历过下面的情况,会遇到一些很奇怪的错误,

  • 代码原本执行正常——直到你打开了编译器优化(比如上面的高速缓存机制),优化之后发现有错误了;
  • 代码原本执行正常——直到打开了中断;
  • RTOS的任务独立运行正常——直到生成了其他任务,使用多个任务的时候又出错了;
  • 古怪的硬件驱动;

如果你的回答是“yes”,很有可能你没有使用C语言关键字volatile,为什么呢?

出现上面这些问题的原因可能是某一个变量被意想不到地改变了,但是我们使用编译器优化的时候,优化器不知道这个变量被莫名其妙的改变了,以为它还没有改变,所以对它进行优化,由于值实际上已经被莫名其妙的改变了,自然会出错嘛。

注意这里什么是莫名其妙、意想不到的改变?

它指的是下面的一些情况:

  • 并行设备的硬件寄存器(如:状态寄存器);
  • 一个中断的服务子程序中会访问到非自动变量(Non-automatic variables);
  • 多线程程序中被几个任务共享的变量;

简而言之就是不是我们显示的去改变了它,一些隐含原因导致了变量的值发生改变,变量可能在你的程序本身不知道的情况下发生改变。

但是像下面由变量所在的程序本身对他进行了改变不算是莫名其妙的改变哦!!!

int x=100;
int val1=x;
x=300;  //由变量所在的程序本身显示改变了x的值,这个不算

int val2=x;

总结:

精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。这是区分C程序员和嵌入式系统程序员的最基本的问题:嵌入式系统程序员经常同硬件、中断、RTOS等等打交道,所有这些都要求使用volatile变量,因为这些变量可能会被无意识的更改。不懂得volatile内容将会带来灾难。

2.3 volatile的使用

volatile的使用很难简单就是用关键字表示一个变量即可,这样明确的告诉编译器,这个变量是容易被莫名其妙改动的,虽然程序本身可能没有显示改变它,所以就不要再优化它了,不要使用寄存器高速缓存了,就这么简单。

注意:

我们我们没有使用编译器优化代码,可能觉得volatile是一个可有可无的东西,的确如此,因为它的的确确体现不出来,只有在进行编译器优化的是后才应该着重考虑volatile的使用。

 

四、volatile的总结

(1)编译器会禁止对volatile修饰的变量进行读写优化。

volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。

(2)每一次读取volatile修饰的变量都会从内存中读取。

volatile应该解释为“直接存取原始内存地址”比较合适,“易变的”这种解释简直有点误导人;“易变”是因为外在因素引起的,像多线程,中断等,并不是因为用volatile修饰了的变量就是“易变”了,假如没有外因,即使用volatile定义,它也不会变化;

 

另外,由于本人没有写过嵌入式程序,所以很多理解可能不是那么深入,有不正确的地方,还望有大佬提醒改正。

 

参考文献:

https://blog.csdn.net/vay0721/article/details/79035854

http://www.sohu.com/a/151096625_777180

https://blog.csdn.net/u013273161/article/details/85640023

 

 

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值