C语言volatile,const关键字的分析

volatile

1.关键字解释:

volatile的本意是“易变的”
因为访问寄存器要比访问内存单元快的多,所以编译器一般都会作减少存取内存的优化,但有可能会读脏数据。当要求使用volatile声明变量值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。精确地说就是,遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问;如果不使用volatile,则编译器将对所声明的语句进行优化。(简洁的说就是:volatile关键词影响编译器编译的结果,用volatile声明的变量表示该变量随时可能发生变化,与该变量有关的运算,不要进行编译优化,以免出错)

一般说来,volatile用在如下的几个地方:
1)中断服务程序中修改的供其它程序检测的变量需要加volatile;
2)多任务环境下各任务间共享的标志应该加volatile;
3)存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能有不同意义。
先不解释为什么在这几个情况下要使用volatile,我们先看一下由来:
硬件
由于内存访问速度远不及CPU,为提高性能,硬件上引入硬件高速缓存Cache,加速对内存的访问。CPU中指令的执行并不一定严格按照顺序执行,如果没有相关性的指令可以乱序执行,以充分利用CPU的指令流水线(梯级),提高执行速度。
软件
一级的优化:一种是在编写代码时由程序员优化,另一种是由编译器优化。编译器优化常用的方法有:将内存变量缓存到寄存器;调整指令顺序充分利用CPU指令流水线,常见的是重新排序读写指令。对常规内存进行优化的时候,这些优化是透明的,而且效率很好。由编译器优化或者硬件重新排序引起的问题的解决办法是在从硬件(或者其他处理器)的角度看必须以特定顺序执行的操作之间设置内存屏障。**void Barrier(void)**这个函数通知编译器插入一个内存屏障,但对硬件无效,编译后的代码会把当前CPU寄存器中的所有修改过的数值存入内存,需要这些数据的时候再重新从内存中读出。
volatile总是与优化有关
编译器有一种技术叫做数据流分析,分析程序中的变量在哪里赋值、在哪里使用、在哪里失效,分析结果可以用于常量合并,常量传播等优化,进一步可以消除一些代码。但有时这些优化不是程序所需要的,这时可以用volatile关键字禁止做这些优化。
2.简单实例解释为什么要使用volatile
1)告诉编译器不能做任何优化

int i;
i=1;
i=2;
i=3;

编译器编译后会被优化为:

int i;
i=3;

造成被赋值的1,2被优化掉,为了保证原意,应该这样:

volatile int i;
i=1;
i=2;
i=3;

2)用volatile定义的变量会在程序外被改变,每次都必须从内存中读取,而不能重复使用放在cache或寄存器中的备份。
条件:在多任务中(多线程中),也就是上面讲的第二点,多线程中存在一个标志位,且都有可能修改

volatile int  a;
a=0;
while(!a){
    
    
do some things;
}
init();

如果其他任务未对a进行修改,则不会执行init()函数,也会按照程序员的要求执行,也无需加上volatile,反之如果不是,在其他任务有修改变量a的值,则需要实时更新寄存器中的数据,必须添加上volatile。
3.对前面使用volatile的三种场景用实例解释:
1)中断服务程序中修改的供其它程序检测的变量需要加volatile

static int a=0;
int main(void)
{
    
    
     while (1){
    
    
if (a) 
dosomething();
              }void IRQ(void)//中断函数
{
    
    
      a=1;
}

程序的本意是希望IRQ中断产生时,在main函数中调用dosomething函数,但是,由于编译器判断在main函数里面没有修改过a,因此可能只执行一次对从a到某寄存器的读操作,然后每次if判断都只使用这个寄存器里面的“a副本”,导致dosomething永远也不会被调用。如果将变量加上volatile修饰,则编译器保证对此变量的读写操作都不会被优化(肯定执行)。
正确的代码如下

volatile static int a=0;
int main(void)
{
    
    
     while (1){
    
    
if (a) 
dosomething();
              }void IRQ(void)//中断函数
{
    
    
      a=1;
}

2)多任务环境下各任务间共享的标志应该加volatile
在上面的实例中已经进行了讲解
3)存储器映射的硬件寄存器,因为每次对它的读写都可能有不同意义。
假设要对一个设备进行初始化,此设备的某一个寄存器地址为0xff800000。

int  *output = (int *)0xff800000;
int   init(void)
{
    
    
      int i;
      for(i=0;i< 5;i++){
    
    
         *output = i;
}
}

经过编译后,编译器认为前面循环半天都没用,对最后的结果毫无影响,因为最终只是将output这个指针赋值为4,所以编译器结果相当于:

int  init(void)
{
    
    
      *output = 4;
}

如果你对此外部设备进行初始化的过程是必须是像上面代码一样顺序的对其赋值,显然优化过程并不能达到目的。反之如果你不是对此端口反复写操作,而是反复读操作,其结果是一样的,编译器在优化后,也许你的代码对此地址的读操作只做了一次。然而从代码角度看是没有任何问题的。这时候就该使用volatile通知编译器这个变量是一个不稳定的,在遇到此变量时候不要优化。

volatile  int *output=(volatile  int *)0xff800000;

以上这三种情分别可以采用这三种方法来屏蔽
1)中可以通过关中断来实现
2)中禁止任务调度
3)中则只能依靠硬件的良好设计。
问题来了
1)一个参数既可以是const还可以是volatile?
可以,例如只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
被volatile这个关键字修饰的变量在每次访问的时候都要去相应内存地址去找,因为随时可能被修改。被const修饰只能说明这个不能被程序员修改,但可能被系统所修改。
2) 一个指针可以是volatile ?
可以,当一个中服务子程序修该一个指向一个buffer的指针时。
4.volatile的本质:
1)在本次线程内, 当读取一个变量时,为提高存取速度,编译器优化时有时会先把变量读取到一个寄存器中;以后,再取变量值时,就直接从寄存器中取值;当变量值在本线程里改变时,会同时把变量的新值copy到该寄存器中,以便保持一致。

2)当变量在因别的线程等而改变了值,该寄存器的值不会相应改变,从而造成应用程序读取的值和实际的变量值不一致。

3)当该寄存器在因别的线程等而改变了值,原变量的值不会改变,从而造成应用程序读取的值和实际的变量值不一致。
再次举例论证

int square(volatile int *ptr)
{
    
    
return *ptr * *ptr;
}

目的是用来返回指针ptr指向值的平方,由于ptr指向一个volatile型参数,编译器将产生类似下面的代码:

int square(volatile int *ptr)
{
    
    
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}

由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的(如果没有其他线程或则外部条件改变 *ptr,那么这句话就没错!)。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:

int square(volatile int *ptr)
{
    
    
int a;
a = *ptr;
return a * a;
}

注意:频繁地使用volatile很可能会增加代码尺寸和降低性能,因此要合理的使用volatile。

总结:
volatile变量有两个作用:
1)告诉编译器不要进行优化;
2)告诉系统始终从内存中取变量的地址,而不是从缓存或寄存器中取变量的值(加volatile和不加volatile系统都会产生缓存)

const

解释
关键字const用来定义常量,如果一个变量被const修饰,那么它的值就不能再被改变,可以保护被修饰的东西,防止意外修改,增强程序的健壮性
与预编译指令相比,const修饰符有以下的优点:
1)预编译指令只是对值进行简单的替换,不能进行类型检查
针对检查:
看看const用于修饰常量静态字符串,例如:
const char* str=“fdsafdsa”;
如果没有const的修饰,我们可能会在后面有意无意的写str[4]=’ x’这样的语句,这样会导致对只读内存区域的赋值,然后程序会立刻异常终止。这个错误就能在被编译的时候就检查出来,这是用const的好处。让逻辑错误在编译期被发现。

2)可以保护被修饰的东西,防止意外修改,增强程序的健壮性
3)编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。
4)宏定义的作用范围仅限于当前文件。 默认状态下,const对象只在文件内有效,当多个文件中出现了同名的const变量时,等同于在不同文件中分别定义了独立的变量。如果想在多个文件之间共享const对象,必须在变量定义之前添加extern关键字(在声明和定义时都要加)。
1.const修饰变量

const int n=1;
int const n=1;

这两种写法是一样的,都是表示变量n的值不能被改变了,需要注意的是,用const修饰变量时,一定要给变脸初始化,否则之后就不能再进行赋值了。
2.常量指针与指针常量
可以如下表示

const int * n;
int const * n;

1)常量指针说的是不能通过这个指针改变变量的值,但是还是可以通过其他的引用来改变变量的值的。

int i=5;
const int* n=&i;
i=6;

常量指针指向的值不能改变,但是这并不是意味着指针本身不能改变,常量指针可以指向其他的地址。

int a=5;
int b=6;
const int* n=&a;
n=&b;

2)指针常量是指指针本身是个常量,不能在指向其他的地址

int *const n;

需要注意的是,指针常量指向的地址不能改变,但是地址中保存的数值是可以改变的,可以通过其他指向改地址的指针来修改。区分常量指针和指针常量的关键就在于星号的位置,我们以星号为分界线,如果const在星号的左边,则为常量指针,如果const在星号的右边则为指针常量。如果我们将星号读作‘指针’,将const读作‘常量’的话,内容正好符合。

int const*n;是常量指针
int*const n;是指针常量

指向常量的常指针是以上两种的结合,指针指向的位置不能改变并且也不能通过这个指针改变变量的值,但是依然可以通过其他的普通指针改变变量的值。

const int* const p;

3)修饰函数的参数
1)防止修改指针指向的内容
2)防止修改指针指向的地址
3)以上两种的结合。
4)修饰函数的返回值
如果给以“指针传递”方式的函数返回值加 const 修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const 修饰的同类型指针。
5)修饰全局变量
全局变量的作用域是整个文件,我们应该尽量避免使用全局变量,因为一旦有一个函数改变了全局变量的值,它也会影响到其他引用这个变量的函数,导致除了bug后很难发现,如果一定要用全局变量,我们应该尽量的使用const修饰符进行修饰,这样防止不必要的人为修改,使用的方法与局部变量是相同的。
3.使用const的一些建议
1)要大胆的使用const,这将给你带来无尽的益处,但前提是你必须搞清楚原委;
2)要避免最一般的赋值操作错误,如将const变量赋值,具体可见思考题;
3)在参数中使用const应该使用引用或指针,而不是一般的对象实例,原因同上;
4)const在成员函数中的三种用法(参数、返回值、函数)要很好的使用;
5)不要轻易的将函数的返回值类型定为const;
6)除了重载操作符外一般不要将返回值类型定为对某个对象的const引用;
7)任何不会修改数据成员的函数都应该声明为const 类型。

const与volatile总结

1.一个对象可以同时被const和volatile修饰,表明这个对象体现常量语义,但同时可能被当前对象所在程序上下文意外的情况修改。
2.被volatile这个关键字修饰的变量在每次访问的时候都要去相应内存地址去找,因为随时可能被修改。被const修饰只能说明这个不能被程序员修改,但可能被系统所修改。
3.const和volatile是类型修饰符,语法类似,用于变量或函数参数声明,也可以限制非静态成员函数。用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问,阻止编译器调整操作volatile变量的指令顺序。当要求使用volatile声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。
4.volatile 指针: 1、修饰由指针指向的对象、数据是 const 或 volatile 的:const char* cpch; volatile char * vpch;;2、指针自身的值——一个代表地址的整数变量,是 const 或 volatile 的:char* const pchc; char* volatile vchc;。

猜你喜欢

转载自blog.csdn.net/weixin_42271802/article/details/105973024