存储类说明符
存储类说明符是名字的声明语法中的声明说明符序列 的一部分。它与名字的作用域一同控制名字的两个独立性质:它的“存储期”和它的“链接”。
存储期
存储期 是对象的一种属性,该属性定义了包含该对象的存储的最短潜在生存期。存储期由创建对象的方式决定,并且是以下之一:
- 静态存储期
|
(C++11 起) |
- 自动存储期
- 动态存储期
静态、线程 (C++11 起)和自动存储期关联到由声明引入的对象和临时对象。动态存储期关联到由 new 表达式创建的对象和隐式创建的对象。
这些存储期类别同样适用于引用。
子对象和引用成员的存储期就是它们的完整对象的存储期。
说明符
以下关键词是存储类说明符 :
|
(C++11 前) |
|
(C++17 前) |
- static
|
(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 前) |
静态存储期
满足以下所有条件的变量具有静态存储期:
- 它属于命名空间作用域,或者首先声明有 static 或 extern。
|
(C++11 起) |
这些实体的存储在程序运行期间持续存在。
线程存储期所有声明有 thread_local 的变量都具有线程存储期。 这些实体的存储在创建它们的线程的持续期间持续存在。每个线程都有一个不同的对象或引用,并且使用声明的名字时会指代与当前线程关联的实体。 |
(C++11 起) |
自动存储期
以下变量具有自动存储期:
- 属于块作用域并且未显式声明有 static、thread_local (C++11 起) 或 extern 的变量。此类变量的存储持续到它们创建时所在的块退出时。
- 属于形参作用域的变量(即函数形参)。函数形参的存储持续到它销毁完成时。
动态存储期
在程序执行过程中通过以下方式创建的对象具有动态存储期:
- new 表达式。此类对象的存储会通过分配函数进行分配,并且会通过解分配函数进行解分配。
- 通过其他方式隐式创建的对象。此类对象的存储与某个已存在的存储重叠。
- 异常对象。此类对象的存储的分配和解分配方式未指定。
链接
名字可以具有外部链接 、模块链接 (C++20 起)、内部链接 或无链接:
|
(C++20 起) |
- 名字具有内部链接的实体可以在同一翻译单元的其他作用域重声明。
- 名字无链接的实体只能在同一作用域重声明。
以下各种链接可以被识别:
无链接
在块作用域声明的下列任何名字都无链接:
- 未显式声明有 extern 的变量(不管有没有 static 修饰符);
- 局部类和它的成员函数;
- 声明于块作用域的其他名字,例如 typedef、枚举及枚举项。
未指定为具有外部、模块 (C++20 起)或内部链接的名字同样无链接,这与它的声明所处的作用域无关。
内部链接
声明于命名空间作用域的下列任何名字都具有内部链接:
- 声明为 static 的变量、变量模板 (C++14 起)、函数或函数模板;
- 类型具有 const 限定 但没有 volatile 限定的非模板 (C++14 起)变量,除非:
|
(C++17 起) |
(C++20 起) |
- 它显式声明为 extern,或
- 它已在先前声明,而且先前的声明不具有内部链接;
- 匿名联合体的数据成员。
另外,所有声明于无名命名空间或无名命名空间内的命名空间的名字,即使显式声明为 extern 也都具有内部链接。 |
(C++11 起) |
外部链接
具有外部链接的变量和函数也具有语言链接,这使得以不同编程语言编写的翻译单元可以互相链接。
声明于命名空间作用域的下列任何名字都具有外部链接,除非这些名字声明于无名命名空间或它们声明于具名模块且未被导出 (C++20 起):
- 以上未列出的变量与函数(即未声明为 static 的函数、命名空间作用域内未声明为 static 的非 const 变量,和所有声明为 extern 的变量);
- 枚举;
- 类以及其成员函数、静态数据成员(不论是否 const)、嵌套类及枚举,及首次以类体内的 friend 声明引入的函数的名字;
- 所有未列于上的模板名(即未声明为 static 的函数模板)。
任何于块作用域首次声明的下列名称都具有外部链接:
- 声明为 extern 的变量名;
- 函数名。
模块链接如果声明于命名空间作用域的名字附着到具名模块,未被导出且无内部链接,那么它具有模块链接。 |
(C++20 起) |
本节未完成 原因:add the description of the behavior when an entity is declared with different linkages in the same translation unit (6.6 paragraph 6), note the difference between C++20 (ill-formed) and the current draft (well-formed) |
静态块变量
具有静态或线程 (C++11 起)存储期的块变量在控制首次经过它的声明时才会被初始化(除非它被零初始化或常量初始化,这可以在首次进入块前进行)。在其后所有的调用中,声明都会被跳过。
- 如果初始化抛出异常,那么不认为变量被初始化,且控制下次经过该声明时将再次尝试初始化。
- 如果初始化递归地进入正在初始化的变量的块,那么行为未定义。
|
(C++11 起) |
具有静态存储期的块变量的析构函数在程序退出时调用,但仅限在它的初始化已成功的情况下。
在相同内联函数(可以是隐式内联)的所有定义中的具有静态存储期的变量指代的都是在一个翻译单元中定义的同一对象,只要函数拥有外部链接。
翻译单元局部实体
C++20 中标准化了翻译单元局部实体的概念,详情见此页面。
满足以下情况的实体是翻译单元局部(简称为 TU 局部)的:
- 它的名字具有内部链接,或者
- 它没有具有链接的名字,且它是在 TU 局部实体的定义之内引入的,或者
- 它是模板或模板特化,其模板实参或模板声明使用了 TU 局部实体。
如果非 TU 局部实体依赖于某个 TU 局部实体,或者非 TU 局部实体的声明或其推导指引 (C++17 起),在以下位置之外对某个 TU 局部实体进行指名,那么就会发生不好的事(通常是违反 ODR):
- 函数体(对于非内联函数或函数模板)
- 初始化器(对于变量或变量模板)
- 类定义中的友元声明
- 使用某个变量的值,该变量可用于常量表达式
模块接口单元(其私有模块片段之外,如果有)或模块分区中不允许进行这种使用,而且其他语境中也都已被弃用。 在一个翻译单元中出现的声明,无法指名另一并非头文件单元的翻译单元中声明的 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"
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; // 在锁外修改 OK;这是线程局部变量 std::lock_guard<std::mutex> lock(cout_mutex); std::cout << 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 << "main 的愤怒计数:" << rage << '\n'; } a.join(); b.join(); }
可能的输出:
a 的愤怒计数:2 main 的愤怒计数:1 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 |
只有在单独使用 thread_local 时 才意味着应用了 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 Storage duration [basic.stc]
- C++20 标准(ISO/IEC 14882:2020):
- 6.7.5 Storage duration [basic.stc]
- C++17 标准(ISO/IEC 14882:2017):
- 6.7 Storage duration [basic.stc]
- C++14 标准(ISO/IEC 14882:2014):
- 3.7 Storage duration [basic.stc]
- C++11 标准(ISO/IEC 14882:2011):
- 3.7 Storage duration [basic.stc]
- C++03 标准(ISO/IEC 14882:2003):
- 3.7 Storage duration [basic.stc]
- C++98 标准(ISO/IEC 14882:1998):
- 3.7 Storage duration [basic.stc]