对于volatile关键字,大部分C语言的教程都是一笔带过,并没有做太深入的分析,所以这里简单的整理了一些

关于volatile的使用注意事项。实际上从语法上来看volatile和const是一样的,但是如果const用错,几乎不会有什
么问题,而volatile用错,后果可能很严重。所以在volatile的使用上建议大家还是尽量求稳,少用一下没有切实把
握的技巧。
 
  首先看下面两个定义的区别:
   unsigned char* volatile reg;

  这行代码里reg是指针类型,存储一个指向unsigned char 类型的指针。volatile修饰的是reg这个变量即为指针

变量是volatile的,但是指针指向的unsigne char 内容不是volatile。在实际使用的时候编译器对代码中指针变量reg

本身的操作不会优化,但是对reg所指的内容 "*reg"却会作为no-volatile内容处理,对“*reg”的操作还是会被优化。

通常这种写法一般用在对共享的指针变量上,即这个指针变量有可能会被中断函数修改,将其定义为volatile后编译器

每次取指针变量值的时候都会从内存载入,这样即使这个变量已经被别的程序修改 了当前函数用的时候也能得到修改后

的值(否则通常只在函数开始取一次放在寄存器里,以后就一直使用寄存器内的副本)。

     volatile unsigned char *reg;

  这行代码里volatile修饰的是指针所指的内容,所以这里定义了一个unsigned char类型的指针,并且这个指针指向

的是一个volatile的对象,但是指针变量本身不是volatile。如果对指针变量reg本身进行计算或赋值等操作,是可能被编

编译器优化的。但是对reg所指的内容*reg的引用却禁止编译器优化。因为这个指针所指的是一个volatile的对象,所以

编译器必须保证对*reg的操作都不被优化。通常在驱动程序的开发中,对硬件寄存器指针的定义,都应该采用这种形式。

     volatile unsigned char * volatile reg

  这样定义出来的指针本身就是个volatile变量,又指向了volatile的数据内容。

---------------------------------------------【分割线】----------------------------------------------------

volatile 和 const 的合用

  从字面上看volatile 和 const 似乎是一个对象的两个对立属性,是互斥的。但实际上,两者是有可能一起修饰同一个

对象的。看看下面的这行声明。

    extern const volatile unsigned int rt_clock;

  这个是在RTOS系统内核中常见的一种声明:rt_clock通常是指系统时钟,他经常被时钟中断进行更新,所以他是volatile

的,易变的。因此在用的时候要让编译器每次从内存里面取值。而rt_clock通常只有一个写者(时钟中断),其他地方的使用

通常是只读的,所以将其声明为const,表示这里不应该修改这个变量。所以volatile和const是两个不矛盾的东西,并且一个

对象同时具备这两种属性也是具有实际意义的。

  注意:上面这个例子里要注意声明和定义是const的使用,在需要读写rt_clock变量的中断函数里应该如下定义

//在需要读写rt_clock变量的中断函数里如下定义
unsigned char* volatile reg;
//在提供给用户的声明头文件中可以如此申明
external const volatile unsigned int rt_clock;
  再看一个例子
volatile struct deveregs* const dvp = DEVADDR;

  这里的volatile和const实际上是分别修饰了两个不同的对象:volatile修饰的是指针dvp所指向的类型为struct devregs

的数据结构,这个结构对应设备的硬件寄存器,所以是易变的,不能被优化的;而后面的const修饰的是指针变量dvp。应为硬

件寄存器地址是一个常量,所以这个指针变量定义成const的不能被修改。

---------------------------------------------【分割线】----------------------------------------------------

危险的volatile用法

 
例:定义为volatile的结构体成员
 
  考察下面对一个设备硬件寄存器的结构类型定义:
struct deveregs{
unsigned short volatile csr;
unsigned short const volatile data;
};

  我们的原意是希望声明一个设备的硬件寄存器组,其中有一个16bit的CSR寄存器,这个寄存器可以由程序向设备写入

控制字,也可以由硬件设备设置反应其工作状态。另外还有一个16bit的data数据寄存器,这个寄存器只会有硬件进行设置

由程序进行读入。

  看起来这个结构的定义没有什么问题,也符合实际情况。但是如果执行下面这样的代码时,会发生什么情况呢。

struct deveregs* const dvp = DEVADDR;
while((dvp->csr & (READY | ERROR)) == ){
//NULL wait till done
;
}

  通过一个non-volatile的结构体指针,去访问被定义为volatile的结构体成员,编译器将如何处理?答案是:undefined!

C99标准没有对编译器在这种情况下的行为做规定,随意编译器有可能正确的将dvp->car作为volatile的变量来处理,使程序

运行正常;也有可能就将dvp->csr作为普通的non-volatile变量来处理,在while当中优化为只有开始的时候取值一次,以后

以后每次循环始终使用第一次取来的值而不再从硬件寄存器里读取,这样上面的代码就有可能死循环。

  如果你使用一个volatile的指针来指向一个非volatile的对象,比如将一个non-volatile的结构体赋给一个volatile的指针,

这样对volatile指针所指向结构体的使用都会被编译器认为是volatile的,计算原本那个对象没有被声明volatile。然而反过来,
如果将一个volatile对象的地址赋值给一个non_volatile的普通变量,通过这个指针访问volatile对象的结果是undefined,是
危险的。

所以对于本例中的代码,我们应该修改成这样:

struct devregs{
unsigned short csr;
unsigned short data;
}; volatile struct devrges* const dvp = DEVADDE;

  这样我们才能保证通过dvp指针去访问结构体成员的时候都是volatile来处理的。

例:定义为volatile的结构体类型

考察如下代码:

volatile struct devregs{
.....
}dev1;
.....
struct devregs dev2;

作者目的也许是希望定义一个volatile的结构体类型,然后顺便定义一个这样的volatile结构体变量,

因此定义了一个dev2.然而第二次所定义的dev2变量实际上是non-volatile的!因为实际在定义结构

体类型时那个关键字volatile修饰的是dev1这个变量而不是struct devregss类型的结构体

所以这个代码应该写成这样:

typedef volatile struct devres{
。。。
}devregs_t; devregs_t dev1;
..
devregs_t dev2;

这样我们才能得到两个volatile的结构体变量。

例:多次的间接指针引用

考察如下代码:

struct bd{
unsigned int state ;
  unsigned char* data_buff;
};
struct devregs{
unsigned int csr;
struct bd* tx_bd;
struct bd* rx_bd;
}; volatile struct devregs* const dvp = DEVADDR; dvp->tx_bd->state = READY;
while((dvp->tx_bd->state & EMPTY | ERROR) == ){
;
}

  这样的代码常用在对一些DMA设备的发送buffer上。通常这些buffer descriptor(BD)当中的状态会由硬件进行

设置已经告诉软件buffer是否完成发送或接收。但是请注意上面的代码中对dvp->ta_bd->state的操作实际上是non-volatile
的,这样的操作有可能因为编译器对其读取的优化而导致后面陷入死循环。
 
  因为虽然dvp已经定义volatile的指针了,但是也只有向其指向的deVregs结构才属于volatile object的范围,也就
是说将dvp声明为指向volatile数据的指针可以保障其所指的volatile object之内的tx_bd这个结构体成员自身是volatile
变量,但是并不能保证这个指针变量所指的数据也是volatile的(因为这个指针并没有声明为指向volatile的指针)。
  要让上面的代码工作如下定义
struct devregs{
unsigned int csr;
volatile struct bd* tx_bd;
volatile sturct bd* rx_bd;
};

这样可以保障对state成员的处理是volatile的。不过最为稳妥和清晰的办法是这样

volatile struct devregs* const dvp = DEVADDR;
volatile struct bd* tx_bd = dvp->tx_bd; tx_bd->state = READY;
whlie((tx_bd->state &(EMPTY | ERROR)) == ) {
;
}

这样在代码里能绝对保障数据结构的易变性,即使数据结构里面没有定义好也没有关系。而且对于日后的维护也有好处

:因为这样从代码里一眼就能看出那些数据的访问是必须保证volatile的。

例:到底哪个volatile可能无效

就在前面的几个例子,感觉自己可能都已经弄明白了的时候,请最好看这个例子

struct hw_bd{
。。。。。
volatile unsigned char* volatile buffer;
}; struct hw_bd* bdp;
......
bdp->buffer = ...; //
bdp->buffer = ...; //

请问上面标记了1和2的代码哪个是确实在访问volatile对象,而哪个是undefined的结果。

答案是2是volatile的1是undefined的。来看本例数据结构示意图

(non-volatile)

bdp->+---------+

    |              |

|  ...  ...    |

|              |

+---------+ (volatile)

|   buffer   |-->+----------+

+---------+     /               /

/               /

/               /

+----------+

/   buffer    /

+----------+

/               /

/               /

+----------+

buffer成员本身是通过一个non-volatile的时钟bdp访问的,按照C99标准的定义,这就属于

undefined的情况,因此对bdp->buffer的访问编译器不一定能保证是volatile的;虽然buffer

本身可能不是volatile的变量,但是buffer成员是一个指向volatile对象的指针。因此对buffer

成员所指向对象的访问编译器可以保证是volatile的,所以bdp->buffer是volatile的。

所以,看似简单的volatile的关键字,用起来还是有很多讲究的。

volatile的陷阱的更多相关文章

  1. Java多线程编程的常见陷阱(转)

    Java多线程编程的常见陷阱 2009-06-16 13:48 killme2008 blogjava 字号:T | T 本文介绍了Java多线程编程中的常见陷阱,如在构造函数中启动线程,不完全的同步 ...

  2. C/C++ Volatile关键词深度剖析(转)

    本文转载自博文C/C++ Volatile关键词深度剖析. 背景 前几天,发了一条如下的微博 (关于C/C++ Volatile关键词的使用建议): 此微博,引发了朋友们的大量讨论:赞同者有之:批评者 ...

  3. C++——volatile关键字的学习

    首先声明一点,本文是关于volatile关键字的学习,学习内容主要是来自一些大牛的网络博客. 一篇是何登成先生的C/C++ Volatile关键词深度剖析(http://hedengcheng.com ...

  4. c++Volatile关键词

    看到的一篇文章觉得还不错吧,文章具体位置也找不到了,复制一下,留着日后复习 背景 此微博,引发了朋友们的大量讨论:赞同者有之:批评者有之:当然,更多的朋友,是希望我能更详细的解读C/C++ Volat ...

  5. volatile关键字的作用、原理

    在只有双重检查锁,没有volatile的懒加载单例模式中,由于指令重排序的问题,我确实不会拿到两个不同的单例了,但我会拿到"半个"单例. 而发挥神奇作用的volatile,可以当之 ...

  6. C/C++ Volatile关键词深度剖析

    文章来源:http://hedengcheng.com/?p=725 背景 此微博,引发了朋友们的大量讨论:赞同者有之:批评者有之:当然,更多的朋友,是希望我能更详细的解读C/C++ Volatile ...

  7. 面试题:volatile关键字的作用、原理

    在只有双重检查锁,没有volatile的懒加载单例模式中,由于指令重排序的问题,我确实不会拿到两个不同的单例了,但我会拿到“半个”单例. 而发挥神奇作用的volatile,可以当之无愧的被称为Java ...

  8. Java并发的若干基本陷阱、原理及解决方案

    勿止于结论:持续探索与求证. 概述 为什么要使用并发 ? 有三点足够信服的理由: 性能提升.单核 CPU 的性能基本抵达瓶颈,充分挖掘多核 CPU 的能力,使得性能提升变成水平可扩展的. 事件本质.世 ...

  9. 深入浅出 Java Concurrency (39): 并发总结 part 3 常见的并发陷阱

    常见的并发陷阱 volatile volatile只能强调数据的可见性,并不能保证原子操作和线程安全,因此volatile不是万能的.参考指令重排序 volatile最常见于下面两种场景. a. 循环 ...

随机推荐

  1. 好吧,我承认我是爱瞎折腾----利用YDUI改变页面UI

    上周恒丰代付接口上线投产后,我做了一个“恒丰代付检查工具”,用途是,当线上调用恒丰代付出现了问题订单时,可以在这个工具页里做相应的弥补. 我项目里其他一些工具页的UI用的是YDUI.YDUI号称是“一 ...

  2. 浅析Web API中FromBody属性

    比较如下两段代码及测试结果: public class ValuesController : ApiController { // POST api/<controller> public ...

  3. CSU 1817 Bones’s Battery Submit(二分+Floyd)

    Bones's Battery Submit [题目链接]Bones's Battery Submit [题目类型]二分+Floyd &题意: 有n个点,m条边,从点ui到点vi的费电量是di ...

  4. jar包的读取

    昨天在做项目插件的时候,因为会用到jar包中的一个文件来初始化程序.并且以后还是会访问这个文件,所以就想到干脆吧文件拷贝到指定目录.在拷贝的时候也费了好一会时间,这里涉及到了jar文件的操作,在这里记 ...

  5. net npoi将List<实体>导出excel的最简单方法

    只是临时导数据用的.方便.最基本的方法, [HttpGet] [Route("ExportEnterprise")] public BaseResponse ExportEnter ...

  6. Unity shader学习之屏幕后期处理效果之高斯模糊

    高斯模糊,见 百度百科. 也使用卷积来实现,每个卷积元素的公式为: 其中б是标准方差,一般取值为1. x和y分别对应当前位置到卷积中心的整数距离. 由于需要对高斯核中的权重进行归一化,即使所有权重相加 ...

  7. html中通过js获取接口JSON格式数据解析以及跨域问题

    前言:本人自学前端开发,一直想研究下js获取接口数据在html的实现,顺利地找到了获取数据的方法,但是有部分接口在调用中出现无法展示数据.经查,发现时跨域的问题,花费了一通时间,随笔记录下过程,以方便 ...

  8. jQuery筛选--hasClass(class)和eq(index|-index)

    hasClass(class) 概述 检查当前的元素是否含有某个特定的类,如果有,则返回true 参数 class  用于匹配的类名 <!DOCTYPE html> <html> ...

  9. django中orm的批量操作

    ORM批量操作 数据模型定义 from django.db import models class Product(models.Model): name = models.CharField(max ...

  10. 20165305 实验二:Java面向对象程序设计

    2-1 参考 http://www.cnblogs.com/rocedu/p/6371315.html#SECUNITTEST 参考http://www.cnblogs.com/rocedu/p/67 ...