知识点

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的变量声明可能是暂定定义;带初始化器的声明也是定义。

组织跨文件的声明

  1. 在头文件写声明

    extern int counter; 与函数原型放在counter.h,声明不分配存储、可以被多次包含。

  2. 在一个源文件写定义

    counter.c里写int counter = 0;,全程序只此一处分配存储。

  3. 其他源文件包含头文件

    使用方 #include "counter.h" 后即可按名字访问,链接阶段把它们连到同一个对象。

register与auto

register只是建议把变量放在寄存器里,现代编译器自己决定是否采纳。声明为register的对象不能使用一元 & 取地址;无论编译器实际是否把它放在寄存器中,这一限制都适用。

auto是局部变量默认的存储类,写不写都一样,现在几乎不显式写。它和C++ 的auto类型推导不是一回事。

例子

static局部变量只初始化一次

counter.c
#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里按名字访问。

交互探索

反复调用函数,比较自动局部变量与静态局部变量的初始化和保留行为。

重复调用时,局部变量何时重新初始化?

比较自动变量和静态局部变量在多次调用中的状态。

正在准备演示…

题集

练习

  1. 一个static局部变量在程序的一次运行中被初始化 次。

    参考解答

    静态存储期的对象在程序开始时创建一次,初始化只执行一次,之后跨函数调用保留上一次的值。

  2. static修饰文件作用域的全局变量时,作用是:

    选择答案
    参考解答

    文件作用域的static给出内部链接,其他翻译单元即使写了extern声明也链接不到;它不改变可修改性,也不会让变量提前销毁。

  3. extern int counter; 这条语句的作用是:

    选择答案
    参考解答

    带extern且没有初始化器的是声明;定义要在某个 .c文件中写int counter = 0;,全程序只允许一处。