C 语言存储类型
内容简介
存储类型说明符用于描述对象或函数的存储期、作用域和链接属性。C 语言常见相关关键字包括 auto、static、extern、register。volatile 严格说不是存储类型说明符,而是类型限定符,但它经常和全局变量、寄存器、中断、并发可见性一起讨论,所以放在本篇补充。
核心问题:
- 变量活多久:自动存储期、静态存储期、动态存储期。
- 名字在哪里可见:块作用域、文件作用域。
- 跨文件是否同一个实体:外部链接、内部链接、无链接。
- 编译器能不能缓存访问结果:
volatile影响优化假设。
用法
auto
局部变量默认就是 auto,表示自动存储期。现代 C 代码几乎不会显式写它。
void func(void)
{
auto int value = 10; // 合法但不推荐,等价于 int value = 10
}value 在进入代码块时创建,离开代码块时生命周期结束。
static 局部变量
int next_id(void)
{
static int id = 0;
id++;
return id;
}局部 static 的名字只在函数块内可见,但对象拥有静态存储期:只初始化一次,函数返回后仍然保留值。
static 文件作用域变量和函数
static int counter = 0;
static void helper(void)
{
counter++;
}文件作用域的 static 表示内部链接:这个名字只在当前 .c 文件内可见,适合隐藏模块内部状态和辅助函数。
extern 声明外部实体
extern 表示“这个名字的定义在别处,这里只是声明”。
// config.h
#ifndef CONFIG_H
#define CONFIG_H
extern int global_config;
#endif// config.c
#include "config.h"
int global_config = 100;// main.c
#include "config.h"
int main(void)
{
return global_config;
}函数声明默认具有外部链接,通常不需要显式写 extern:
int add(int a, int b); // 常见写法
extern int add(int a, int b); // 也合法,但少见register
register 是给编译器的历史性建议,表示希望变量放在寄存器中。现代编译器通常不需要这个提示。
void func(void)
{
register int i = 0;
/* 不能对 register 变量取地址 */
}volatile
volatile 告诉编译器:这个对象的值可能被当前代码之外的因素改变,每次访问都要按源代码语义真的读写。
常见场景:
- 内存映射硬件寄存器。
- 中断服务程序和主循环共享的简单标志。
- 信号处理函数修改的
sig_atomic_t标志。
volatile unsigned int * const UART_STATUS = (volatile unsigned int*)0x40000000u;
while ((*UART_STATUS & 1u) == 0u) {
/* 等待硬件状态变化 */
}中断标志示意:
static volatile int g_ready = 0;
void interrupt_handler(void)
{
g_ready = 1;
}
int main(void)
{
while (!g_ready) {
/* wait */
}
return 0;
}volatile 不等于线程安全,不提供原子性、互斥、内存顺序,也不能修复数据竞争。
最佳代码实践
- 能限制在当前
.c文件内的全局变量和辅助函数,用static限制可见性。 - 跨文件共享变量时,声明放在
.h,唯一的定义放在一个.c。 - 少用可写全局变量;需要共享状态时优先封装访问函数。
extern放声明,不要在头文件里定义普通全局变量。volatile只用于确实会被“当前执行流之外”改变的对象,不要把它当并发同步工具。- C++ 的
auto是类型推导,和 C 的auto存储类型不是一回事。
错误用法
// a.h
int global_count = 0; // 错误:头文件中定义全局变量,多个 .c include 后会重复定义正确做法:
// a.h
extern int global_count;
// a.c
int global_count = 0;extern int value = 10; // 错误倾向:带初始化就是定义,不是普通 extern 声明volatile int done = 0;
/* 错误倾向:多线程用 done 当同步变量。volatile 不保证原子性和内存顺序。 */static int global_state; // 错误倾向:滥用全局状态,导致模块难测试、难复用注意事项
- 文件作用域对象如果没有显式初始化,会被零初始化。
- 局部自动变量如果没有初始化,读取它是未定义行为。
const描述只读语义,不等同于存储类型;文件作用域const在 C 和 C++ 中链接规则也不完全一样。volatile const常见于只读硬件寄存器:程序不能写,但硬件可能改变它。- 动态存储期来自
malloc/free,不由这些存储类型关键字直接控制。
相关:C语言知识地图、C语言系统栈与过程调用、CandCppconst、CandCpp底层知识
学习路径
- 上一节:C语言位域
- 当前阶段:C 语言补充
- 下一节:C语言系统栈与过程调用