命名空间
变体
操作

存储类说明符

来自 cppreference.cn
< c‎ | 语言

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

  • auto - 自动存储期,无链接
  • register - 自动存储期,无链接;此变量的地址不能被获取
  • static - 静态存储期和内部链接(除非在块作用域内)
  • extern - 静态存储期和外部链接(除非已声明为内部链接)
  • _Thread_local(C23前)thread_local(C23起) - 线程存储期
(C11 起)

目录

[编辑] 解释

存储类说明符出现在声明复合字面量表达式中(C23起)。最多可以使用一个说明符,但_Thread_local(C23前)thread_local(C23起)可以与staticextern结合以调整链接(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起)指示线程存储期。它不能用于函数声明。如果它用于对象声明,则它必须出现在同一对象的所有声明中。如果它用于块作用域声明,则它必须与staticextern结合以决定链接。
(C11 起)

如果没有提供存储类说明符,则默认值是

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

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

块作用域的函数声明可以使用extern或不使用。文件作用域的函数声明可以使用externstatic

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

[编辑] 存储期

每个对象都有一个名为存储期的属性,它限制了对象的生命周期。C中有四种存储期

  • 自动存储期。当声明对象的被进入时分配存储,并在通过任何方式(gotoreturn、到达末尾)退出时解除分配。一个例外是VLA;它们的存储在声明执行时分配,而不是在块进入时,并在声明超出作用域时解除分配,而不是在块退出时(C99起)。如果块被递归进入,则每个递归级别都会执行新的分配。所有函数参数和非static块作用域对象都具有此存储期,以及在块作用域中使用的复合字面量(C23前)
  • 静态存储期。存储期是程序的整个执行过程,并且对象中存储的值只初始化一次,在main函数之前。所有声明为static的对象以及所有具有内部或外部链接但未声明为_Thread_local(C23前)thread_local(C23起)(C11起)的对象都具有此存储期。
  • 线程存储期。存储期是创建它的线程的整个执行过程,并且对象中存储的值在线程启动时初始化。每个线程都有自己的、独立的对象。如果执行访问此对象的表达式的线程不是执行其初始化的线程,则行为是实现定义的。所有声明为_Thread_local(C23前)thread_local(C23起)的对象都具有此存储期。
(C11 起)
  • 分配存储期。使用动态内存分配函数,根据请求分配和解除分配存储。

[编辑] 链接

链接指的是标识符(变量或函数)在其他作用域中被引用的能力。如果一个具有相同标识符的变量或函数在多个作用域中声明,但不能从所有作用域中被引用,那么会生成该变量的多个实例。识别以下链接

  • 无链接。变量或函数只能从其所在的作用域(块作用域)中引用。所有未声明为extern的块作用域变量,以及所有函数参数和所有非函数或变量的标识符都具有此链接。
  • 内部链接。变量或函数可以从当前翻译单元中的所有作用域中引用。所有声明为staticconstexpr(C23起)的文件作用域变量都具有此链接,以及所有声明为static的文件作用域函数(静态函数声明只允许在文件作用域)。
  • 外部链接。变量或函数可以从整个程序中的任何其他翻译单元中引用。所有未声明为staticconstexpr(C23起)的文件作用域变量都具有此链接,所有未声明为static的文件作用域函数声明,所有块作用域函数声明,以及,如果之前没有可见的内部链接声明,所有声明为extern的变量或函数都具有此链接。

如果同一标识符在同一翻译单元中同时出现内部链接和外部链接,则行为是未定义的。这在使用了暂定定义时可能发生。

[编辑] 链接和库

具有外部链接的声明通常在头文件中提供,以便所有#include该文件的翻译单元都可以引用在其他地方定义的相同标识符。

任何出现在头文件中的具有内部链接的声明都会在每个包含该文件的翻译单元中产生一个单独且不同的对象。

库接口,头文件 "flib.h"

#ifndef FLIB_H
#define FLIB_H
void f(void);              // function declaration with external linkage
extern int state;          // variable declaration with external linkage
static const int size = 5; // definition of a read-only variable with internal linkage
enum { MAX = 10 };         // constant definition
inline int sum (int a, int b) { return a + b; } // inline function definition
#endif // FLIB_H

库实现,源文件 "flib.c"

#include "flib.h"
 
static void local_f(int s) {} // definition with internal linkage (only used in this file)
static int local_state;       // definition with internal linkage (only used in this file)
 
int state;                       // definition with external linkage (used by main.c)
void f(void) { local_f(state); } // definition with external linkage (used by main.c)

应用程序代码,源文件 "main.c"

#include "flib.h"
 
int main(void)
{
    int x[MAX] = {size}; // uses the constant and the read-only variable
    state = 7;           // modifies state in flib.c
    f();                 // calls f() in flib.c
}

[编辑] 关键词

auto, register, static, extern, _Thread_local thread_local

[编辑] 注意

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

(直至 C23)

typedefconstexpr(C23起)说明符在C语言语法中形式上被列为存储类说明符,但它们不指定存储。

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

(自 C23 起)

文件作用域中声明为const且不是extern的名称在C中具有外部链接(作为所有文件作用域声明的默认值),但在C++中具有内部链接。

[编辑] 示例

#include <stdio.h>
#include <stdlib.h>
 
// static storage duration
int A;
 
int main(void)
{
    printf("&A = %p\n", (void*)&A);
 
    // automatic storage duration
    int A = 1;   // hides global A
    printf("&A = %p\n", (void*)&A);
 
    // allocated storage duration
    int* ptr_1 = malloc(sizeof(int));   // start allocated storage duration
    printf("address of int in allocated memory = %p\n", (void*)ptr_1);
    free(ptr_1);                        // stop allocated storage duration
}

可能的输出

&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 标识符的链接 (p: 30-31)
  • 6.2.4 对象的存储期 (p: 32)
  • 6.7.1 存储类说明符 (p: 98-99)
  • C89/C90 标准 (ISO/IEC 9899:1990)
  • 3.1.2.2 标识符的链接
  • 3.1.2.4 对象的存储期
  • 3.5.1 存储类说明符

[编辑] 另请参阅

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