存储类说明符
存储类说明符是名称声明语法的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 | 仅限块作用域 | 是 | 否 | 否 | 否 | 否 | 否 | 不适用 | |
register | 仅限块作用域 | 是 | 否 | 否 | 否 | 否 | 否 | 不适用 | |
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修饰符);
- 局部类及其成员函数;
- 在块作用域中声明的其他名称,如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)、嵌套类和枚举,以及在类体内首次通过friend声明引入的函数;
- 未在上面列出的所有模板名称(即未声明static的函数模板)。
以下在块作用域中首次声明的任何名称都具有外部链接
- 声明为extern的变量名称;
- 函数名称。
模块链接在命名空间作用域中声明的名称如果其声明附加到命名模块且未导出,并且不具有内部链接,则具有模块链接。 |
(C++20 起) |
本节不完整 理由:添加在同一翻译单元中声明具有不同链接的实体时的行为描述(6.6 段落 6),注意 C++20(格式错误)与当前草案(格式良好)之间的区别 |
[编辑] 静态块变量
具有静态或线程(C++11 起)存储期的块变量在控制流首次通过其声明时初始化(除非其初始化是零初始化或常量初始化,这可以在首次进入块之前执行)。在所有后续调用中,声明被跳过。
- 如果初始化抛出异常,则变量不被视为已初始化,并且在控制流下次通过声明时将再次尝试初始化。
- 如果初始化递归地进入正在初始化变量的块,则行为未定义。
|
(C++11 起) |
具有静态存储期的块变量的析构函数在程序退出时调用,但仅当初始化成功时。
在同一个内联函数(可能隐式内联)的所有定义中具有静态存储期的变量都引用在一个翻译单元中定义的同一个对象,只要该函数具有外部链接。
[编辑] 翻译单元局部实体
翻译单元局部实体的概念在 C++20 中标准化,更多详细信息请参见此页面。
如果满足以下条件,则实体是翻译单元局部的(简称TU-local):
- 它具有内部链接的名称,或者
- 它没有链接的名称,并且是在 TU 局部实体的定义中引入的,或者
- 它是一个模板或模板特化,其模板实参或模板声明使用 TU 局部实体。
如果非 TU 局部实体的类型依赖于 TU 局部实体,或者非 TU 局部实体的声明,或其推导指引(C++17 起)在以下之外命名了 TU 局部实体,则可能会发生不良情况(通常是违反ODR):
- 非内联函数或函数模板的函数体
- 变量或变量模板的初始化器
- 类定义中的友元声明
- 变量值的使用,如果该变量可在常量表达式中使用
此类用法在模块接口单元中(如果存在私有模块片段,则在其外部)或模块分区中是不允许的,并且在任何其他上下文中都已弃用。 在一个翻译单元中出现的声明不能命名在另一个非头单元的翻译单元中声明的 TU 局部实体。为模板实例化的声明出现在特化的实例化点。 |
(C++20 起) |
[编辑] 注意
C 中顶层命名空间作用域(文件作用域)中const且非extern的名称具有外部链接,但在 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 起) |
功能测试宏 | 值 | 标准 | 特性 |
---|---|---|---|
__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++ 标准。
缺陷报告 | 应用于 | 发布时的行为 | 正确的行为 |
---|---|---|---|
CWG 216 | C++98 | 类作用域中的未命名类和枚举具有 与命名空间作用域中不同的链接 |
它们都具有外部链接 在这些作用域中 |
CWG 389 | C++98 | 无链接的名称不应 用于声明具有链接的实体 |
无链接的类型不应被用作 具有链接的变量或函数的类型 除非该变量或函数具有 C 语言链接 或函数具有 C 语言链接 |
CWG 426 | C++98 | 一个实体可以在同一翻译单元中 同时声明为内部和外部链接 |
在这种情况下程序格式错误 |
CWG 527 | C++98 | CWG 389 决议引入的类型限制 也适用于不能在其自身翻译单元之外 命名的变量和函数 |
对于这些变量和函数(即无 链接或内部链接,或在未命名 命名空间内声明的)取消了限制 命名空间内声明的)取消了限制 |
CWG 809 | C++98 | register作用很小 | 已弃用 |
CWG 1648 | C++11 | static被隐式指定,即使 thread_local与extern结合使用 |
仅当没有其他存储 类说明符存在时才隐式指定 |
CWG 1686 | C++98 C++11 |
在命名空间作用域中声明的非静态变量的名称 只有在其显式声明为const(C++98)或constexpr(C++11)时才具有内部链接 只有在其显式声明为const(C++98)或constexpr(C++11)时才具有内部链接 |
仅要求类型为 const-qualified 为 const-qualified |
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 文档有关存储期
|