存储类说明符
存储类说明符是名称的声明语法中decl-specifier-seq的一部分。它们与名称的作用域一起控制名称的两个独立属性:其存储期和其链接。
内容 |
[编辑] 存储期
存储期是对象的属性,它定义了包含该对象的存储的最小潜在生存期。存储期由用于创建对象的构造决定,并且是以下之一
- 静态存储期
|
(自 C++11 起) |
- 自动存储期
- 动态存储期
静态、线程(自 C++11 起)和自动存储期与由声明引入的对象以及临时对象相关联。动态存储期与由new 表达式创建的对象或隐式创建的对象相关联。
存储期类别也适用于引用。
子对象和引用成员的存储期与其完整对象的存储期相同。
[编辑] 说明符
以下关键字是存储类说明符
|
(直到 C++11) |
|
(直到 C++17) |
- static
|
(自 C++11 起) |
- extern
- mutable
在 decl-specifier-seq 中,最多只能有一个存储类说明符,但 thread_local 可以与 static 或 extern 一起出现(自 C++11 起)。
mutable 对存储期没有影响。有关其用法,请参见 const/volatile。
其他存储类说明符可以出现在以下声明的 decl-specifier-seq 中
说明符 | 可以出现在以下的 decl-specifier-seq 中 | ||||||||
---|---|---|---|---|---|---|---|---|---|
变量声明 | 函数声明 | 结构化绑定声明 (自 C++17 起) | |||||||
非成员 | 成员 | 非成员 | 成员 | ||||||
非参数 | 函数参数 | 非静态 | 静态 | 非静态 | 静态 | ||||
auto | 仅限块范围 | 是 | 否 | 否 | 否 | 否 | 否 | N/A | |
register | 仅限块范围 | 是 | 否 | 否 | 否 | 否 | 否 | N/A | |
static | 是 | 否 | 声明为静态 | 仅限命名空间范围 | 声明为静态 | 是 | |||
thread_local | 是 | 否 | 否 | 是 | 否 | 否 | 否 | 是 | |
extern | 是 | 否 | 否 | 否 | 是 | 否 | 否 | 否 |
匿名联合 也可以用 static 声明。
register 是一个提示,表明如此声明的变量将被频繁使用,因此其值可以存储在 CPU 寄存器中。此提示可以被忽略,在大多数实现中,如果获取变量的地址,则该提示会被忽略。此用法已过时。 |
(直到 C++17) |
[编辑] 静态存储期
满足以下所有条件的变量具有静态存储期
- 它属于 命名空间范围,或首次使用 static 或 extern 声明。
|
(自 C++11 起) |
这些实体的存储在程序持续时间内有效。
线程存储期所有用 thread_local 声明的变量都有线程存储期。 这些实体的存储在创建它们的线程持续时间内有效。每个线程都有一个不同的对象或引用,使用声明的名称是指与当前线程关联的实体。 |
(自 C++11 起) |
[编辑] 自动存储期
以下变量具有自动存储期
- 属于 块范围 且未明确声明 static、thread_local 或(自 C++11 起) extern 的变量。此类变量的存储在创建它们的块退出之前有效。
- 属于参数范围(即函数参数)的变量。函数参数的存储在 销毁 之后立即有效。
[编辑] 动态存储期
在程序执行期间,通过以下方法创建的对象具有动态存储期
[编辑] 链接
一个名称可以具有外部链接、模块链接(自 C++20 起)、内部链接或无链接
|
(自 C++20 起) |
- 具有内部链接的实体的名称可以在同一个翻译单元中的另一个作用域中重新声明。
- 具有无链接的实体的名称只能在同一个作用域中重新声明。
识别以下链接
[编辑] 无链接
以下在块范围内声明的任何名称都具有无链接
- 未明确声明 extern 的变量(无论 static 修饰符如何);
- 局部类 及其成员函数;
- 在块范围内声明的其他名称,例如类型定义、枚举和枚举成员。
未指定具有外部、模块或(自 C++20 起)内部链接的名称也具有无链接,无论它们在哪个作用域中声明。
[编辑] 内部链接
以下在命名空间范围内声明的任何名称都具有内部链接
- 声明为 static 的变量、变量模板(自 C++14 起)、函数或函数模板;
- 非模板(自 C++14 起) 的非易失性 const 限定类型变量,除非
|
(自 C++17 起) |
(自 C++20 起) |
- 它们明确声明为 extern,或
- 它们之前已声明,并且先前声明没有内部链接;
- 匿名联合 的数据成员。
此外,在 未命名命名空间 或未命名命名空间内的命名空间中声明的所有名称,即使是明确声明为 extern 的名称,也具有内部链接。 |
(自 C++11 起) |
[编辑] 外部链接
具有外部链接的变量和函数也具有 语言链接,这使得用不同编程语言编写的翻译单元链接成为可能。
以下在命名空间范围内声明的任何名称都具有外部链接,除非它们是在未命名命名空间中声明的,或者它们的声明附加到命名模块并且没有导出(自 C++20 起)
- 上面未列出的变量和函数(即,未声明为 static 的函数、未声明为 static 的非常量变量,以及任何声明为 extern 的变量);
- 枚举;
- 类、其成员函数、静态数据成员(常量或非常量)、嵌套类和枚举以及在类主体中使用 friend 声明首次引入的函数的名称;
- 上面未列出的所有模板的名称(即,未声明为 static 的函数模板)。
以下在块范围内首次声明的任何名称都具有外部链接
- 声明为 extern 的变量的名称;
- 函数的名称。
模块链接在命名空间范围内声明的名称,如果它们的声明附加到命名模块并且没有导出,并且没有内部链接,则具有模块链接。 |
(自 C++20 起) |
本节不完整 原因:添加当同一个翻译单元中实体声明具有不同链接时的行为描述(6.6 节第 6 段),注意 C++20(格式错误)和当前草案(格式正确)之间的差异。 |
[编辑] 静态块变量
具有 static 或 thread(自 C++11 起) 存储期的块变量在控制首次通过其声明时初始化(除非其初始化为 零- 或 常量初始化,这可以在块首次进入之前完成)。在所有后续调用中,将跳过声明。
- 如果初始化 抛出异常,则变量不被视为已初始化,并且将在下次控制通过声明时尝试再次初始化。
- 如果初始化递归地进入正在初始化变量的块,则行为未定义。
|
(自 C++11 起) |
具有静态存储期的块变量的析构函数 在程序退出时调用,但仅在初始化成功完成的情况下。
同一个 内联函数(可以是隐式内联)的所有定义中的具有静态存储期的变量都引用同一个在某个翻译单元中定义的对象,只要该函数具有外部链接。
[编辑] 翻译单元局部实体
翻译单元局部实体的概念在 C++20 中是标准化的,请参阅 此页面 以了解更多详细信息。
如果某个实体是翻译单元局部(或简称TU-局部),则
- 它的名称具有内部链接,或者
- 它没有与链接相关的名称,并且是在 TU 本地实体的定义中引入的,或者
- 它是一个模板或模板特化,其模板参数或模板声明使用 TU 本地实体。
如果非 TU 本地实体的类型依赖于 TU 本地实体,或者如果非 TU 本地实体的声明或其推导指南(自 C++17 起)在
- 非内联函数或函数模板的函数体之外
- 变量或变量模板的初始化器
- 类定义中的友元声明
- 变量值的用途,如果变量在常量表达式中可用
这种使用在模块接口单元(在其私有模块片段之外,如果有)或模块分区中是不允许的,并且在任何其他上下文中都已弃用。 出现在一个翻译单元中的声明不能命名另一个翻译单元中声明的不是头文件的 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 #109126和MSVC STL PR #4546。 |
(自 C++17 起) |
功能测试宏 | 值 | Std | 功能 |
---|---|---|---|
__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; // 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 | 即使 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 文档的存储持续时间
|