核心概念速览
| 关键字 | 核心作用 | 目的 | 编译器行为 |
|---|---|---|---|
const |
“只读” | 保证变量的值在程序运行期间不被意外修改。 | 优化:编译器会假设 const 变量的值不会改变,从而进行更激进的优化,比如将值缓存到寄存器中。 |
volatile |
“易变” | 告诉编译器,变量的值可能被程序之外的因素(硬件、其他线程)改变。 | 禁止优化:编译器每次使用 volatile 变量时,都必须从内存中重新读取,而不是使用寄存器中的缓存值。 |
const 关键字详解
const 的全称是 "constant",意为“常量”,它用来声明一个“只读”变量。

基本用法
最简单的用法是声明一个普通常量:
const int MAX_SIZE = 100; // MAX_SIZE = 200; // 错误!不能修改 const 变量的值。
const 与指针
const 与指针结合使用时,情况会变得稍微复杂,关键在于 const 修饰的是谁。
a. 指向常量的指针
int a = 10; int b = 20; const int *ptr = &a; // 或者 int const *ptr = &a; *ptr = 30; // 错误!不能通过 ptr 修改它指向的值。 ptr = &b; // 正确!可以改变指针的指向,让它指向另一个变量。
- 含义:
ptr是一个指针,它指向的值是“只读”的,你不能通过ptr来修改这个值。 - 重点:
const修饰的是*ptr(指针指向的值),而不是ptr(指针本身)。
b. 常量指针

int a = 10; int b = 20; int *const ptr = &a; *ptr = 30; // 正确!可以通过 ptr 修改它指向的值。 ptr = &b; // 错误!不能改变指针的指向。
- 含义:
ptr本身是一个“常量”指针,它初始化后就不能再指向其他地址了。 - 重点:
const修饰的是ptr(指针本身)。
c. 指向常量的常量指针
int a = 10; int b = 20; const int *const ptr = &a; *ptr = 30; // 错误!不能修改指向的值。 ptr = &b; // 错误!不能改变指针的指向。
- 含义:
ptr是一个“常量”指针,并且它指向的值也是“只读”的,两者都不能改变。
const 的主要用途
- 定义真正的常量:如
const double PI = 3.14159;。 - 函数参数:防止函数意外修改传入的参数。
void print_string(const char *str) { // str[0] = 'H'; // 错误!防止函数修改字符串内容 printf("%s\n", str); } - 函数返回值:返回一个指向只读数据的指针,防止调用者修改。
const char* get_status_message() { static const char msg[] = "Operation successful."; return msg; } - 全局/静态常量:避免使用
#define宏,因为const变量有类型检查,作用域也更可控。
volatile 关键字详解
volatile 的全称是 "volatile",意为“不稳定的、易变的”,它用于告诉编译器,不要对它修饰的变量做任何“假设”,因为它的值可能在任何时候、以任何未知的方式被改变。
为什么需要 volatile?
编译器为了优化性能,会做一些“合理”的假设。
int flag = 0;
// ... 其他代码 ...
while (flag == 0) {
// do something
}
编译器可能会认为 flag 在循环中不会被改变,于是它可能会将 flag 的值(0)加载到 CPU 寄存器中,然后一直检查寄存器里的值,而不是去内存中读取,如果另一个线程或硬件中断将 flag 的值改为了 1,这个循环将永远不会结束,因为它永远看不到内存中的变化。

volatile 的作用就是打破这种“合理”的假设。
volatile 的典型应用场景
a. 硬件寄存器
在嵌入式系统或驱动开发中,我们直接操作硬件寄存器,这些寄存器的值由硬件本身改变,而不是由 C 代码改变。
// 假设 0x12345678 是一个状态寄存器的地址
#define STATUS_REGISTER (*(volatile unsigned int *)0x12345678)
// 轮询等待某个事件发生
while ((STATUS_REGISTER & 0x01) == 0) {
// 循环体内什么都不做
// 编译器不能优化掉这个循环,也不能将 STATUS_REGISTER 的值缓存
}
- 为什么必须用
volatile? 因为硬件可能会在任何时候改变STATUS_REGISTER的值,没有volatile,编译器可能会认为循环条件STATUS_REGISTER & 0x01永远为0,从而将整个循环优化成一个空操作或死循环。
b. 中断服务程序
全局变量可能被主循环使用,同时被中断服务程序修改。
int g_flag = 0;
void main_loop() {
while (1) {
if (g_flag) {
// 处理中断事件
g_flag = 0;
}
}
}
void ISR() { // Interrupt Service Routine
g_flag = 1; // 中断发生时设置标志
}
g_flag 没有 volatile 修饰,main_loop 中的 if (g_flag) 可能会被编译器优化为只读取一次,导致无法响应后续的中断。
正确写法:
volatile int g_flag = 0;
c. 多线程环境
在多线程编程中,一个线程共享的变量,如果被另一个线程修改,那么这个变量就应该声明为 volatile,以防止编译器优化导致一个线程看不到另一个线程的修改。
// 线程1
volatile int shared_data = 0;
// 线程1
while (shared_data == 0) {
// 等待线程2修改 shared_data
// 没有 volatile,编译器可能优化成死循环
}
// 线程2
shared_data = 1; // 修改共享数据
volatile 的局限性
volatile 不能保证原子性,它只保证“每次都从内存读取”,但不保证读取-修改-写回这个操作是原子的。
volatile int counter = 0; // 线程A counter++; // 线程B counter++;
即使 counter 是 volatile 的,counter++ 操作(读取-加1-写回)在多线程下仍然是不安全的,如果两个线程同时读取到 0,然后都加 1 再写回,最终结果会是 1 而不是 2,在这种情况下,你需要使用互斥锁 或原子操作 来保证线程安全。
const 和 volatile 的结合使用
这是非常重要的一个用法,一个变量可以同时是 const 和 volatile。
const volatile int *p_reg = (const volatile int *)0x12345678;
volatile:告诉编译器,这个变量的值可能被硬件(程序之外)改变,所以每次使用都必须从内存重新读取,不要缓存。const:告诉编译器,这个变量的值不能被当前程序(C 代码)修改,你不能通过指针p_reg来改变这个地址的值。
典型场景:一个只读的状态寄存器,它的值由硬件自动更新,但你的程序只能读取它,不能写入。
- 硬件:可以改变这个寄存器的值(所以是
volatile)。 - 你的程序:不能改变这个寄存器的值(所以是
const)。
总结与对比
| 特性 | const |
volatile |
|---|---|---|
| 中文含义 | 只读的 | 易变的 |
| 核心目的 | 保护数据不被代码修改 | 保护代码不被数据误导 |
| 告诉编译器 | “这个变量我不会改,你可以放心地基于这个假设进行优化。” | “这个变量可能会被我不知道的东西(硬件/中断/其他线程)改变,每次用都得去内存里看看,别用你手里的缓存。” |
| 主要用途 | 定义常量、函数参数、函数返回值 | 硬件寄存器、全局标志位、多线程共享变量 |
| 优化影响 | 允许编译器进行更激进的优化(如缓存值)。 | 禁止编译器进行某些优化(如缓存值、移除看似“无用”的代码)。 |
| 线程安全 | 不提供任何线程安全保障。 | 不提供原子性,因此不保证线程安全。 |
| 结合使用 | const volatile:表示一个“只读”但“易变”的变量,如硬件只读状态寄存器。 |
- 用
const是为了让代码更安全、清晰。 - 用
volatile是为了让程序能正确地与外部世界(硬件/多线程)交互。
