C++内存模型
C++内存模型
通常编译器使用三块独立的内存:一块用于静态变量(可能还会被细分)、一块用于自动变量、一块用于动态存储。
存储持续性
C++存储持续性有以下类别:
- 自动存储持续性:在函数定义中声明的变量(包括函数参数)。
- 静态存储持续性:在函数定义外定义的变量和使用关键字static定义的变量。
- 线程存储持续性(C++11):使用关键字thread_local定义的。生命周期与线程一样长。
- 动态存储持续性:使用new运算符分配的内存将一直存在,直到使用delete运算符将其释放或程序结束为止。有时被称为自由存储/堆。
内存分配位置
-
自动存储变量:分配到栈中,在程序执行其所属的函数/代码块时被创建,执行完函数/代码块时所使用的内存被释放。
-
静态持续变量:分配到内存中固定位置,在程序整个运行过程中都存在。
int global = 300; // static duration, external linkage static int one_file = 50; // static duration, internal linkage void funct1() { static int count = 0; // static duration, no linkage }静态持续变量首先会被初始化为0,若有显示初始化,然后再进行静态初始化(编译器根据文件按内容算出值进行初始化)或动态初始化(编译后在初始化,通常是调用了函数)。局部静态持续变量并非像自动存储变量那样,它只初始化一次。
-
动态持续变量:使用new,会在堆中找到一个足以满足要求的内存块,不过也可使用定位new特性指定位置.
#include <new> struct chaff { char dross[20]; int slag; } char buffer[50]; int main() { chaff *p = new (buffer) chaff; }
链接性
链接性为外部的变量称为外部变量,它们的存储持续性为静态。根据单定义规则,使用变量前需要声明,但只能定义一次。使用外部变量的文件中,必须先声明它,声明可分为两种:
- 引用声明/声明:不给变量分配存储空间,因为它引用已有的变量。使用extern关键字,且不进行初始化,否则为定义声明,导致分配存储空间。
- 定义声明/定义:他给变量分配存储空间。
如下示例,file01.cpp中全为定义,file02.cpp与file03.cpp全为声明。
// file01.cpp
extern int cats = 20;
int dogs = 22;
int fleas;
// file02.cpp
extern int cats;
extern int dogs;
// file03.cpp
extern int cats;
extern int dogs;
extern int fleas;
注意:const修饰的外部变量的链接性为内部,可再次使用extern关键字将其变为外部。
extern const int states = 50;
作用域
- 自动存储变量:声明处到代码块结束。
- 静态存储变量:开头为声明处,若为外部链接和內部链接则结尾是文件末尾,若为无链接则结尾是代码块结束。
注意作用域可能会被同名的”相对局部变量“隐藏。
// file01.cpp
int errors = 20;
// file02.cpp
static int errors = 25;
void f()
{
int errors = 30;
}
对于函数
函数的存储持续性都自动为静态,默认下链接性为外部,但可使用static关键字来修改为内部,使之只能作用于该文件。
static int private();
static int private()
{
...
}
内联函数不受单定义规则,意味着内联函数可放在头文件中被不同文件包含,各文件中均有定义,然而C++要求所有内联定义必须相同。
C++寻找函数的顺序:static函数在本身文件中寻找,否则先去所有程序文件中寻找,未找到去库文件中寻找。
修改语言链接性:
extern "C" void spiff(int);
extern void spoff(int);
extern "C++" void spaff(int);
名称空间
using声明和using编译是不一样的:使用了using声明时,就好像声明了相应的名称一样,如果某个名称已经在函数中声明了,则不能用using声明导入相同的名称。然而,使用using编译指令时,将进行名称解析,就像在包含using声明和名称空间本身最小的声明区域中声明了名称一样。
#include <iostream>
namespace Jill
{
double fetch;
struct Hill {};
}
double fetch;
int main()
{
using namespace Jill;
// fetch = 3.0; // ERROR, global fetch and Jill::fetch
double fetch;
std::cin >> fetch; // local fetch
std::cin >> ::fetch; // global fetch
std::cin >> Jill::fetch; // Jill::fetch
}
int foom()
{
// Hill top; // ERROR
Jill::Hill crest; // valid
}
如上述代码,名称空间为全局的,main中using编译指令,根据”名称空间本身最小的声明区域中声明“,Jill中的变量会在文件这个声明区域中声明。所以main中声明的fetch会隐藏在全局声明的fetch和Jill全局声明的fetch,不过可以使用相应的作用域解析运算符来使用变量。需要注意的是,虽然函数中的using编译指令将名称空间的名称视为在函数之外声明的,但它不会使得该文件中的其他函数都能使用这些名称。如foom中无法直接使用Hill来表示全局的Jill::Hill。
在默认情况下,在名称空间中声明的名称的链接性为外部的(除非它的引用了常量)。可使用匿名名称空间,此名称空间不能使用using声明和using编译指令,潜在作用域为从声明点到该声明区末尾。这相当于链接性为内部的静态变量附属品。

浙公网安备 33010602011771号