Namespaces
Variants

Storage-class specifiers

From cppreference.net

指定对象和函数的 存储期 链接

  • auto - 自动存储期且无链接
  • register - 自动存储期且无链接;无法获取该变量地址
  • static - 静态存储期且内部链接(除非处于块作用域)
  • extern - 静态存储期且外部链接(除非已声明为内部链接)
  • _Thread_local (C23前) thread_local (C23起) - 线程存储期
(C11起)

目录

说明

存储类说明符出现在 声明 复合字面量 表达式 (C23起) 中。最多只能使用一个说明符 ,但 _Thread_local (C23前) thread_local (C23起) 可以与 static extern 组合使用以调整链接 (C11起) 。存储类说明符决定了所声明名称的两个独立属性: 存储期 链接

1) auto 说明符仅允许用于在块作用域中声明的对象(函数参数列表除外)。它表示自动存储期和无链接性,这些是此类声明的默认属性。
2) register 说明符仅允许用于块作用域(包括函数参数列表)中声明的对象。它指示自动存储期和无链接(这是此类声明的默认属性),但额外提示优化器尽可能将该变量的值存储在CPU寄存器中。无论是否进行此优化,声明为 register 的变量不能用作 取址运算符 的参数 ,不能使用 _Alignas (C23前) alignas (C23起) (C11起) ,且 register 数组不可转换为指针。
3) static 说明符同时指定静态存储期 (除非与 _Thread_local 结合使用) (C11 起) 和内部链接(除非用于块作用域)。它可用于文件作用域的函数以及文件作用域和块作用域的变量,但不能用于函数参数列表。
4) extern 说明符指定静态存储期 (除非与 _Thread_local (C23前) thread_local (C23起) 组合使用) (C11起) 且具有外部链接。它可用于文件作用域和块作用域中的函数与对象声明(不包括函数形参列表)。若 extern 出现在已声明为内部链接的标识符的重复声明上,则链接保持内部。否则(若先前的声明具有外部链接、无链接或不在作用域内),则链接为外部链接。
5) _Thread_local (C23前弃用) thread_local (自C23起) 表示 线程存储期 。不可用于函数声明。若用于对象声明,则必须出现在该对象的每个声明中。若用于块作用域声明,必须与 static extern 组合使用以确定链接性。
(自C11起)

如果未提供存储类说明符,则默认值为:

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

对于任何使用存储类说明符声明的结构体或联合体,其存储持续时间(但不包括链接)会递归地应用于它们的成员。

块作用域中的函数声明可以使用 extern 或完全不使用存储类说明符。文件作用域中的函数声明可以使用 extern static

函数参数不能使用除 register 之外的任何存储类说明符。请注意 static 在数组类型的函数参数中具有特殊含义。

存储期

每个 对象 都具有称为 存储期 的属性,该属性决定了对象的 生存期 。C语言中存在四种存储期:

  • 自动 存储期。存储空间在声明对象的 代码块 进入时分配,并在通过任意方式退出时释放(包括 goto return 或执行到代码块末尾)。 例外情况是 可变长度数组 :其存储空间在声明执行时(而非代码块进入时)分配,在声明离开作用域时(而非代码块退出时)释放 (C99起) 。若代码块被递归进入,每次递归层级都会执行新的分配。所有函数参数和非 static 的块作用域对象均具有此存储期, 同时包括在块作用域使用的 复合字面量 (C23前)
  • 静态 存储期。存储期覆盖整个程序执行过程,对象存储的值仅在 main函数 执行前初始化一次。所有声明为 static 的对象,以及所有具有内部或外部链接且 未声明为 _Thread_local (C23前) thread_local (C23起) 的对象 (C11起) 均具有此存储期。
  • 线程 存储期。该存储期覆盖声明对象所在线程的整个执行周期,对象存储的值在线程启动时完成初始化。每个线程都拥有独立且互不相同的该对象。若执行访问该对象表达式的线程并非执行其初始化的线程,则行为由实现定义。所有声明为 _Thread_local (C23前) thread_local (C23起) 的对象均具有此存储期。
(C11起)
  • 已分配 存储期。存储空间通过< a href="../memory.html" title="c/memory">动态内存分配函数按需进行分配和释放。

链接

链接(Linkage)指的是标识符(变量或函数)在其他作用域中被引用的能力。如果在多个作用域中声明了相同标识符的变量或函数,但无法从所有作用域中引用它们,则会生成该变量的多个实例。以下是所支持的链接类型:

  • 无链接 。该变量或函数仅能在其所在作用域(块作用域)内被引用。所有未声明为 extern 的块作用域变量均具有此链接属性,函数参数及所有非函数/变量的标识符同样适用。
  • 内部链接 。该变量或函数可以从当前翻译单元中的所有作用域引用。所有声明为 static constexpr (自 C23 起) 的文件作用域变量都具有此链接,所有声明为 static 的文件作用域函数(静态函数声明仅允许在文件作用域使用)也具有此链接。
  • external linkage 。该变量或函数可以被整个程序中任何其他翻译单元引用。所有未声明为 static constexpr (since C23) 的文件作用域变量、所有未声明为 static 的文件作用域函数声明、所有块作用域函数声明,以及所有声明为 extern 的变量或函数(除非此时可见的先前声明具有内部链接)都具有此链接属性。

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

链接与库

具有外部链接的声明通常会在头文件中提供,以便所有 #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; } // 内联函数定义
#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 thread_local

注释

关键字 _Thread_local 通常通过便捷宏 thread_local 使用,该宏定义在头文件 <threads.h> 中。

(C23前)

typedef constexpr (C23起) 说明符在C语言文法中被正式列为存储类说明符,但并不指定存储。

auto 说明符也用于类型推断。

(since C23)

在文件作用域中,被声明为 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

参考文献

  • C23 标准 (ISO/IEC 9899:2024):
  • 6.2.2 标识符的链接 (p: 35-36)
  • 6.2.4 对象的存储期 (p: 36-37)
  • 6.7.1 存储类说明符 (p: 97-100)
  • C17 标准 (ISO/IEC 9899:2018):
  • 6.2.2 标识符的链接 (p: 29-30)
  • 6.2.4 对象的存储期 (p: 30)
  • 6.7.1 存储类说明符 (p: 79)
  • C11 标准 (ISO/IEC 9899:2011):
  • 6.2.2 标识符的链接 (p: 36-37)
  • 6.2.4 对象的存储期 (p: 38-39)
  • 6.7.1 存储类说明符 (p: 109-110)
  • C99标准(ISO/IEC 9899:1999):
  • 6.2.2 标识符的链接(第30-31页)
  • 6.2.4 对象的存储期(第32页)
  • 6.7.1 存储类说明符(第98-99页)
  • C89/C90 标准 (ISO/IEC 9899:1990):
  • 3.1.2.2 标识符的链接
  • 3.1.2.4 对象的存储期
  • 3.5.1 存储类说明符

参见

C++ 文档 关于 存储类说明符