0、前言
程序员通过C的内存管理系统指定变量的作用域和生命期,实现对程序的控制。而合理使用内存存储数据是设计程序的一个要点。
1、存储类别相关基本概念
-
对象(object):从硬件上来看,被存储的每个值都占用一定的物理内存,即这块内存便被称为对象。
-
标识符(identifier):从软件来看,通过声明对象来访问对象,而标识符可以用来指定特定对象的内容。
-
作用域(scope):描述程序可以访问标识符的区域。
- 1、块作用域(block scope):块是用一对花括号扩起来的代码区域,块作用域变量的可见范围是从定义处到包含该定义的块的末尾。(函数的形参也属于块作用域)
- 2、函数作用域(function scope):仅用于
goto
语句的标签,即即使一个标签首次出现在函数的内层块中,它的作用域也会延伸至整个函数。 - 3、函数原型作用域(function prototype scope):用于函数原型中的形参名,即从形参定义处到原型声明结束。即意味着,编译器在处理函数原型中的形参时只关心其类型,而形参名通常无关紧要。
- 4、文件作用域(file scope):变量的定义在函数的外面。即该变量从它的定义处到该定义所在文件的末尾均可见。
-
链接(linkage)
- 1、外部链接:可以在多文件程序中使用。
- 2、内部链接:只能在一个翻译单元中使用。
- 3、无链接:具有块作用域、函数作用域或函数原型作用域的变量。
-
存储期(Storage Period):描述通过标识符访问的对象的生存期。
- 1、静态存储期:该变量在程序执行期间一直存在,所有的文件作用于变量都具有静态存储期。
- 2、线程存储期:用于并发程序设计,该对象(关键字
_Thread_local
)从被声明时到线程结束一直存在。 - 3、自动存储期:当程序进入定义这些变量的块时,为这些变量分配内存,当退出这个块时,释放刚才分配的内存。
- 4、动态分配存储期: 用库函数分配和管理内存。
2、五种存储类别
在C语言中存储类别定义了变量或函数的作用域、生命周期和链接属性。其共有五种存储类别,可以通过auto、register、static、extern和默认隐式等关键字声明。
存储类别与变量属性对照表
存储类别 | 关键字 | 作用域 | 生命周期 | 存储位置 | 默认初始化值 | 地址获取 | 链接属性 |
---|---|---|---|---|---|---|---|
自动 | auto | 代码块 | 代码块执行期间 | 栈内存 | 未定义 | 可获取 | 无 |
寄存器 | register | 代码块 | 代码块执行期间 | 寄存器 | 未定义 | 不可获取 | 无 |
静态(内部) | static | 文件 | 程序运行期间 | 静态存储区 | 0 | 可获取 | 内部 |
静态(局部) | static | 代码块 | 程序运行期间 | 静态存储区 | 0 | 可获取 | 无 |
静态外部 | extern | 文件 | 程序运行期间 | 静态存储区 | 0 | 可获取 | 外部 |
隐式(全局) | 无 | 文件 | 程序运行期间 | 静态存储区 | 0 | 可获取 | 外部 |
2.1、自动存储类别
属于自动存储类别的变量具有自动存储期、块作用域和无链接。默认情况下,声明在块或者函数头中的任何变量都属于自动存储类别。存储位置是栈内存。
- 特点:
- 每次进入代码块时重新创建,退出时销毁。
- 未初始化时值为未定义(随机)。
auto
关键字很少使用,可以忽略。
void func() {
int x = 10; // 等价于 auto int x = 10;
// x 在函数执行期间存在
} // x 在此处销毁
2.2、寄存器存储类别
变量通常存储在计算机内存中,幸运的话,寄存器变量存储在CPU的寄存器中。与普通变量相比,访问和处理这些变量的速度更快。存储位置是CPU 寄存器(或高速缓存)。
- 特点:
- 建议编译器将变量存储在寄存器中,提高访问速度。
- 无法获取变量的地址(即
®ister_va
r非法)。 - 编译器可能忽略register请求,将变量存储在内存中。
void loop() {
register int i; // 建议将i存储在寄存器中
for (i = 0; i < 1000; i++) {
// 高频访问的变量使用register可能提升性能
}
}
2.3、无链接和内部链接的静态变量
块作用域的静态变量和自动变量一样,具有相同的作用域,但是程序离开它们所在的函数后,这些变量并不会消失,多次调用该函数也不会重新初始化该变量,即该变量的初始化只执行一次,后续改变的值存储在当前位置,不会丢失。
- 关键字:
static
- 作用域:
- 内部链接:文件作用域(仅当前文件可见)。
- 无链接:代码块作用域(仅函数内可见,但保持全局寿命)。
- 生命周期:程序整个运行期间
- 存储位置:静态存储区
- 特点:
- 未初始化时自动初始化为0(全局变量也如此)。
- 内部链接的
static
变量可避免命名冲突(类似 C++ 的namespace)。
内部链接(文件作用域)
// file1.c
static int file_private = 100; // 仅file1.c可见
void func() {
// 可访问file_private
}
// file2.c
extern int file_private; // 错误!无法外部链接
无链接(块作用域)
void counter() {
static int count = 0; // 仅首次调用时初始化
count++;
printf("Count: %d\n", count);
}
int main() {
counter(); // 输出1
counter(); // 输出2
return 0;
}
2.4、 外部存储类别(Extern)
- 关键字:
extern
- 作用域:文件作用域(全局可见)
- 生命周期:程序整个运行期间
- 存储位置:静态存储区
- 特点:
- 声明已有变量或函数,不分配内存。
- 用于跨文件共享全局变量或函数。
// file1.c
int global_var = 10; // 定义全局变量
// file2.c
extern int global_var; // 声明外部变量
void func() {
printf("%d\n", global_var); // 使用file1.c中的global_var
}
3、常见的使用技巧
1、静态变量的常见用途
- 实现函数内的计数器或状态保持;
- 缓存计算结果。
2、extern
使用技巧
声明与定义分离,比如头文件中函数或外部链接变量声明。
3、register
使用
常用于高频访问的变量,但不可获取地址。(且不一定能成功申请为寄存器变量)
4、auto
变量
自动变量定义时,一定要进行初始化,否则不可使用其数值。
5、静态变量的线程安全问题
在多线程环境中,静态变量的修改需要加锁保护。
#include <pthread.h>
void thread_func() {
static int counter = 0;
pthread_mutex_lock(&mutex);
counter++; // 线程不安全操作,需加锁
pthread_mutex_unlock(&mutex);
}