const、#define和volatile的总结

1.const:代表着只读,不允许被更改,但是有可能被其他程序更改(可以通过指针修改局部的const变量但是不能修改全局的

上面这个定义给人模糊的感觉,不防先看一个例子

const、#define和volatile的总结_第1张图片

编译结果:

可以看到编译的结果,显示max变量是read-only(只读)的,无法再次给该变量进行左值运算,但是把这个const变量的地址给一个指针,可以通过该指针来修改这个值。

虽然可以做,但是程序员的使用这个限定符初衷是不允许该变量被改变,所以最好不要进行这种操作

再来看几个例子:

const int p;//代表p只读,不能对p进行再一次赋值

int const p;//和上面是一样的

int const *p;//代表指针p是可变,它可以指向其他地方,但是指向的内容不可变,即*p只读

int * const p;//代表指针p是只读,它不允许指向其他地方,但是指向的内容可变

int const * const p;//代表p是只读,*p也是只读,都不允许改变

可以看看下面这段代码的编译结果

const、#define和volatile的总结_第2张图片

编译结果:

const、#define和volatile的总结_第3张图片

让我们来了解一下const的目的:为了取代预编译指令,消除它的缺点,同时继承它的优点

其中预编译的指令指的是#define

来看看二者的区别

1.const是发生编译阶段,#define发生在预编译阶段

2.const有类型,#define是没有类型的,使用的时候需要自判类型

3.const的变量是需要占用内存,但是只有该只读变量的一份拷贝,#define的宏是不需要占用内存的,但是在编译阶段,是需要占用内存的

4.由第3点就可以知道,const可以节约内存

 #define PI 3.14159 //常量宏 
const doulbe Pi=3.14159; //此时并未将Pi放入ROM中 ...... 
double i=Pi; //此时为Pi分配内存,以后不再分配! 
double I=PI; //编译期间进行宏替换,分配内存 
double j=Pi; //没有内存分配 
double J=PI; //再进行宏替换,又一次分配内存! 
const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而 #define定义的常量在内存中有若干个拷贝。

 5.提高了效率。 
编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高

6.const的变量是有作用域和生命周期的,#define是全局的,但是可以#undef取消某个宏

让我们在来看一看volatile这个关键字

volatile:易变的,所以每一次使用这个变量的时候,必须从该变量对应的内存去读该变量,编译器就不会去做优化,使用该变量汇编代码会多一个读内存的动作

下面来看一个例子

const、#define和volatile的总结_第4张图片

再分别来看一看不优化的汇编代码和优化的汇编代码

const、#define和volatile的总结_第5张图片

const、#define和volatile的总结_第6张图片

由上面可以知道,有volatile修饰的变量是每一次使用,都要从内存去拿,编译器不会去做优化

有了一定的理解之后,再让我们来看几个经常在嵌入式面试中会遇到几个问题

举出3个不同volatile的例子

1. 并行设备的硬件寄存器                                                     
2. 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables) 
3. 多线程应用中被几个任务共享的变量 
                                                      

感觉这些都是让某一些人难以理解,那么再来看几个

1. 一个参数既可以是const还可以是volatile吗?解释为什么。

2. 一个指针可以是volatile 吗?解释为什么。

3. 下面的函数有什么错误:int square(volatile int *ptr){return *ptr * *ptr;}

下面是答案:
1. 是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const是说明:程序员不应该试图去修改它,并不是说不能改变。
2. 是的。尽管这并不很常见。一个例子是当一个中断服务子程序修改一个指向一个buffer的指针时
3. 这段代码有点变态。这段代码的目的是用来返回指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:
int square(volatile int *ptr) 
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}

由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的结果,这段代码可能返不是你所期望的平方值!正确的代码如下:
long square(volatile int *ptr) 
{
int a;
a = *ptr;
return a * a;
}

 

你可能感兴趣的:(c/c++)