C存储类说明符
定义
存储类说明符auto、register、static、extern描述对象的三个不同属性:作用域(名字在哪些代码里可见)、存储期(对象何时创建、何时销毁)、链接(哪些翻译单元能看到这个名字)。
| 存储类 | 用在哪里 | 作用域 | 存储期 | 链接 |
|---|---|---|---|---|
auto | 块内局部变量,可省略 | 块 | 自动 | 无 |
register | 块内局部变量 | 块 | 自动 | 无 |
static | 块内局部变量 | 块 | 静态(程序全程) | 无 |
static | 文件作用域的变量、函数 | 文件 | 静态 | 内部 |
extern | 变量、函数的声明 | 由声明位置决定 | 对象通常为静态存储期 | 通常为外部,也可能继承已有内部链接 |
性质
static的两种含义
修饰局部变量时,static只改变存储期,不改变作用域:变量在程序开始时创建、结束时销毁,但名字仍只在块内可见。它只初始化一次,未显式初始化时被置0(自动变量不会)。适合记录跨调用保留的状态,例如计数器、缓存。
修饰文件作用域的变量或函数时,static改变链接属性:名字只在本翻译单元(.c文件)内可见,其他文件即使写了extern声明也链接不到。这用来避免多个文件里出现同名函数或全局变量。
声明与定义、extern
extern int counter; 是声明:告诉编译器这个名字存在、类型是什么,不分配存储。定义才分配存储,写成int counter = 0;。一个程序里同一个变量只能有一处定义,但可以有任意多处声明。
/* counter.h:声明,可被多个文件包含 */extern int counter;
/* counter.c:唯一的定义 */int counter = 0;函数声明默认具有外部链接,extern可以省略;文件作用域下不带extern的变量声明可能是暂定定义;带初始化器的声明也是定义。
组织跨文件的声明
-
在头文件写声明
extern int counter;与函数原型放在counter.h,声明不分配存储、可以被多次包含。 -
在一个源文件写定义
counter.c里写int counter = 0;,全程序只此一处分配存储。 -
其他源文件包含头文件
使用方
#include "counter.h"后即可按名字访问,链接阶段把它们连到同一个对象。
register与auto
register只是建议把变量放在寄存器里,现代编译器自己决定是否采纳。声明为register的对象不能使用一元 & 取地址;无论编译器实际是否把它放在寄存器中,这一限制都适用。
auto是局部变量默认的存储类,写不写都一样,现在几乎不显式写。它和C++ 的auto类型推导不是一回事。
例子
static局部变量只初始化一次
#include <stdio.h>
void tick(void) { static int count = 0; /* 只初始化一次 */ count++; printf("count = %d\n", count);}
int main(void) { tick(); /* 1 */ tick(); /* 2 */ tick(); /* 3 */ return 0;}去掉static后,每次调用都会重新初始化为0,三次输出都是1。这正是“存储期”和“作用域”分开理解的地方:count一直存在,但只能在tick里按名字访问。
交互探索
反复调用函数,比较自动局部变量与静态局部变量的初始化和保留行为。
重复调用时,局部变量何时重新初始化?
比较自动变量和静态局部变量在多次调用中的状态。
正在准备演示…
练习
一个
static局部变量在程序的一次运行中被初始化 次。参考解答
静态存储期的对象在程序开始时创建一次,初始化只执行一次,之后跨函数调用保留上一次的值。
static修饰文件作用域的全局变量时,作用是:参考解答
文件作用域的
static给出内部链接,其他翻译单元即使写了extern声明也链接不到;它不改变可修改性,也不会让变量提前销毁。extern int counter;这条语句的作用是:参考解答
带
extern且没有初始化器的是声明;定义要在某个.c文件中写int counter = 0;,全程序只允许一处。