命名空间
变体
操作

存储类说明符

来自 cppreference.cn
< cpp‎ | language
 
 
C++ 语言
通用主题
流程控制
条件执行语句
if
迭代语句(循环)
for
范围 for (C++11 起)
跳转语句
函数
函数声明
Lambda 函数表达式
inline 说明符
动态异常规范 C++17* 前已弃用
noexcept 说明符 (C++11 起)
异常
命名空间
类型
说明符
const/volatile
decltype (C++11 起)
auto (C++11 起)
constexpr (C++11 起)
consteval (C++20 起)
constinit (C++20 起)
存储期说明符
初始化
表达式
替用表示
字面量
布尔 - 整数 - 浮点
字符 - 字符串 - nullptr (C++11 起)
用户定义 (C++11 起)
工具
属性 (C++11 起)
类型
typedef 声明
类型别名声明 (C++11 起)
转型
内存分配
类特有函数属性
虚函数
override 说明符 (C++11 起)  
final 说明符 (C++11 起)
explicit (C++11 起)
static

特殊成员函数
模板
模板特化
形参包 (C++11 起)
杂项
 
 

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

目录

[[编辑]] 存储期

存储期对象的一个属性,它定义了包含该对象的存储的最小潜在生命周期。 存储期由用于创建对象的构造决定,并且是以下之一

  • 静态存储期
  • 线程存储期
(C++11 起)
  • 自动存储期
  • 动态存储期

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

存储期类别也适用于引用。

子对象和引用成员的存储期是其完整对象的存储期。

[[编辑]] 说明符

以下关键字是存储类说明符 

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

decl-specifier-seq 中,最多可以有一个存储类说明符,除非 thread_local 可以与 staticextern 一起出现(C++11 起)

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

其他存储类说明符可以出现在以下声明的 decl-specifier-seq s 中

说明符 可以出现在 decl-specifier-seq s 中
变量声明 函数声明 结构化绑定声明
(C++17 起)
非成员 成员 非成员 成员
非形参 函数形参 非静态  静态  非静态  静态 
auto 仅限块作用域 不适用
register 仅限块作用域 不适用
static 声明为静态 仅限命名空间作用域 声明为静态
 thread_local 
extern

匿名联合体也可以用 static 声明。

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

(C++17 前)

[[编辑]] 静态存储期

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

  • 它没有线程存储期。
(C++11 起)

这些实体的存储持续程序的整个生命周期。

线程存储期

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

这些实体的存储持续它们在其中创建的线程的生命周期。 每个线程都有一个不同的对象或引用,并且声明名称的使用是指与当前线程关联的实体。

(C++11 起)

[[编辑]] 自动存储期

以下变量具有自动存储期 

  • 属于块作用域且未显式声明为 staticthread_local(C++11 起)extern 的变量。 这些变量的存储持续到它们在其中创建的块退出为止。
  • 属于形参作用域(即函数形参)的变量。 函数形参的存储持续到其析构之后立即结束。

[[编辑]] 动态存储期

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

[[编辑]] 链接

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

  • 名称具有模块链接的实体可以在另一个翻译单元中重新声明,只要重新声明附加到同一模块即可。
(C++20 起)
  • 名称具有内部链接的实体可以在同一翻译单元中的另一个作用域中重新声明。
  • 名称没有链接的实体只能在同一作用域中重新声明。

识别以下链接

[[编辑]] 无链接

在块作用域中声明的以下任何名称都没有链接

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

未指定为外部、模块或(C++20 起)内部链接的名称也没有链接,无论它们在哪个作用域中声明。

[[编辑]] 内部链接

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

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

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

(C++11 起)

[[编辑]] 外部链接

具有外部链接的变量和函数也具有语言链接,这使得链接用不同编程语言编写的翻译单元成为可能。

在命名空间作用域中声明的以下任何名称都具有外部链接,除非它们在未命名的命名空间中声明或它们的声明附加到命名的模块并且未导出(C++20 起)

  • 上面未列出的变量和函数(即,未声明为 static 的函数,未声明为 static 的非 const 变量,以及任何声明为 extern 的变量);
  • 枚举;
  • 类名、其成员函数、静态数据成员(const 或非 const)、嵌套类和枚举,以及首次在类体内部使用 friend 声明引入的函数;
  • 上面未列出的所有模板的名称(即,未声明为 static 的函数模板)。

在块作用域中首次声明的以下任何名称都具有外部链接

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

模块链接

如果命名空间作用域中声明的名称的声明附加到命名的模块并且未导出,并且没有内部链接,则该名称具有模块链接。

(C++20 起)

[[编辑]] 静态块变量

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

  • 如果初始化抛出异常,则变量不被视为已初始化,并且下次控制通过声明时将再次尝试初始化。
  • 如果初始化递归地进入正在初始化的变量所在的块,则行为未定义。
  • 如果多个线程尝试并发初始化同一个静态局部变量,则初始化只发生一次(对于具有 std::call_once 的任意函数,可以获得类似的行为)。
  • 此功能的常用实现使用双重检查锁定模式的变体,这会将已初始化的局部静态变量的运行时开销降低到单个非原子布尔比较。
(C++11 起)

静态存储期的块变量的析构函数在程序退出时调用,但前提是初始化已成功进行。

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

[[编辑]] 翻译单元局部实体

翻译单元局部实体的概念在 C++20 中标准化,有关更多详细信息,请参见此页面

如果实体是以下情况,则实体是翻译单元局部的(或简称 TU 局部

  • 它具有带内部链接的名称,或者
  • 它没有带链接的名称,并且是在 TU 局部实体的定义中引入的,或者
  • 它是模板或模板特化,其模板参数或模板声明使用 TU 局部实体。

如果非 TU 局部实体的类型取决于 TU 局部实体,或者如果非 TU 局部实体的声明或推导指引(C++17 起)命名了 TU 局部实体,则可能会发生糟糕的事情(通常违反 ODR),并且该 TU 局部实体在其外部

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

模块接口单元(在其私有模块片段之外,如果有的话)或模块划分中,不允许此类使用,并且在任何其他上下文中都已弃用。

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

(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" does not appear here

用于具有内部链接的默认值的 const (可能由 constexpr 暗示)变量模板,与其他模板实体不一致。 缺陷报告 CWG2387 对此进行了更正。

(C++14 起)
inline 充当 CWG2387 的解决方法,默认情况下提供外部链接。 这就是为什么 inline添加到许多变量模板,然后在 CWG2387 被接受后删除的原因。 只要支持的编译器尚未实现 CWG2387,标准库实现也需要使用 inline。 请参见 GCC Bugzilla #109126MSVC STL PR #4546 (C++17 起)
特性测试宏 Std 特性
__cpp_threadsafe_static_init 200806L (C++11) 具有并发性的动态初始化和销毁

[[编辑]] 关键字

autoregisterstaticexternthread_localmutable

[[编辑]] 示例

#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; // modifying outside a lock is okay; this is a thread-local variable
    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++ 标准。

DR 应用于 已发布行为 正确行为
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 static 即使
thread_localextern 结合使用,也是隐含的
仅当没有其他存储
类说明符存在时才隐含
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 文档 关于 存储持续时间