Namespaces
Variants

Storage class specifiers

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

存储类说明符是名称 声明语法 decl-specifier-seq 的组成部分。它们与名称的 作用域 共同控制着名称的两个独立属性:其 存储期 和其 链接

目录

存储期

存储期 对象 的一种属性,它定义了包含该对象的存储空间的最小潜在生命周期。存储期由创建对象时使用的构造决定,且为下列之一:

  • static storage duration
  • 线程存储期(亦称为线程局部存储期)
(since C++11)
  • 自动存储期
  • 动态存储期

静态 、线程 (C++11 起) 和自动存储期与通过 声明 引入的对象以及 临时对象 相关联。动态存储期与通过 new 表达式 创建的对象或 隐式创建的对象 相关联。

存储期类别同样适用于引用。

子对象 与引用成员的存储周期与其完整对象的存储周期相同。

说明符

以下关键字为 存储类说明符 :

  • auto
(C++11前)
  • register
(C++17前)
  • static
  • thread_local
(自 C++11 起)
  • extern
  • mutable

声明说明符序列 中,最多只能有一个存储类说明符 ,但 thread_local 可以与 static extern 同时出现 (C++11 起)

mutable 对存储期没有影响。关于其用法,请参阅 const/volatile

其他存储类说明符可以出现在以下声明的 声明说明符序列 中:

说明符 可出现在以下声明的 声明说明符序列
变量声明 函数声明 结构化绑定声明
(C++17 起)
非成员 成员 非成员 成员
非参数 函数参数 非静态 静态 非静态 静态
auto 仅块作用域 不适用
register 仅块作用域 不适用
static 声明静态成员 仅命名空间作用域 声明静态函数
thread_local
extern

匿名联合 也可以使用 static 进行声明。

register 是一个提示,表明如此声明的变量将被频繁使用,因此其值可以存储在CPU寄存器中。该提示可能被忽略,在大多数实现中,如果获取了变量的地址,该提示就会被忽略。此用法已被弃用。

(直至 C++17)

静态存储期

满足以下所有条件的变量具有 静态存储期 :

  • 它不具有线程存储期。
(since C++11)

这些实体的存储周期与程序运行期间相同。

线程存储期

所有用 thread_local 声明的变量都具有 线程存储期

这些实体的存储期持续于其被创建的线程的整个生命周期。每个线程拥有独立的对象或引用,使用声明名称时指向与当前线程关联的实体。

(C++11 起)

自动存储期

以下变量具有 自动存储期 :

  • 属于 块作用域 且未显式声明为 static thread_local (C++11 起) extern 的变量。此类变量的存储期持续到其所在代码块执行结束。
  • 属于参数作用域(即函数参数)的变量。函数参数的存储期持续到其 析构 完成后立即结束。

动态存储期

程序执行期间通过以下方式创建的对象具有 动态存储期

链接

一个名称可以具有 外部链接  , 模块链接 (C++20 起) , 内部链接 无链接

  • 具有模块链接性的实体可以在另一个翻译单元中重新声明,只要该重新声明隶属于同一模块。
(since C++20)
  • 具有内部链接名称的实体可以在同一翻译单元的其他作用域中重新声明。
  • 不具有链接名称的实体只能在同一作用域内重新声明。

以下链接关系已被识别:

无链接

以下在块作用域声明的名称均无链接:

  • 未显式声明为 extern 的变量(无论是否包含 static 修饰符);
  • 局部类 及其成员函数;
  • 在块作用域声明的其他名称,如类型别名、枚举类型及枚举项。

未指定为外部 、模块 (C++20 起) 或内部链接的名称同样不具有链接属性,无论其声明于哪个作用域中。

内部链接

以下在命名空间作用域声明的名称具有内部链接:

  • 声明为 static 的变量 、变量模板 (C++14 起) 、函数或函数模板;
  • 非模板 (C++14 起) 非 volatile const 限定类型的变量,除非
  • 它们是内联的,
(since C++17)
(since C++20)
  • 它们被显式声明为 extern ,或
  • 它们先前已被声明,且先前的声明未具有内部链接;

此外,所有在 无名命名空间 内或无名命名空间内的命名空间中声明的名称,即使显式声明为 extern ,都具有内部链接。

(C++11 起)

外部链接

具有外部链接的变量和函数还拥有 语言链接 特性,这使得使用不同编程语言编写的翻译单元能够进行链接。

以下在命名空间作用域声明的名称均具有外部链接,除非它们声明于无名命名空间 ,或其声明附属于具名模块且未被导出 (C++20 起)

  • 未在上方列出的变量与函数(即未声明为 static 的函数、未声明为 static 的非 const 变量,以及任何声明为 extern 的变量);
  • 枚举类型;
  • 类的名称、其成员函数、静态数据成员(无论是否为 const)、嵌套类和枚举类型,以及在类体内通过 friend 声明首次引入的函数;
  • 未在上方列出的所有模板名称(即未声明为 static 的函数模板)。

以下名称若首次在块作用域声明,则具有外部链接:

  • 声明为 extern 的变量名称;
  • 函数名称。

模块链接

在命名空间作用域声明的名称具有模块链接性,如果其声明附属于具名模块且未被导出,并且不具有内部链接性。

(since C++20)

静态块变量

具有静态 或线程 (C++11 起) 存储期的块作用域变量,在控制首次经过其声明时被初始化(除非其初始化是 零初始化 常量初始化 ,这些初始化可以在首次进入块之前执行)。在所有后续调用中,该声明将被跳过。

  • 如果初始化过程 抛出异常 ,该变量不被视为已完成初始化,下次控制流经过该声明时将再次尝试初始化。
  • 如果初始化过程递归进入正在初始化该变量的代码块,则行为未定义。
  • 如果多个线程尝试并发初始化同一个静态局部变量,初始化操作只会精确执行一次(类似行为可通过 std::call_once 对任意函数实现)。
  • 该特性的常规实现采用双检锁模式的变体,这将已初始化局部静态变量的运行时开销降低至单个非原子布尔值比较。
(since C++11)

具有静态存储期的块变量 析构函数会在程序退出时被调用 ,但仅当初始化成功完成时才会执行。

在所有相同 内联函数 (可能是隐式内联的)定义中,具有静态存储期的变量都指向同一翻译单元中定义的同一对象,只要该函数具有外部链接。

翻译单元局部实体

C++20 标准化了翻译单元局部实体的概念,详见 此页面 获取更多信息。

一个实体是 翻译单元局部 (或简称 TU局部 )的,如果

  • 它具有内部链接的名称,或
  • 它没有具有链接的名称,并在一个翻译单元局部实体的定义内被引入,或
  • 它是一个模板或模板特化,其模板参数或模板声明使用了翻译单元局部实体。

若以下情况发生,可能导致问题(通常是违反 ODR ):非翻译单元局部实体的类型依赖于翻译单元局部实体,或对非翻译单元局部实体的声明 (或 (C++17 起) 推导指引 在其外部引用了翻译单元局部实体。

  • 非内联函数或函数模板的函数体
  • 变量或变量模板的初始化器
  • 类定义中的友元声明
  • 变量值的用法,如果该变量 可用于常量表达式

此类用法在 模块接口单元 (及其私有模块片段之外,如果存在)或模块分区中不被允许,在其他任何上下文中均已弃用。

出现在一个翻译单元中的声明不能命名在另一个非头文件单元的翻译单元中声明的TU局部实体。为 模板 实例化的声明出现在特化的实例化点。

(since C++20)

注释

在顶层命名空间作用域(C语言中称为文件作用域)中,被声明为 const 且未使用 extern 的命名实体在C语言中具有外部链接属性,但在C++中具有内部链接属性。

自C++11起, auto 不再是存储类说明符;它被用于指示类型推导。

在C语言中,不能获取 register 变量的地址,但在C++中,声明为 register 的变量在语义上与未声明任何存储类说明符的变量没有区别。

(C++17 前)

在C++中,与C语言不同,变量不能声明为 register

(C++17 起)

在不同作用域中引用的具有内部或外部链接的 thread_local 变量名称,可能指向相同或不同的实例,这取决于代码是在相同线程还是不同线程中执行。

extern 关键字还可用于指定 语言链接 显式模板实例化声明 ,但在这些情况下它并非存储类说明符(除非声明直接包含在语言链接规范中,此时该声明会被视为包含 extern 说明符)。

除了 thread_local 之外,存储类说明符不允许出现在 显式特化 显式实例化 中:

template<class T>
struct S
{
    thread_local static int tlm;
};
template<>
thread_local int S<float>::tlm = 0; // 此处未出现 "static" 关键字

constexpr 隐含的 const 变量模板原先默认具有内部链接,这与其他模板化实体不一致。缺陷报告 CWG2387 修正了这一问题。

(C++14 起)
inline 通过默认提供外部链接,可作为 CWG2387 的解决方案。这就是为何 inline 加入 到许多变量模板中,并在 CWG2387 被接受后 移除 的原因。只要支持的编译器尚未实现 CWG2387,标准库实现仍需使用 inline 。参见 GCC Bugzilla #109126 MSVC STL PR #4546 (C++17 起)
功能测试宏 标准 功能
__cpp_threadsafe_static_init 200806L (C++11) 动态初始化与销毁的并发支持

关键词

auto register static extern thread_local mutable

示例

#include <iostream>
#include <mutex>
#include <string>
#include <thread>
thread_local unsigned int rage = 1;
std::mutex cout_mutex;
void increase_rage(const std::string& thread_name)
{
    ++rage; // 在锁外修改是安全的;这是线程局部变量
    std::lock_guard<std::mutex> lock(cout_mutex);
    std::cout << "Rage counter for " << thread_name << ": " << rage << '\n';
}
int main()
{
    std::thread a(increase_rage, "a"), b(increase_rage, "b");
    {
        std::lock_guard<std::mutex> lock(cout_mutex);
        std::cout << "Rage counter for main: " << rage << '\n';
    }
    a.join();
    b.join();
}

可能的输出:

Rage counter for a: 2
Rage counter for main: 1
Rage counter for b: 2

缺陷报告

以下行为变更缺陷报告被追溯应用于先前发布的C++标准。

缺陷报告 适用版本 发布时行为 正确行为
CWG 216 C++98 类作用域中的无名类和枚举与命名空间作用域中的具有
不同的链接性
在这些作用域中均具有外部
链接性
CWG 389 C++98 无链接性的名称不应被用于
声明具有链接性的实体
无链接性的类型不得用作具有链接性的变量或函数
的类型,除非该变量或函数具有C语言链接性
CWG 426 C++98 同一翻译单元中可能声明同时具有内部
和外部链接性的实体
此情况下程序非良构
CWG 527 C++98 CWG 389决议引入的类型限制也适用于
无法在其翻译单元外被引用的变量和函数
对此类变量和函数取消限制
(即无链接性或内部链接性,或声明
在无名命名空间内)
CWG 809 C++98 register 几乎不起作用 弃用
CWG 1648 C++11 即使 thread_local extern 组合使用
仍隐含 static
仅当不存在其他存储类
说明符时隐含
CWG 1686 C++98
C++11
命名空间作用域中声明的非静态变量名仅在显式声明为
const (C++98)或 constexpr (C++11)时
才具有内部链接性
仅要求类型
具有const限定
CWG 2019 C++98 引用成员的存储
期限未明确
与其完整对象相同
CWG 2387 C++14 未明确const限定的变量模板
是否默认具有内部链接性
const限定符不影响
变量模板或其实例的
链接性
CWG 2533 C++98 隐式创建对象的
存储期限不明确
予以明确
CWG 2850 C++98 函数参数的存储空间
何时释放不明确
予以明确
CWG 2872 C++98 “可被引用”的含义不明确 改进措辞
P2788R0 C++20 在命名空间中声明const限定变量
即使在模块单元中也会赋予其内部链接性
不赋予内部链接性

参考文献

  • C++23 标准 (ISO/IEC 14882:2024):
  • 6.7.5 存储期 [basic.stc]
  • C++20 标准 (ISO/IEC 14882:2020):
  • 6.7.5 存储期 [basic.stc]
  • C++17 标准 (ISO/IEC 14882:2017):
  • 6.7 存储期 [basic.stc]
  • C++14 标准 (ISO/IEC 14882:2014):
  • 3.7 存储期 [basic.stc]
  • C++11 标准 (ISO/IEC 14882:2011):
  • 3.7 存储期 [basic.stc]
  • C++03 标准 (ISO/IEC 14882:2003):
  • 3.7 存储期 [basic.stc]
  • C++98 标准 (ISO/IEC 14882:1998):
  • 3.7 存储期 [basic.stc]

参见

C 文档 关于 存储期