C++为在内存中存储数据提供了多种选择,可以选择数据保存在内存中的时间长度(存储持续性 storage duration)以及程序哪一部分可以访问数据(作用域 scope 和链接 linkage)
存储持续性
C++中有四种不同的方案来存储数据,这些方案的区别就在于数据保留在内存中的时间
- **自动存储持续性(Automatic Storage Duration):**在函数定义中声明的变量(包括函数参数)在存储持续性上是自动的。它们在程序开始执行其所属的函数或者代码块时被创建,在执行完函数或代码块时,他们使用的内存被释放。C++有两种存储持续性为自动的变量:自动变量和寄存器变量。
- 静态存储持续性(Static Storage Duration):在函数定义外定义的变量和使用关键字 static 定义的变量。它们在程序运行的运行的整个过程中都存在,C++有三种该变量。
- 线程存储持续性(C++11 Thread Storage Duration):多核处理器很常见,这些CPU能同时处理多个执行任务,这让程序能将计算放在可并行处理的不同线程中。如果变量用 thread_local 声明,那么其生命周期和所属的线程一样长。
- 动态存储持续性(Dynamic Storage Duration):用 new 运算符分配的内存将一直存在,直到使用 delete 运算符将其释放或程序结束为止,有时被称为 自由存储 (free store)或 堆 (heap)
存储描述 | 持续性 | 作用域 | 链接性 | 如何声明 |
---|---|---|---|---|
自动 | 自动 | 代码块 | 无 | 在代码块中 |
寄存器 | 自动 | 代码块 | 无 | 在代码块中,使用关键字 register |
静态,无链接性 | 静态 | 代码块 | 无 | 在代码块中,使用关键字 static |
静态,外部链接性 | 静态 | 文件 | 外部 | 不在任何函数内 |
静态,内部链接性 | 静态 | 文件 | 内部 | 不在任何函数内,使用关键字static |
作用域和链接
作用域描述了名称在文件(翻译单元)的多大范围内可见。
链接描述了名称如何在不同单元间共享。链接性为外部的名称可在文件间共享,链接性为内部的名称只能由一个文件中的函数共享,自动变量的名称没有链接性。
静态持续变量
静态变量的数目在程序运行期间是不变的,因此编译器将分配固定的内存快来存储所有的静态变量,这些变量在整个运行期间一直存在。
以下是静态持续变量三种链接性的用法。
int global = 100; // static duration, external linkage
static int one_file = 50; // static duration, internal linkage
int main() {}
void func() {
static in_count = 10; // static duration, no linkage
}
静态变量初始化
- 零初始化:未被初始化的静态变量的所有位都被置为0
- 常量表达式初始化:C++11中新增了constexpr关键字
- 动态初始化
其中零初始化和常量表达式初始化统称为静态初始化,这意味着编译器在处理翻译单元时初始化变量,而动态初始化变量将在编译之后初始化。
#include <cmath>
int a; // zero-initialization
int b = 5; // constant-expression initialization
int c.= 6 * 6; // constant-expression initialization
const double pi = 4.0 * atan(1.0); // dynamic initialization
静态初始化顺序:
- 1.所有静态变量被零初始化
- 2.如果使用常量表达式初始化,编译器将执行常量表达式初始化
- 3.在编译后进行动态初始化,如上述的变量 pi,因为要调用函数 atan,因此要等到该函数被链接且程序执行时才会初始化。
外部连接性
链接性为外部的变量通常简称外部变量,持续存储性为静态,作用域为整个文件,也成为全局变量。
单定义原则:每个外部变量都必须被声明,且变量只能由一次定义。
C++定义了两种变量声明:
- defining declaration: 定义,为变量分配空间
- referencing declaration:声明,引用已有的变量,不分配空间,引用声明使用关键字 extern
来看代码示例
#include <iostream>
using namespace std;
// external variable, warming defined
double warming = 0.3;
// function prototype
void update(double dt);
void local();
int main() {
// using global variable
cout << "Global warming is " << warming << " degrees.\n";
// call function to change warming
update(0.1);
cout << "Global warming is " << warming << " degrees.\n";
// call function with local warming
local();
cout << "Global warming is " << warming << " degrees.\n";
return 0;
}
#include <iostream>
// using warming from another file
extern double warming;
// function prototypes, optional
void update(double dt);
void local();
using std::cout;
// modified global variable
void update(double dt) {
// optional redeclaration
extern double warming;
// uses global warming
warming += dt;
cout << "support Updating global warming to " << warming << " degrees.\n";
}
// use local variable
void local() {
double warming = 0.8; // new variable hides external one
cout << "Local warming = " << warming << " degrees.\n";
// core Access global variable with the scope resolution operator
cout << "But global warming = " << ::warming << " degrees.\n";
}
运行结果
Global warming is 0.3 degrees.
support Updating global warming to 0.4 degrees.
Global warming is 0.4 degrees.
Local warming = 0.8 degrees.
But global warming = 0.4 degrees.
Global warming is 0.4 degrees.
从上述运行结果中我们可以看到
- C++中也存在就近原则,即函数中的局部变量会将引用的外部覆盖
- 可以使用作用于解析运算符
::
来访问被隐藏的外部变量
内部链接性
首先回顾外部变量的单定义规则
// file 1
int a = 10;
// file 2
int a = 10; // error
如果使用内部链接性的变量,就不会违反单定义规则
// file 1
int a = 1;
// file 2
static file a = 1; // ok
通过以下程序可以看出内部链接性的基本使用
#include <iostream>
// external variable definition
int tom = 3;
// external variable definition
int dick = 30;
// static, internal linkage
static int harry = 300;
// function prototype
void remote_access();
int main() {
using namespace std;
cout << "main() reports the following addresses:\n";
cout << &tom << " = &tom, " << &dick << " = &dick, " << &harry << " = &harry\n";
cout << "main() reports the following values:\n";
cout << tom << " = tom, " << dick << " = dick, " << harry << " = harry\n";
remote_access();
return 0;
}
#include <iostream>
// tom defined elsewhere
extern int tom;
// overrides external dick
static int dick = 10;
// external variable definition,
// no conflict with twofile1 harry
int harry = 200;
void remote_access() {
using namespace std;
cout << "remote_access() reports the following addresses:\n";
cout << &tom << " = &tom, " << &dick << " = &dick, " << &harry << " = &harry\n";
cout << "remote_access() reports the following values:\n";
cout << tom << " = tom, " << dick << " = dick, " << harry << " = harry\n";
}
输出结果
main() reports the following addresses:
0x106e910b8 = &tom, 0x106e910bc = &dick, 0x106e910c0 = &harry
main() reports the following values:
3 = tom, 30 = dick, 300 = harry
remote_access() reports the following addresses:
0x106e910b8 = &tom, 0x106e910c8 = &dick, 0x106e910c4 = &harry
remote_access() reports the following values:
3 = tom, 10 = dick, 200 = harry
无链接性
在代码块中使用 static 时,将导致局部变量的存储持续性为静态。这意味着虽然它只在该代码块中能用,但是它在该代码块不处于活动状态时仍然存在。因此在两次函数调用之间,警惕局部变量将保持不变。如果初始化来静态局部变量,那么程序只在启动时进行一次初始化。以后再调用函数,将不会像自动变量那样再次被初始化。
int sum(int num);
int main() {
for (int i = 1; i < 3; i++) {
std::cout << sum(i) << std::endl;
}
}
int sum(int num) {
static int total;
total += num;
return total;
}
output
1
3
上述代码很好地表明了代码中 static 变量的特性,静态变量 total 只在程序运行是通过零初始化被设置为0,以后在两次函数调用之间,其值保持不变,因此能够进行累计。
函数和链接性
C++不允许在一个函数中定义另一个函数,因此所有函数的存储持续性都是静态的,即在整个程序执行期间都一直存在。在默认的情况下,函数的链接性为外部的,但是可以使用 static 将函数的连接线置为内部的,使之只能在一个文件中使用。同时静态函数将覆盖外部定义的同名函数。
单定义规则也适用于非内联函数。即可以将内联函数放入头文件中。
Reference
- 《C++ Primer Plus》