一、前言
1. 编译器优化介绍
-
硬件优化:
- 内存访问速度低于 CPU 处理速度,使用硬件高速缓存(Cache)加速内存访问。
- 现代 CPU 指令可以乱序执行,以充分利用指令流水线,提高执行速度。
-
软件优化:
- 程序员优化:在编写代码时进行优化。
- 编译器优化:
- 将内存变量缓存到寄存器。
- 调整指令顺序,充分利用 CPU 指令流水线,如重新排序读写指令。
- 对常规内存进行优化时,这些优化通常是透明且高效的。
-
内存屏障:
- 解决编译器优化或硬件重新排序引起的问题。
- 内存屏障确保特定顺序执行的操作之间的顺序性。
- Linux 提供了一个宏函数
void Barrier(void)
,通知编译器插入一个内存屏障(仅对编译器有效,对硬件无效)。
2. volatile 关键字
- 数据流分析:
- 编译器技术,通过分析程序中变量的赋值、使用和失效位置,进行常量合并、传播等优化。
- 有时这些优化不符合程序需求,此时使用
volatile
关键字禁止这些优化。
二、volatile 详解
1. volatile 的定义和作用
- 定义:“易变的”。
- 作用:
- 编译器通常通过减少内存访问来优化性能,但这可能会导致读取脏数据。
- 使用
volatile
声明的变量,每次访问都重新从内存读取数据。 - 禁止编译器对访问
volatile
变量的代码进行优化,确保对特殊地址的稳定访问。
2. 例子解析
1. 禁止编译器优化
int *ip = ...; //设备地址
*ip = 1; //第一个指令
*ip = 2; //第二个指
- 编译器优化后可能变为:
int *ip = ...;
*ip = 2;
- 使用
volatile
禁止优化:
volatile int *ip = ...;
*ip = 1;
*ip = 2;
2. volatile 变量每次都从内存读取
volatile char a;
a = 0;
while (!a) {
// do some things;
}
doother();
- 如果没有
volatile
,doother()
可能不会被执行。
3. volatile 变量的使用场景
1. 中断服务程序修改的变量
static int i = 0;
int main(void) {
...
while (1) {
if (i) dosomething();
}
}
void ISR_2(void) {
i = 1;
}
i
需要用volatile
修饰,确保在中断服务程序修改时,主程序能正确读取。
2. 多任务环境下共享的标志
- 多任务间共享的标志变量需要加
volatile
修饰。
3. 存储器映射的硬件寄存器
volatile int *output = (volatile unsigned int *)0xff800000; int init(void) { int i; for (i = 0; i < 10; i++) { *output = i; } }
- 对外部设备初始化时,需要
volatile
确保每次对寄存器的写操作都生效。
4. 常见问题
1. const 和 volatile 同时使用
- 可以,例如只读的状态寄存器:
const volatile int status;
2. volatile 指针
- 可以,当中断服务程序修改指向缓冲区的指针时:
const volatile int status;
5. volatile 的本质
1. 编译器优化
- 为提高存取速度,编译器可能将变量缓存到寄存器中。
volatile
禁止这种优化,确保每次都从原始内存地址读取变量。
2. 解释
volatile
应解释为“直接存取原始内存地址”。
6. 函数示例
错误示例
int square(volatile int *ptr) { return *ptr * *ptr; }
- 可能导致错误,因为
*ptr
的值可能在两次读取之间改变。
正确示例
long square(volatile int *ptr) { int a; a = *ptr; return a * a; }
注意事项
- 频繁使用
volatile
可能增加代码尺寸并降低性能,因此应合理使用。