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编译指令,潜在作用域为从声明点到该声明区末尾。这相当于链接性为内部的静态变量附属品。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现