存储类说明符

< c‎ | language

指定对象和函数的存储期( storage duration )链接( linkage )

  • auto - 自动存储期与无链接
  • register - 自动存储期与无链接;不能取这种对象的地址
  • static - 静态存储期与内部链接(除非在块作用域)
  • extern - 静态存储期与外部链接(除非已声明带内部链接)
  • _Thread_local - 线程存储期
(C11 起)

解释

存储类说明符出现于声明中。至多可使用一个说明符,除了能将 _Thread_localstaticextern 组合以调整链接 (C11 起)。存储类说明符确定其所声明的名称的二个独立属性:存储期链接

1) auto 说明符只对声明于块作用域的对象(除了函数参数列表)允许。它指示自动存储期与无链接,也是这种声明的默认属性。
2) register 说明符只对声明于块作用域的对象允许,包括函数参数列表。它指示自动存储期与无链接(即这种声明的默认属性),但另外提示优化器,若可能则将此对象的值存储于 CPU 寄存器中。无论此优化是否发生,声明为 register 的对象不能用作取址运算符的参数,不能用 _Alignas (C11 起),而且 register 数组不能转换为指针。
3) static 说明符指定静态存储期(除非与 _Thread_local 组合) (C11 起)和内部链接(除非用于块作用域)。它能用于在文件作用域的函数,以及文件和块作用域的对象,但不能用于函数参数列表。
4) extern 指定静态存储期(除非与 _Thread_local 组合) (C11 起)和外部链接。它能用于文件和块作用域中的函数和对象声明(除了函数参数列表)。若 extern 出现在已经声明带内部链接的标识符的再声明上,则链接仍为内部。否则(若前一声明为外部、无链接或不在作用域内)链接为外部。
5) _Thread_local 指示线程存储期。它不能用于函数声明。若将它用在对象声明上,则它必须在同一对象的每次声明上都存在。若将它用在块作用域声明上,则必须与 staticextern 之一组合以决定链接。
(C11 起)

若不提供存储类说明符,则默认为:

对所有函数为 extern
对在文件作用域的对象为 extern
对在块作用域的对象为 auto

对于任何用存储类说明符声明的结构体或联合体,存储期(但非链接)递归地应用到其成员。

在块作用域的函数声明能使用 extern 或完全不使用存储类说明符。在文件作用域的函数声明能使用 externstatic

函数参数不能使用异于 register 的存储类说明符。注意 static 在数组类型的函数参数中有特殊含义。

存储期

每个对象都有称为存储期的属性,它限制对象的生存期。 C 中有四种存储期:

  • 自动存储期。进入声明对象于其中的时分配其存储,而在以任何方式( gotoreturn 、抵达结尾)退出该块时解分配存储。一个例外是 VLA ;在执行声明时而非块入口分配其存储,并在声明离开作用域而非退出块时解分配存储。 (C99 起)若递归地进入块,则对每个递归层进行新的分配。所有函数参数和非 static 块作用域对象,还有用于块作用域的复合字面量拥有此存储期。
  • 静态存储期。存储期是整个程序的执行过程,只在 main 函数之前初始化一次存储于对象的值。所有声明为 static 对象和所有带内部或外部链接且不声明为 _Thread_local (C11 起)的对象都拥有此存储期。
  • 线程存储期。存储期是创建对象的线程的整个执行过程,在启动线程时初始化存储于对象的值。每个线程拥有其自身的相异对象。若执行访问此对象的表达式的线程,不是执行其初始化的线程,则行为是实现定义的。所有声明为 _Thread_local 的对象拥有此存储期。
(C11 起)

链接

链接指的是在其他作用域指代一个标识符(具名对象或函数)的能力。若在数个作用域中声明有同一标识符的对象或函数,但不能从所有这些作用域指代它们,则会创建数个对象的实例。辨识下列链接:

  • 无链接。只能从其所在的作用域指代该标识符。所有函数参数和所有非 extern 的块作用域对象(包含声明为 static 者)拥有此链接。
  • 内部链接。能从当前翻译单元的所有作用域指代该标识符。所有 static 文件作用域标识符(函数和对象)都拥有此链接。
  • 外部链接。能从整个程序的任何其他翻译单元指代该标识符。所有非 static 函数、所有 extern 对象(除非之前声明为 static )和所有文件作用域的非 static 对象拥有此链接。

若同一标识符在同一翻译单元中一同带内部和外部链接出现,则行为未定义。这在使用试探性定义时有可能。

链接与库

带外部链接的声明常在头文件中可用,这使得所有 #include 该头文件的翻译单元都可以指代定义于别处的相同标识符。

任何出现于头文件中的带内部链接的声明,在每个包含该文件的翻译单元中产生一个分离而相异的对象或函数。

库接口:

// flib.h
#ifndef FLIB_H
#define FLIB_H
void f(void);              // 带外部链接的函数声明
extern int state;          // 带外部链接的对象声明
static const int size = 5; // 带内部链接的只读对象定义
enum { MAX = 10 };         // 常量定义
inline int sum (int a, int b) { return a+b; } // inline 函数定义
#endif // FLIB_H

库实现:

// flib.c
#include "flib.h"
static void local_f(int s) {}  // 带内部链接的定义(只用于此文件)
static int local_state;        // 带内部链接的定义(只用于此文件)
 
int state;                     // 带外部链接的定义( main.c 使用)
void f(void) {local_f(state);} // 带外部链接的定义( main.c 使用)

应用代码:

// main.c 
#include "flib.h"
int main(void)
{
    int x[MAX] = {size}; // 使用常量和只读变量
    state = 7;           // 修改 flib.c 中的 state
    f();                 // 调用 flib.c 中的 f()
}

关键词

auto, register, static, extern, _Thread_local

注解

一般通过定义于头文件 threads.h 的便利宏 thread_local 使用关键词 _Thread_local

C 语言文法中, typedef 说明符在形式上列作存储类说明符,但它被用于声明类型名,而不指定存储。

在文件作用域的 const 且非 extern 的名称在 C 中拥有外部链接(同所有文件作用域的默认情况),但在 C++ 中拥有内部链接。

示例

#include <stdio.h>
#include <stdlib.h>
 
int A; // 静态存储期
 
int main(void)
{
    printf("&A = %p\n", (void*)&A);
 
    // 自动存储期
    int A = 1;   // 隐藏全局 A
    printf("&A = %p\n", (void*)&A);
 
    // 分配存储期
    int *ptr_1 = malloc(sizeof(int));   // 开始分配存储期
    printf("address of int in allocated memory = %p\n", (void*)ptr_1);
    free(ptr_1);                        // 停止分配存储期
}

可能的输出:

&A = 0x600ae4
&A = 0x7ffefb064f5c
address of int in allocated memory = 0x1f28c30

引用

  • C11 standard (ISO/IEC 9899:2011):
  • 6.2.2 Linkages of identifiers (p: 36-37)
  • 6.2.4 Storage durations of objects (p: 38-39)
  • 6.7.1 Storage-class specifiers (p: 109-110)
  • C99 standard (ISO/IEC 9899:1999):
  • 6.2.2 Linkages of identifiers (p: 30-31)
  • 6.2.4 Storage durations of objects (p: 32)
  • 6.7.1 Storage-class specifiers (p: 98-99)
  • C89/C90 standard (ISO/IEC 9899:1990):
  • 3.1.2.2 Linkages of identifiers
  • 3.1.2.4 Storage durations of objects
  • 3.5.1 Storage-class specifiers

参阅