存储类指定符

来自cppreference.com
< cpp‎ | language

存储类指定符是名称声明语法decl-specifier-seq 的一部分。与名称的作用域一同,它们控制名称的二个独立属性:其“存储期”与其“链接”。

  • auto - 自动存储期。
(C++11 前)
  • register - 自动存储期。亦提示编译器将此对象置于处理器的寄存器。(弃用)
(C++17 前)
  • static - 静态线程存储期和内部链接。
  • extern - 静态线程存储期和外部链接。
  • thread_local - 线程存储期。
(C++11 起)


声明中只可以出现一个存储类指定符,除了 thread_local 可以与 staticextern 结合 (C++11 起)

目录

[编辑] 解释

1) auto 指定符仅对声明于块作用域或于函数参数列表的对象允许。它指示自动存储期,对于这种声明是默认选项。此关键词的含义于 C++11 更改。
(C++11 前)
2) register 仅对声明于块作用域和函数参数列表的对象允许。它指示自动存储期,对于这种声明是默认选项。另外,此关键词的存在可用于提示优化器将此变量的值存储于 CPU 寄存器。此关键词于 C++11 过时。
(C++17 前)
3) static 指定符仅在对象声明(除了函数参数列表中)、函数声明(除了块作用域中)及匿名联合体的声明中允许。用于类成员时,它声明静态成员。用于对象声明时,它指定静态存储期(除非为 thread_local 所伴随)。用于命名空间作用域的声明时,它指定内部链接。
4) extern 指定符仅在变量和函数的声明中允许(除了类成员或函数参数)。它指定外部链接,而且技术上不影响存储期,但它不能用于自定存储期对象的定义,故所有 extern 对象拥有静态或线程存储期。另外,使用 extern 且无初始化器的声明不是定义
5) thread_local 关键词只对声明于命名空间作用域的对象、声明于块作用域的对象及静态数据成员允许。它指示对象拥有线程存储期。它能与 staticextern 结合,以分别指定内部或外部链接(除了静态数据成员始终拥有外部链接),但附加的 static 不影响存储期。
(C++11 起)

[编辑] 存储期

程序中的所有对象拥有下列存储期之一:

  • 自动存储期。对象在外围代码块开始时分配,而在结束时解分配。所有局部对象拥有此存储期,除了声明为 staticexternthread_local 者。
  • 静态存储期。对象的存储在程序开始时分配,而在程序结束时解分配。只存在对象的一个实例。所有声明于命名空间作用域(包含全局命名空间)的对象,加上声明带有 staticextern 的对象拥有此存储期。
  • 线程存储期。对象在线程开始时分配,而在线程结束时解分配。每个线程拥有其自身的对象实例。唯有声明为 thread_local 的对象拥有此存储期。 thread_local 能与 staticextern 一同出现,以调整链接。
(C++11 起)
  • 动态存储期。通过使用动态内存分配函数,由请求分配和解分配对象。

[编辑] 链接

指代对象、引用、函数、类型、模板、命名空间或值的名称可拥有链接。若名称拥有链接,则其所指代的实体与另一作用域中的声明所引入的同一名称所指代者相同。若变量、函数或另一实体声明于数个作用域,但无充分的链接,则生成实体的数个实例。

辨认下列链接:

  • 无链接。名称只能从其所在的作用域使用。
任何声明于块作用域的下列名称无链接:
  • 不显式声明为 extern 的变量(无关乎 static 修饰符)
  • 局部类与其成员函数
  • 其他声明于块作用域的名称,例如 typedef 、枚举及枚举项。
  • 内部链接。名称可从当前翻译单元中的所有作用域使用。
任何声明于命名空间作用域的下列名称拥有内部链接
  • 声明为 static 的变量、函数或函数模板
  • 不是 extern 的,且先前未声明拥有外部链接的,非 volatile 非 inline (C++17 起) const 限定变量(包含 constexpr )。
  • 匿名联合体的数据成员。
另外,所有声明于无名命名空间或无名命名空间内的名称,即使是显式声明为 extern 者,均拥有内部链接。
(C++11 起)
  • 外部链接。名称能从其他翻译单元中的作用域使用。拥有外部链接的变量和函数亦拥有语言链接,这使得可以链接到以不同编程语言书写的翻译单元。
任何声明于命名空间作用域的下列名称拥有外部链接,除非命名空间无名或为无名命名空间所含有 (C++11 起)
  • 未列于上的变量与函数(即不声明为 static 的函数、不声明为 static 的命名空间作用域非 const 变量,和所有声明为 extern 的变量)
  • 枚举与枚举项
  • 类名、其成员函数、静态数据成员(不论是否 const )、嵌套类及枚举,及首次以类体内的 friend 声明引入的函数
  • 所有未列于上的模板名(即不声明为 static 的函数模板)
任何首次声明于块作用域的下列名称拥有外部链接
  • 声明为 extern 的变量名
  • 函数名

[编辑] 静态局部变量

声明于块作用域,带指定符 static 的变量拥有静态存储期,但在控制首次经过其声明时才得到初始化(除非其初始化是常量初始化,这可以在首次进入块前进行)。在所有进一步调用上,声明被跳过。

若初始化抛异常,则不认为变量被初始化,且控制下次经过该声明时,将再次尝试初始化。

若初始化递归地进入正在初始化的变量的块,则行为未定义。

若多个线程试图初始化同一静态局部变量,则初始化准确出现一次(可对任意函数以 std::call_once 取得类似行为)。

注意:此特性的通常实现使用双检查锁定模式的变体,这将已初始化的局部静态变量的运行时开销减少到单次非原子布尔比较。
(C++11 起)

块作用域静态变量的析构函数在程序退出时执行,但仅若初始化成功发生才执行。

相同 inline 函数(可以是隐式 inline )的所有定义中,函数局域的静态对象全部指代定义于一个翻译单元中的同一对象。

[编辑] 注意

位于顶层命名空间作用域( C 中的文件作用域),且是 const 而非 extern 在 C 中拥有外部链接,但在 C++ 中拥有内部链接。

C 中,不能取 register 变量的地址,但 C++ 中,声明为 register 的对象与声明不带任何存储类指定符的变量在语义上无法辨别。

(C++17 前)

C++ 中不同于 C ,变量不可声明为 register

(C++17 起)

从不同作用域指涉的,带内部或外部链接的 thread_local 变量名称,可能指代相同或不同实例,依赖于代码执行于相同还是不同的线程。

extern 关键词亦可用于指定语言链接显式模板实例化声明,但在这些情况中不是存储类指定符(除了声明直接为语言链接指定所含,该情况下将声明当做如同它含 extern 指定符)。

关键词 mutable 在 C++ 文法中是存储类指定符,尽管它不影响存储期或链接。

存储类指定符除了 thread_local ,都不允许存在于显式特化显式实例化

template <class T> struct S {
    thread_local static int tlm;
};
template <> thread_local int S<float>::tlm = 0; // "static" 不出现于此

[编辑] 关键词

auto, register, static, extern, thread_local

[编辑] 示例

#include <iostream>
#include <string>
#include <thread>
#include <mutex>
 
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 << "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 文档