参考文章1:嵌入式面经
参考文章2:关于STM32库中 __IO 修饰符(volatile修饰符)
volatile 初印象
最初接触到 volatile,是看野火的自己编写库函数的章节,其中在寄存器结构体中的寄存器成员前加了 "__IO" 修饰,这个 __IO 就是 volatile。
1 //volatile 表示易变的变量,防止编译器优化 2 #define __IO volatile 3 typedef unsigned int uint32_t; 4 typedef unsigned short uint16_t; 5 6 /* GPIO 寄存器列表 */ 7 typedef struct { 8 __IO uint32_t MODER; /*GPIO 模式寄存器 地址偏移: 0x00 */ 9 __IO uint32_t OTYPER; /*GPIO 输出类型寄存器 地址偏移: 0x04 */ 10 __IO uint32_t OSPEEDR; /*GPIO 输出速度寄存器 地址偏移: 0x08 */ 11 __IO uint32_t PUPDR; /*GPIO 上拉/下拉寄存器 地址偏移: 0x0C */ 12 __IO uint32_t IDR; /*GPIO 输入数据寄存器 地址偏移: 0x10 */ 13 __IO uint32_t ODR; /*GPIO 输出数据寄存器 地址偏移: 0x14 */ 14 __IO uint16_t BSRRL; /*GPIO 置位/复位寄存器低 16 位部分 地址偏移: 0x18 */ 15 __IO uint16_t BSRRH; /*GPIO 置位/复位寄存器 高 16 位部分地址偏移: 0x1A / 16 __IO uint32_t LCKR; /GPIO 配置锁定寄存器 地址偏移: 0x1C / 17 __IO uint32_t AFR[2]; /GPIO 复用功能配置寄存器 地址偏移: 0x20-0x24 / 18 } GPIO_TypeDef;
当时火哥大致讲解了原因:
而寄存器很多时候是由外设或 STM32 芯片状态修改的,也就是说即使 CPU 不执行代码修改这些变量,变量的值也有可能被外设修改、更新,所以每次使用这些变量的时候,我们都要求 CPU 去该变量的地
址重新访问。
若没有这个关键字修饰,在某些情况下,编译器认为没有代码修改该变量,就直接从 CPU 的某个缓存获取该变量值,这时可以加快执行速度,但该缓存中的是陈旧数据,与我们要求的寄存器最新状态可能会有出入。
总结一下:
- 当变量可变允许除了程序之外的比如硬件来修改他的内容时,应该用 volatile 修饰。
- 加volative的原理是:访问该数据任何时候都会直接访问该地址处内容,即通过cache提高访问速度的优化被取消。
应用场合:
场合1:多线程变量
对于共享的内存地址(多线程变量),多个程序都对它操作的时候。你的程序并不知道,这个内存何时被改变了。如果不加这个voliatile修饰,程序是利用catch当中的数据,那个可能是过时的了,加了 voliatile,就在需要用的时候,程序重新去那个地址去提取,保证是最新的。
场合2:外设通过映象的方式的内存
现在硬件设备往往也有自己的私有内存地址,比如显存,他们一般是通过映象的方式,反映到一段特定的内存地址当中、因此当其自己改变了其内存的数据时,程序的缓存的数据可能还没有改。
场合3:硬件寄存器(尤其指:状态寄存器)