1 unsigned char* volatile reg;
这行代码里reg是指针类型,存储一个指向unsigned char 类型的指针。volatile修饰的是reg这个变量即为指针
变量是volatile的,但是指针指向的unsigne char 内容不是volatile。在实际使用的时候编译器对代码中指针变量reg
本身的操作不会优化,但是对reg所指的内容 "*reg"却会作为no-volatile内容处理,对“*reg”的操作还是会被优化。
通常这种写法一般用在对共享的指针变量上,即这个指针变量有可能会被中断函数修改,将其定义为volatile后编译器
每次取指针变量值的时候都会从内存载入,这样即使这个变量已经被别的程序修改 了当前函数用的时候也能得到修改后
的值(否则通常只在函数开始取一次放在寄存器里,以后就一直使用寄存器内的副本)。
1 volatile unsigned char *reg;
这行代码里volatile修饰的是指针所指的内容,所以这里定义了一个unsigned char类型的指针,并且这个指针指向
的是一个volatile的对象,但是指针变量本身不是volatile。如果对指针变量reg本身进行计算或赋值等操作,是可能被编
编译器优化的。但是对reg所指的内容*reg的引用却禁止编译器优化。因为这个指针所指的是一个volatile的对象,所以
编译器必须保证对*reg的操作都不被优化。通常在驱动程序的开发中,对硬件寄存器指针的定义,都应该采用这种形式。
1 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用法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)) == 0){ //NULL wait till done ; }
通过一个non-volatile的结构体指针,去访问被定义为volatile的结构体成员,编译器将如何处理?答案是:undefined!
C99标准没有对编译器在这种情况下的行为做规定,随意编译器有可能正确的将dvp->car作为volatile的变量来处理,使程序
运行正常;也有可能就将dvp->csr作为普通的non-volatile变量来处理,在while当中优化为只有开始的时候取值一次,以后
以后每次循环始终使用第一次取来的值而不再从硬件寄存器里读取,这样上面的代码就有可能死循环。
如果你使用一个volatile的指针来指向一个非volatile的对象,比如将一个non-volatile的结构体赋给一个volatile的指针,
所以对于本例中的代码,我们应该修改成这样:
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) == 0){ ; }
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)) == 0) { ; }
这样在代码里能绝对保障数据结构的易变性,即使数据结构里面没有定义好也没有关系。而且对于日后的维护也有好处
:因为这样从代码里一眼就能看出那些数据的访问是必须保证volatile的。
例:到底哪个volatile可能无效
就在前面的几个例子,感觉自己可能都已经弄明白了的时候,请最好看这个例子
struct hw_bd{ 。。。。。 volatile unsigned char* volatile buffer; }; struct hw_bd* bdp; ...... bdp->buffer = ...; //1 bdp->buffer = ...; //2
请问上面标记了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的关键字,用起来还是有很多讲究的。