%%
# 纲要
> 主干纲要、Hint/线索/路标
# Q&A
#### 已明确
1. C/C++中**在不同代码位置、以不同形式定义的变量、函数的存储持续性**分别是什么?
2. **关键字对存储持续性的影响**
#### 待明确
> 当下仍存有的疑惑
**❓<font color="#c0504d"> 有什么问题?</font>**
%%
# 存储类说明符
存储类说明符包括:`auto`, `static`, `extern`, `register`, `thread_local`,`mutable`
「**存储类说明符**」与 「**名称的作用域**」一起控制名称的两个独立属性[^1]:
- **==存储持续性==**(storage duration)
- **==链接性==**(linkage)。
> [!NOTE] 各个 "存储类说明符" 可出现的位置
>
> ![[_attachment/02-开发笔记/01-cpp/cpp 基本概念/cpp-存储类说明符.assets/IMG-cpp-存储类说明符-3EC8D1204079CCF1D736D15451751855.png|805]]
>
<br>
### `auto` 说明符
> 此关键字自 C++11 起,作用及含义已改变,不再关于存储持续性,而是**用于自动类型推断**。
在 C++11 之前,用于在块作用域内或函数参数列表中显式声明对象为**自动存储持续性**,即自动变量。
(块作用域和函数参数中的变量默认就是自动变量,C++11 之前的 auto 通常都是省略的)
<br>
### `resiter` 说明符
> 此关键字**自 C++17 起已弃用**。
只用于在**块作用域**和**函数参数列表**中声明的对象,其 "**指示**" (而非强制) 优化器使用 CPU 寄存器存储变量值(现代编译器通常会自动做出最优决策)。
<br>
### `static` 说明符⭐
只允许在**对象声明 (函数参数除外)**、**函数声明(块作用域除外)**、**匿名 unions 声明**中使用。
其作用有两方面:
- 声明 "**==静态存储持续性==**":
- 用于**局部变量**,声明静态局部变量(除非同时带有 `thread_local` 声明为 "线程局部存储持续性")
- 用于**类成员声明**中,声明**静态成员变量**、**静态成员函数**
- 声明 "**==内部链接性==**"(针对原本具有 "外部链接性" 的实体名称)
- 用于 "**命名空间作用域**" 下的==**全局变量**==、==**普通函数**==时,修改为具有 "**内部链接性**"。
<br>
### `extern` 说明符⭐
`extern` 的作用有三个:
1. 声明引入一个 "**在其它地方定义的、本身具有"==外部链接性=="的变量或函数**",让编译器去**当前编译单元之外**查找定义。
2. **修饰全局 `const` 或 `constexpr` 常量**时,为其赋予 "**==外部链接性==**"(全局常量默认具有内部链接性)
3. 通过 `extern` 关键字来 **指定语言链接性** 以及 **显式模版实例化声明**,但在这些情况下**不作为存储类说明符**。
###### (1)用于引入外部变量或函数
`extern` 只允许在**变量和函数的声明中**使用(类成员或函数参数除外),可用在**命名空间作用域下声明引入**,也可用于在**块作用域(例如函数)中声明引入**。
> [!faq] "全局变量" 和 "非静态函数" 都具有外部链接性,为什么引入外部变量时必须声明 `extern`,但引入外部函数时却可省略 `extern` ?
>
> - 对于变量,使用 `extern` 旨在标识语句为 "**==变量声明==**" 而非定义,**如果不带有 `extern`,则编译器会视为 "变量定义"**(**无显式初始化项的变量定义**),故尝试**为该变量分配内存**,从而触发 "**重复定义**" 的错误。
>
>>
>
> - 对于函数,**函数声明**本身就只表示 "**引入一个函数名称**",链接器会根据函数声明在 "程序范围内" 查找函数定义,因此,`extern` 关键字对于引入外部函数而言是可有可无的。
###### (2)用于赋予全局常量 "外部链接性"
```cpp
// 下面四个全局常量都可在其它文件中通过`extern const`声明引入
// 将默认具有内部链接性的全局常量声明为具有外部链接性.
extern const int extCstVar = 15;
extern constexpr int extCstexprVar = 99;
// 也可以先通过`extern`声明为具有外部链接性, 然后再初始化.
extern const int extCstVar2;
const int extCstVar2 = 66;
extern const int extCstexprVar2; // 这里声明时只能先用`const`, 而不能用`constexpr`
constexpr int extCstexprVar2 = 55; // 因为`constexpr`声明的变量必须被显式初始化, 给定常量表达式
// 参考: https://stackoverflow.com/questions/45987571/difference-between-constexpr-and-static-constexpr-global-variable
```
> [!summary]
>
> - `static` 用于**默认具有外部链接性的全局变量、函数**时,修改为内部链接性
> - `extern` 用于**默认具有内部链接性的对象** (例如`const`全局常量) 时,修改为外部链接性。
###### (3)指定语言链接性、显式模版实例化声明
略。
<br>
### `thead_local` 说明符
> since C++11
声明具有 **==线程存储持续性==**(Thread-Local Storage,**TLS**),**各线程持有一份独立实例**,只允许对**在命名空间作用域声明的对象**、**在块作用域声明的对象**、**类的静态数据成员**使用。
> [!NOTE] **`thread_local` 可以与 `static` 或 `extern` 组合,指定内部或外部链接性**。
> (类的静态数据成员除外,因为其始终具有外部链接性 )
> [!example] C/C++ 中的 TLS 变量声明
>
>
> ```cpp
> // GCC风格的 TLS
> __thread int x = 0;
> // C++11标准的 TLS
> thread_local int y = 42;
> ```
>
> [!NOTE] 对于 "线程存储持续性" 变量,由 **系统库** 负责确定其内存区域并进行初始化
>
> 以 Linux 下 C++程序为例,当调用 `pthread_create()` 系统调用时,其背后会:
>
> 1. 为新线程分配 "**==栈区==**" + "**==TLS 区域==**"(根据 **ELF 文件中的 `PT_TLS` 段配置**);
> 2. 在线程入口处,**将新线程的 TLS 区域地址写入相应寄存器**(`x86-64` 中的 `%fs.base` 段寄存器)以及**线程控制块 TCB**
> 3. 跳转至执行**线程函数**
---
> [!caution] 除 `thread_local` 以外的存储类说明符都不允许用于" [explicit specializations](https://en.cppreference.com/w/cpp/language/template_specialization) and [explicit instantiations](https://en.cppreference.com/w/cpp/language/class_template#Explicit_instantiation)",如下所示:
```cpp
template<class T>
struct S
{
thread_local static int tlm;
};
template<>
thread_local int S<float>::tlm = 0; // "static" does not appear here
```
<br>
### `mutable` 说明符
**不影响存储持续性和链接性**,仅用于修饰类成员变量,标识 **==放宽对类成员变量的常量性限制==**:
当一个成员变量被声明为`mutable`时,**即使在一个`const`成员函数中,该成员变量仍然可以被修改**。
<br><br>
# 存储持续性 ⭐
存储持续性描述了 "**==实体对象的生命周期==**",即它们 "**被分配的内存空间**" 存在的时间持续性[^5]。
(此处的 "对象" 是指 C++ 内存模型层面的实体,包括**变量、函数** 等)
C++中存储持续性分为四种:
- **自动存储持续性**(automatic storage)
- **静态存储持续性**(static storage duration)
- **动态存储持续性**(dynamic storage duration)
- **线程存储持续性**(thread storage duration)
> [!NOTE] 子对象(subobjects)和引用成员(reference member)的存储持续性是它们的完整对象的存储持续性。
<br>
### 自动存储持续性
对象的内存在**封闭代码块的起始处被分配**,**在结束处被释放**。
即**在声明它们的代码块被执行时创建,在退出该代码块时被销毁**。例如函数、`{}` 代码块。
**除了被声明为`static`、`extern`、`thread_loacal` 以外** 的 **所有"==局部对象=="都具有自动存储持续性**,也称为 "**==自动变量==**"。
即 C++中下列两种对象为**自动存储持续性**:
1. 无 `static`、`extern`、`thread_local` 修饰的**局部变量**
2. **函数参数**
<br>
### 静态存储持续性
对象的内存在程序开始时分配,在程序结束时释放,**整个程序运行期间都存在**。
C++中下列对象具有静态存储持续性:
- **命名空间作用域(包括全局命名空间) 声明的所有实体**(`thread_local` 声明的除外)
- **带有 `static` 声明的对象**:静态全局变量、静态局部变量、**静态成员变量**、**静态成员函数**
- **带有 `extern` 声明的对象**
> [!NOTE] **同一内联函数**(具有**外部链接性** )的**所有定义**中的函数内==**静态局部变量**==都指向 **==单个翻译单元==** 中的 **==同一对象==**
<br>
### 动态存储持续性
通过 **==动态内存分配==(如使用`new`或`malloc`)创建的对象** 具有动态存储持续性。
对象的内存根据请求**通过动态内存分配函数进行分配和释放**,
即使用 `new`/`malloc` 分配的内存,**直到使用 `delete`/`free`释放** 或 **程序结束为止**。
<br>
### 线程存储持续性
只有**声明为 `thread_local` 的对象**才具有此存储持续性。
每个线程对 `thread_local` 对象都 **==持有一份自己的对象实例==**。
这类对象的内存**在线程开始时分配,在线程结束时释放**,生命周期与所属线程一样长。
> [!info] 局部静态变量,局部线程变量的初始化
>
> 块作用域中的`static` 或 `thread_local` 变量的分别具有静态、线程存储持续性,但这些变量的 **==初始化==** 只在程序 **==首次执行到他们被声明的位置时==** 才唯一执行(零初始化或常量初始化除外,这两种情况可在首次进入到块时就执行),**此后再次执行到块中的初始化语句时将跳过**。
>
> 块作用域静态变量的**析构函数**在程序退出时调用,但只有在初始化成功的情况下才会调用。
>
> 如果多个线程试图并发地初始化一个静态局部变量,则初始化只会实际执行一次。(C++11 标准保证对局部静态变量初始化的线程安全性)
>
> 
>
<br><br>
# 链接性 ⭐
C++中的链接性(Linkage)标识的是一个**实体名称**(对象、引用、函数、类型、模版、命名空间等)**在程序中不同位置的可见性** [^2]。
C++中一个 "**名称**" 的链接性包括下列几种:
- **无链接性**(No linkage):只在定义其的 **==作用域内==** 可见;
- **内部链接性**(Internal linkage):只在 "**==定义其的编译单元==**" 内可见;
- **外部链接性**(External linkage):**整个程序**中可见,即对 **==所有编译单元==** 可见。
- **模块链接性**(Module linkage) since C++20
> [!NOTE] 在一个编译上下文中,当对一个实体名称**多次声明**时, **其"链接性" 仅取决于首次声明**。
> [!quote] [What is external linkage and internal linkage?](https://stackoverflow.com/questions/1358400/what-is-external-linkage-and-internal-linkage)
>
> 
>
>
<br>
## 无链接性
无链接性的实体 **只在定义它们的==作用域内部==** 可见。
在「**==块作用域中==**」**定义**的下列实体名称都没有链接性:
- **局部变量**(包括 `static`修饰的 **==静态局部变量==**、`thread_local` 修饰的**局部线程变量**))
- **局部类**及其**成员函数**
- **局部定义的枚举类以及枚举成员**。
- 块作用域内**通过`typedef`、`using` 声明的其他名称**
<br>
## 内部链接性
具有内部链接性的实体**只在定义它们的编译单元内可见**。
在「**==命名空间作用域内==**」**定义**的下列实体名称都具有内部链接性:
- **声明为`static`的==静态全局变量==、变量模板 (自 c++ 14 起)
- **声明为`static` 的函数、函数模板**。
- **未被 "`extern`" 修饰的==全局常量==**( "**const-qualified"且"non-volatile"的 "non-templte" 类型的变量**)
- 在 "**匿名命名空间**"或 "**匿名命名空间内的命名空间中**" 声明的**所有名称**(即使显式声明为`extern`)
> [!NOTE] 全局变量和函数本身具有 "**外部链接性**",而声明为 "`staic`" 会将其修改为 "**具有==内部链接性==**"。
> [!NOTE] 通过 `const` 或 `constexpr` 定义的"**==全局常量==**" 本身具有 "**==内部链接性==**",加上 `extern` 修饰后会改为 "**具有==外部链接性==**"。
> [!caution] `static` 的优先级高于 `inline`,使变量具有内部链接性而不会跨翻译单元共享,后者失效,两个关键字组合使用没有实际意义。
<br>
## 外部链接性
具有外部链接性的实体 **在整个程序中可见,即在所有翻译单元中可见**。
在「**==命名空间作用域内(除未命名的命名空间)==**」定义的下列实体均具有外部链接性:
- `non-static` 且 `non-const` 的 **==全局变量==**
- `extern` 修饰的**全局==常量==**
- **==内联变量==**、**==内联常量==**( `inline const`)(since C++17)
- `non-static` 的 **==函数==、==函数模板==**(包括 **==内联函数==**)
- **==类类型 & 其所有成员函数(包括静态) & 其静态数据成员==** (const or not) & 其中定义的**嵌套类与嵌套枚举类**
- **枚举类** enumerations
> [!NOTE] 具有外部链接性的变量和函数同样具有语言链接性,因此可以链接使用不同编程语言编写的 TUs。
<br>
## 链接指示(语言链接性)
由于 C、C++在编译时对符号名的修饰规则不同[^3] [^4],当在 C++中使用 **预编译 C 库中的函数** 时,
需要 **使用`extern "C"` 对函数原型声明进行 "==链接指示=="**(linkage directive),
指示编译器对该函数**按照 "C 语言的名称修饰规则" 生成符号**,从而确保能够 "**正确链接**"[^6]。
用法说明:
```cpp
// use C protocol for name look-up
extern "C" void spiff(int);
// use C++ protocol for name look-up (C++中默认如此, 因此"C++"可省略)
extern "C++" void spaff(int);
extern void spbff(int);
// 可使用花括号, 对其中的所有适用于该链接指示的函数声明进行声明.
extern "C" {
size_t strlen(const char *);
int strcmp(const char*, const char*);
char *strcat(char*, const char*);
}
// 可对"头文件"进行链接指示:该头文件中所有普通函数声明, 将被认为是由链接指示的语言所编写的.
extern "C" {
#include <string.h>
}
```
> [!info] C 和 C++ 程序在编译时==**对符号名(例如函数名、变量名)的修饰规则**==不同
>
> 例如函数 `void spiff (int)` 在 C 编译器下生成的符号名可能为 `_spiff`,
> 而在 C++编译器下生成的符号名可能为 `_Z5spiffi`。
>
> 当**在 C++ 代码中引用了==预编译好的 C 库(动/静态库)中的外部函数==** 时,编译器在**编译 C++代码**时会对该函数按 **==C++名称修饰规则==** 进行生成 "**==外部符号==**",于是使得 **该函数 "在 C++文件中的符号名"** 与 "**预编译库中的符号名**" 不同,**导致链接器无法正确与 "C 库中的符号进行连接"**。
>
> [!tip] 令 "库文件" 兼容 C/C++符号修饰规则的技巧
>
> 对于 "**库源文件**" **提供者** (提供**库的头文件和源代码文件**,而非预编译好的动静态库)而言,为**保证库对 C、C++程序的兼容性**,
> 通常会在**其库的头文件或源文件的声明**中采用如下 **==条件编译==** 技巧:(该技巧几乎在所有系统头文件中都有被使用。)
>
> ```cpp
> #ifdef __cplusplus
> extern "C" {
> #endif
>
> void *memset(void*, int, size_t);
>
> #ifdef __cplusplus
> }
> #endif
> ```
>
> - 若编译的是 C++代码,C++编译器会默认定义宏 `__cplusplus`,因此上述条件编译在**预处理阶段会引入 `extern "C"` 的声明**。
> - 若编译的是 C 代码,则条件编译不会生效,直接声明。
>
>
>
> [!NOTE] C/C++函数在 C/C++程序中的使用
>
> ```cpp
> // 在 C++ 中, 引入一个 C 库中的函数;
> extern "C" void spiff(int);
>
> // 在 C++中定义一个可被 C 程序调用的函数. 编译器将为该函数生成适用于指定语言的代码.
> extern "C" double calc(double dpram) {
> // ...
> }
> ```
>
>
> [!caution] "链接指示" 对函数声明中作为 "返回类型或形参" 的函数指针也有效
>
> ```cpp
> // C函数f1, 其接收一个`void(*)(int)`类型的 "C 函数指针".
> extern "C" void f1(void(*)(int));
> extern "C" void spiff(int);
>
> extern "C" typedef void(*FC)(int); // 为 C 函数指针声明别名
> FC = spiff;
>
> // 调用 f1 时, 必须传入一个 C 函数名字或者 C 函数指针, 而不能是C++函数或C++函数指针
> f1(spiff);
> f1(FC);
> ```
>
>
<br>
## 模块链接性
略。
<br><br>
# Buffer
## 闪念
> sudden idea
## 候选资料
> Read it later
# ♾️参考资料
# Footnotes
[^1]: [Storage class specifiers - cppreference.com](https://en.cppreference.com/w/cpp/language/storage_duration)
[^2]: [Storage class specifiers - cppreference.com](https://en.cppreference.com/w/cpp/language/storage_duration#Linkage)
[^3]: 《程序员的自我修养:链接、装载与库》
[^4]: [cpp/language/language linkage) - cppreference.com](https://en.cppreference.com/w/cpp/language/language_linkage))
[^5]: [Storage class specifiers - cppreference.com](https://en.cppreference.com/w/cpp/language/storage_duration#Explanation)
[^6]: 《C++ Primer》P758