static/const/volatile 关键字的作用与区别
一、 static(静态变量)
1. static修饰局部变量:
普通局部变量存在于栈中,函数退出即销毁;static 局部变量存在于全局/静态存储区(.data 或 .bss 段),生命周期贯穿整个程序运行期间。
特点: 只在第一次调用时初始化一次,后续调用会保留上一次的值。
2.static修饰全局变量:
普通全局变量(和函数)具有全局可见性,static 修饰后,它们的作用域被限制在当前文件内(内部链接),其他源文件无法通过 extern 访问。
3.static修饰函数:
修饰该函数仅在这个c文件中被使用,不会被外部的程序调用,适合模块的函数权限区分,以及防止函数重名,比如我有一个motor和uart都需要gpio_init函数的时候,我在两个文件中都分别写对应自己模块的gpio_init这样不会冲突;
嵌入式应用场景:
* 模块化封装防冲突: 在C语言面向对象编程或模块化设计中,底层驱动(如 uart.c)内部使用的辅助函数和状态变量,都应该用 static 修饰。这能防止与其他模块发生命名冲突,实现类似C++中 private 的封装效果。
* 状态保持: 在状态机或ISR定时器中断的回调函数中,如果需要记录上一次的状态或计数,使用 static 局部变量比使用全局变量更安全,因为它避免了被其他函数意外篡改。
二、 const(常量)
const 的核心意思是:只读。
1. 修饰基本数据类型: const int a = 10; 变量不可修改。
2.修饰指针:
- const int *p; (或 int const *p;):底层 const。指针指向的值不可变,但指针本身的指向可以变。
- int * const p;:顶层 const。指针本身的指向不可变,但指向的值可以变。
- const int * const p;:指针的指向和指向的值都不可变。
3.修饰函数参数: 防止传入的指针在函数内部被意外解引用篡改(如 void send_data(const uint8_t *data))。
嵌入式应用场景:
* 节省 RAM 空间: 嵌入式系统的 RAM 可能只有几KB,使用 const 修饰的大型数组(如点阵字库、LCD图片数据、正弦波查表数据等),编译器会将其分配到 ROM/Flash(.rodata 段)中,而不是占用 RAM。
* 保护关键配置: 保护硬件配置结构体或出厂校验参数不被非法指针篡改。
三、 volatile
volatile 的核心作用:防止编译器优化。
如果编译器发现一个变量在代码中没有被显式修改,它可能会将该变量的值缓存在 CPU 寄存器中,后续读取都直接用寄存器里的旧值,而不是去内存中重新读取。
加上 volatile 后,强制编译器每次使用它时,必须强制去内存(或物理地址)中读取,而不是使用寄存器缓存值。
嵌入式应用场景
* 硬件寄存器(Memory-Mapped I/O):
当读取硬件外设的状态寄存器时(例如判断串口接收标志位 UART_RX_FLAG)。硬件状态随时会改变,如果不加 volatile,编译器可能会优化成死循环。
C
#define GPIOA_IDR (*(volatile uint32_t *)0x40010808) // 强制去该物理地址读最新状态
*
* 中断服务函数(ISR)与主循环共享的全局变量:
主循环在跑时,中断随时可能打断并修改某个标志位(如 volatile bool is_timer_expired;)。如果没有 volatile,主循环可能永远检测不到这个改变。
* 多线程/多任务(RTOS)中共享的标志变量:
与中断类似,一个任务修改了变量,另一个任务需要立刻感知到,防止被编译器优化进各自任务的寄存器中。
Test:
一个变量可以同时是 const 和 volatile 吗?
可以。
* 含义: volatile 表示它会被程序之外的因素改变(每次必须从内存读),const 表示程序代码本身不被允许去修改它(只读)。
* 场景: 硬件的只读状态寄存器。例如 ADC(模数转换)的结果寄存器,或者电池电量状态寄存器。
硬件会随时刷新它的值(所以需要 volatile),但我们的软件代码只能读取它,不能去写它(所以需要 const)。
C
const volatile uint32_t * adc_result = (uint32_t *)ADC_BASE_ADDR;
*
发布于 美国
