一直想看一看 volatile 的用法,之前就看过这种静态量是不会被编译器无意破坏的,但是一直没有搞彻底明白。
今天查看了大量前辈们的总结,自己又有了新的心得,写下此随笔来分享自己的心得。
最开始接触 volatile 是在STM32程序的 __IO 中,其中#define __IO volatile
(下面一段来自维基百科对Volatile的解读)
volatile关键字用来阻止(伪)编译器认为的无法“被代码本身”改变的代码(变量/对象)进行优化。如在C语言中,
volatile关键字可以用来提醒编译器它后面所定义的变量随时有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。如果没有volatile关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。
(上面一段来自维基百科对Volatile的解读)
在C语言中,编译器会根据你得代码进行优化,而 volatile 最直接的就是,告诉编译器不要优化这一个变量的运算过程。
(下面图片来自维基百科)
(上面图片来自维基百科)
在汇编下,可以很明确的看出使用Volatile后编译器不会让你的代码丢失。
先说下使用volatile的好处:
1.volatile允许除了程序以外的事件来改变它的内容,比如硬件直接改变。
2.每次读取volatile的内容都是直接访问该地址的内容,不是通过cache( 高速缓冲存储器)来访问。
稍稍解释一下:1.我们告诉编译器,volatile类型的变量的确可能被外部条件改变,比如硬件设备。
2.正常情况下,如果我们不加volatile的话,在共享地址区的变量程序并不知道它什么时候被改变,你读取的只可能是cache里面的变量,而这个变量可能是过时的变量。
3.(补充)私有地址里面的内容,程序可以直接访问
回到我们的截图内容
#define __IO volatile
#define uint32_t uisigned long
如果是(volatile unsigned long *) (0x1FFF7A10),再分解看(volatile unsigned long *)是一个为随硬件需要定义的一种地址,前面加上*指针,即直接指向改地址,整个定义#define CPU_Sn0,调用的时候直接向地址寄存器写内容就好了。这是内存机制的方便性,volatile是嵌入式系统开发发一个重要特点。
再将其展开来分析:(volatile unsigned long *) (0x1FFF7A10)的意思就是将0x1FFF7A10强制转化成volatile unsigned long的指针,然后赋给CPU_Sn0就是表达CPU_Sn0为指向地址的内容了。这里是通过内存寻址访问寄存器,可以进行读/写操作。
再通过这个volatile去分析,关键字volatile 确保本条指令不会因C 编译器的优化而被省略,且要求每次直接读值。例如用while((unsigned char *)0x1FFF7A10)时,有时系统可能不真正去读0x1FFF7A10的值,而是用第一次读出的值,如果这样,那这个循环可能是个死循环。用了volatile 则要求每次都去读0x1FFF7A10的实际值。那么它就是一个固定的指针,而char *p;是一个指针变量。
再在前面加一个 “*” :则变成了变量,只不过这是一个地址固定的指针变量。