C语言 const、volatile、const volatile限定符理解

x33g5p2x  于2021-10-28 转载在 其他  
字(1.9k)|赞(0)|评价(0)|浏览(567)

在C语言经常会用到变量类型限定符const和volatile,但是const和 volatile也可以一起使用。下面就一次分析一下这三种情况的使用方法。

1、const

从字面意思理解,意思就是常量,常数,也就是不能通过赋值、递增、递减来修改它的值。

对于普通变量来说,它的使用很简单。比如定义了一个变量
const int i = 10;

那么在代码中只能使用 i 的值,而不能修改 i 的值。

对于指针来说,它的使用就会稍微复杂一点。
const int /*p;

这个意思就是指针 p 指向的值不能被修改,但是指针本身的值可以修改,比如可以把指针 p 指向另一个地方。

int /* const p;

这个意思是指针本身不能修改,也就是 p 不能指向其他地址,但是指针 p 指向的值可以改变。

const int /* const p;

这个意思是指针本身和指针指向的值都不能改变。

上面这几种情况理解起来比较绕,可以使用一个简单的方法来判断:只要const 在 /* 号的左边,表示指针指向的值不能改变。只要const在 /* 号的右边,就表示指针本身不能改变。,也就是指针必须指向同一个地址。

下面通过一个简单的示例来看const的使用,将整形变量 i 设置为const 类型,然后打印 i 的值。

i 的值可以正常打印。下面给 i 的值自加 1。

  此时编译器报错,提示 对一个只读类型的变量 i 的进行了递增操作。但是也不能将const修饰的变量都理解为只读类型,有时候const限定的值也是可以修改的,这个后面再讲。

2、volatile

从字面意思理解,意思就是不稳定的,易变的。用这个限定符主要是给编译器说的。告诉编译器我这个值是容易改变的,在优化代码的时候不能优化。

比如有下面的一种情况:
val1 = x;
val2 = x;

编译器在编译时看到了有两个变量都使用了x,但是x的值并没有发生改变。于是编译器第一次把x的值取出来之后,临时存放到了寄存器中,当变量val2需要使用x的使用,编译器直接从寄存器中读取x的值,而不是从存储x的原始位置直接读取。如果这个变量 x 里面存储的是外部传感器的值,那么使用上面的语句,如果被编译器优化了,那么读出来传感器的值就是不变的。为了解决这个问题就需要给变量前面加上volatile进行修饰。

volatile val1 = x;
volatile val2 = x;

这样当编译器编译的时候,看到val1和val2都被 volatile 修饰了,当val1和val2读取x的值时,它就会从x原始存储位置去读取。这样当 x 的值为外部上拉传感器的值时,每次读取都是真正有效的值。

3、const volatile

从上面可以知道 const 修饰符表示常量,volatile 修饰符表示易变的,那么两个结合起来用的话,意思是易变的常量吗? 难道const修饰了之后,值还可以改变吗?答案是可以改变。const修饰的对象在它的作用域之内不能够被改变,但是在作用域之外可以被改变。这个要怎么理解呢?

还是使用上面的例子
const val1 = x;

x 中存储的传感器的数据,在程序中不能通过代码去改变val1的值,但是传感器的值改变后,val1的值也会改变。这个理解起来比较抽象,那么就用一个最常见的例子来演示,比如现在要读取系统的时间。

  这里使用了一个用const来修饰的对象 rawtime,它里面存储的是当前系统的时间。系统时间是随时会发生变化的,但是依然可以用 const 来修饰,同时系统时间改变后 rawtime 的值也发生了改变。那么const用来限定什么呢? 比如在程序中手动给 rawtime 赋值。

  此时编译器会提示 rawtime 是被 const 修饰的,不能更改它的值。这里就是上面说的作用域的问题,const修饰的rawtime 在作用域内,也就是当前main函数之内是不能被修改的,但是在这个函数之外,在系统运行的时候,这个 rawtime 的值是可以被系统修改的。

当在加上修饰符 volatile时,就表示这个对象在作用域内不能被修改,但是在作用域外是易变的,每次读取的时候,需要从原始位置读取。volatile 和 const 一起使用时位置无所谓,这两个不论哪个在前哪个在后,都是一样的。

  比如这里依然是读取系统时间,第一次读取时间之后,延时3s再读取一次时间。通过打印的结果可以看出,系统时间也相差了3s。这里就使用 volatile 和 const 一起修饰 rawtime,就是告诉编译器,这个对象在它的作用域内不能被修改,但是在作用域外是易变的。当需要读取的时候,不需要编译器优化,直接从原始地址读取。

通过上面的三个例子应该就能明白了const、volatile、const volatile这三个限定符的使用环境和含义了。

相关文章