C++程序在内存中的模型
创始人
2025-05-29 15:32:48
0

进程(Process)是计算机中的程序,数据集合在其上运行的一次活动,是系统进行资源分配的基本单位。

每个进程都有自己独立的虚拟内存地址空间,这个虚拟的内存地址空间一般是线性连续的,这个内存地址空间是虚拟的,不是真正的物理地址。

在linux操作系统中,内核通过页表的方式让虚拟地址与物理地址进行内存映射,从而获得真正的物理地址。

在32位操作系统进程最大可寻址是0xFFFFFFFF,即4G;在64位操作系统进程中理论上最大可寻址是0xFFFFFFFFFFFFFFFF。

那么,对于Linux 64位系统,理论上,64bit内存地址可用空间为:

0x0000000000000000 ~ 0xFFFFFFFFFFFFFFFF,实际上linux只用了其中一小部分(256T)。Linux 64位操作系统仅使用了低地址的47位,高地址的17位。所以,实际用到的地址空间:

0x0000000000000000 ~ 0x00007FFFFFFFFFFF为用户空间地址;

0xFFFF800000000000 ~ 0xFFFFFFFFFFFFFFFF为内核空间地址;

其余的都是为未使用区域。

基于以上认识,那么在linux 64位程序,x86_64构架中,一个C++程序在内存中的模型如下:

受保护区

受保护区,是用户不能直接访问的区域,否则会报段错误,在linux 64位,x86_64构架下,占用4M,即:0x0000000000000000 ~ 0x0000000000400000所对应的地址空间

int *p = NULL; //空指针,就是指向内存地址为0的指针

代码区(.text段)

.text段,代码区,我们编译生成的可执行文件二进制代码就存放在这个区,例如:全局函数,类的成员函数(包括静态成员函数,非静态成员函数)。读写权限:r-x

常量区(.rodata段)

.rodata段,read only data的缩写,只读数据段。存储内置数据类型的全局常量、全局静态常量、类的静态成员常量,"Hello World"这种字面常量等。注意:这里只存储内置数据类型,内置数据类型是有原子性的,比如:int,float,double等,const string name = "JIM";因为string是抽象数据类型,不是内置数据类型,是存放在.bss区的。读写权限:r--

注意:#define BUF_MAX 1024;enum DAY{FRI = 5}; 中的BUF_MAX和FRI是在其作用域内,在预编译程序阶段就将它们展开,代码中出现BUF_MAX的地方用1024替换,FRI出现的地方用5替换,所以BUF_MAX和FRI在程序中是没有地址空间的。

已初始化全局变量区(.data段)

.data段,数据段,存储已初始化的内置数据类型的全局变量、全局静态变量、类的静态成员变量。注意:这里只存储内置数据类型。已初始化的不是内置数据类型全局变量、全局静态变量、类的静态成员变量,存放在bss区的。读写权限:rw-。注意:函数体内定义的局部静态变量是在栈空间

未初始化全局变量区(.bss段)

.bss段,block started by symbol的缩写。未初始化的变量没有对应的值,C/C++强制未初始化的全局变量被赋以0为默认值。存储了未初始化的全局变量、全局静态变量以及上面提到过的不是内置数据类型的全局常量(例如:const string name = "JIM";)、已初始化的不是内置数据类型全局变量、全局静态变量、类的静态成员变量。读写权限:rw-

int g_uninit_i; //默认g_uninit_i = 0;
int *pg_uninit_i; //默认pg_uninit_i = (int*)0;

堆区(heap)

堆区,是在程序运行中使用的,由程序员控制这个堆区内存的分配和释放,如果没有释放,则在程序退出时,操作系统进行回收释放。C中用malloc在堆中开辟内存空间,用free释放内存空间;C++中用new在堆中开辟内存空间,用delete释放内存空间。堆区的地址是向上生在的。读写权限:rw-

共享库

libc.so,libstdc++.so等等程序中用到的共享库映射区

栈区(stack)

栈区,是由编译器控制内存的分配和释放,当调用函数时,传入函数的参数,函数执行时中创建的临时变量,以及函数有返回值,返回时,都会用到栈区,并且当函数调用结束时,系统调用该函数在栈区分配的临时空间,都将会被释放。栈区的地址是向下生在的。读写权限:rw-

命令行参数、环境变量

extern char **environ; //环境变量
int main(int argc, char *argv[]) //argv就是存放了命令行参数的指针数组
{}

下面,我们验证一下C++程序中的内存是不是按照上面所说的那么分配管理的。我们有一个test.cpp,内容如下。

#include 
#include 
#include 
#include 
using namespace std;extern char **environ; //环境变量#define BUF_MAX 1024 //宏,在程序预编译阶段编译器会将出现BUF_MAX的地方用1024替换掉
enum DAY
{FRI = 5 //FRI是枚举数值,在程序预编译阶段编译器会将出现FRI的地方用5替换掉
};void g_func_test() //全局函数
{static int local_static_i = 10;cout << "&local_static_i = " << &local_static_i << endl;cout << "test" << endl;
}const int gc_i = 100; //内置数据类型全局常量
static const int gsc_i = 50; //内置数据类型全局静态常量int g_i = 1; //已初始化内置数据类型全局变量
static int gs_i = 10; //已初始化内置数据类型全局静态变量int g_uninit_i; //未初始化的全局变量,默认初始化g_uninit_i = 0;
static int gs_uninit_i; //未初始化的全局静态变量,默认初始化gs_uninit_i = 0;
int *pg_uninit_i; //未初始化的全局指针变量,默认初始化为pg_uninit_i = NULL;
string g_name = "JIM"; //已初始化抽象数据类型全局变量
const string gc_hello = "Hello World"; //抽象数据类型全局常量
static const string gsc_hello = "Hello World"; //抽象数据类型全局静态常量class Test
{public:Test():m_num(0),mc_num(1){}void test() const{cout << "Hello World" << endl;printf("&\"Hello World\" = %p\n", &"Hello World");}const int mc_num; //内置数据类型常量,必须在构造函数中初始化static const int msc_num; //内置数据类型静态常量,必须类外初始化static const string msc_name; //抽象数据类型静态常量,必须类外初始化int m_num; //内置数据类型变量static int ms_num; //内置数据类型静态变量
};const int Test::msc_num = 2;
const string Test::msc_name = "JIM";
int Test::ms_num = 1;const Test gc_test; //抽象数据类型全局常量int main(int argc, char *argv[])
{cout << "---命令行参数,环境变量---" << endl;cout << "environ = " << environ << endl;  //环境变量cout << "argv = " << argv << endl; //命令行参数cout << endl; cout << "---------栈区--------" << endl;int temp1 = 1; //局部变量,存储在栈区int temp2 = 2; //局部变量,存储在栈区const int ctemp = 3; //局部常量,存储在栈区cout << "&temp1 = " << &temp1 << endl;cout << "&temp2 = " << &temp2 << endl;cout << "&ctemp = " << &ctemp << endl;cout << endl;cout << "---------堆区--------" << endl;int *p1 = new int(1); //堆中开辟空间int *p2 = new int(2); //堆中开辟空间cout << "p1 = " << p1 << endl;cout << "p2 = " << p2 << endl;delete p1;p1 = NULL;delete p2;p2 = NULL;cout << endl;cout << "-----.bss段------" << endl;cout << "&g_uninit_i = " << &g_uninit_i << endl; //未初始化的全局变量,存储在.bss段cout << "g_uninit_i = " << g_uninit_i << endl;cout << "&gs_uninit_i = " << &gs_uninit_i << endl; //未初始化的全局静态变量,存储在.bss段cout << "gs_uninit_i = " << gs_uninit_i << endl;cout << "&pg_uninit_i = " << &pg_uninit_i << endl; //未初始化的全局指针变量,存储在.bss段cout << "pg_uninit_i = " << pg_uninit_i << endl;//cout << "*pg_uninit_i = " << *pg_uninit_i << endl; //默认初始化为NULL,默认指向了内存地址为0,访问会出现断错误cout << "&g_name = " << &g_name << endl; //已初始化抽象数据类型全局变量,存储在.bss段cout << "&gc_hello = " << &gc_hello << endl; //抽象数据类型全局常量,存储在.bss段cout << "&gsc_hello = " << &gsc_hello << endl; //抽象数据类型全局静态常量,存储在.bss段cout << "&gc_test = " << &gc_test << endl; //抽象数据类型全局常量,存储在.bss段cout << "&Test::msc_name = " << &Test::msc_name << endl; //类的抽象数据类型静态常量,存储在.bss段//g_test.test();cout << endl;cout << "---------.data段--------" << endl;cout << "&g_i = " << &g_i << endl; //已初始化内置数据类型全局变量,存储在.data段cout << "&gs_i = " << &gs_i << endl; //已初始化内置数据类型全局静态变量,存储在.data段//cout << "&local_static_i = " << &local_static_i << endl; //函数体内定义的局部静态变量是在栈空间cout << "&Test::ms_num = " << &Test::ms_num << endl; //类的内置数据类型静态变量,存储在.data段cout << endl;cout << "-------.rodata段-------" << endl;cout << "&gc_i = " << &gc_i << endl; //内置数据类型全局常量,存储在.rodata段cout << "&gsc_i = " << &gsc_i << endl; //内置数据类型全局静态常量,存储在.rodata段//printf("&Test::mc_num = %p\n", &Test::mc_num); //类的成员常量,只有类被实例化后才存在的cout << "&Test::msc_num = " << &Test::msc_num << endl; //类的内置数据类型静态常量,存储在.rodata段printf("&\"Hello World\" = %p\n", &"Hello World"); //字面常量,存储在.rodata段cout << endl;cout << "-------.text段-----" << endl;printf("&g_func_test = %p\n", &g_func_test); //全局函数,在代码区printf("&Test::test = %p\n", &Test::test); //类的成员函数,在代码区printf("&printf = %p\n", &printf); //libc库中的函数,为什么打印的函数地址是在代码区?cout << endl;//g_func_test();//写成死循环,是为了让进程一直在运行,方便我们在bash中输入命令,查看进程的相关信息while(1) {sleep(1);}return 0;
}
g++ test.cpp -o test //生成64位可执行文件
./test //将生成的64位可执行文件跑起来

ps aux //找到test跑起来的进程id

cat /proc/7472/maps //查看进程映射

那么,我们结合代码,以及程序打印的结果,以及查看进程映射,即可进行分析

1.受保护区

地址范围:0x0000000000000000 - 0x0000000000400000

2.代码区(.text段)、常量区(.rodata)

地址范围:0x0000000000400000 - 0x0000000000402000

    cout << "-------.rodata段-------" << endl;cout << "&gc_i = " << &gc_i << endl; //内置数据类型全局常量,存储在.rodata段cout << "&gsc_i = " << &gsc_i << endl; //内置数据类型全局静态常量,存储在.rodata段//printf("&Test::mc_num = %p\n", &Test::mc_num); //类的成员常量,只有类被实例化后才存在的cout << "&Test::msc_num = " << &Test::msc_num << endl; //类的内置数据类型静态常量,存储在.rodata段printf("&\"Hello World\" = %p\n", &"Hello World"); //字面常量,存储在.rodata段cout << endl;cout << "-------.text段-----" << endl;printf("&g_func_test = %p\n", &g_func_test); //全局函数,在代码区printf("&Test::test = %p\n", &Test::test); //类的成员函数,在代码区printf("&printf = %p\n", &printf); //libc库中的函数,为什么打印的函数地址是在代码区?cout << endl;

可以验证:

.text段:存储全局函数,类的成员函数(但这里有一个问题,printf函数在libc.so共享库中,不应该在共享库虚拟地址段吗?为什么打印出的结果是在.text段?欢迎在评论区留言)

.rodata段:存储内置数据类型的全局常量、全局静态常量、类的静态常量

.text段往上是.rodata段,因为打印出的全局函数,类的成员函数的在内存地址隔了一定的地址空间,并且都比常量在内存地址小。

3.已初始化全局变量区(.data段)、未初始化全局变量区(.bss段)

地址范围:0x0000000000602000 - 0x0000000000603000

    cout << "-----.bss段------" << endl;cout << "&g_uninit_i = " << &g_uninit_i << endl;cout << "g_uninit_i = " << g_uninit_i << endl;cout << "&gs_uninit_i = " << &gs_uninit_i << endl;cout << "gs_uninit_i = " << gs_uninit_i << endl;cout << "&pg_uninit_i = " << &pg_uninit_i << endl;//cout << "*pg_uninit_i = " << *pg_uninit_i << endl; //默认初始化为NULL,默认指向了内存地址为0,访问会出现断错误cout << "&g_name = " << &g_name << endl;cout << "&gc_hello = " << &gc_hello << endl;cout << "&gsc_hello = " << &gsc_hello << endl;cout << "&gc_test = " << &gc_test << endl;cout << "&Test::msc_name = " << &Test::msc_name << endl;//g_test.test();cout << endl;cout << "---------.data段--------" << endl;cout << "&g_i = " << &g_i << endl;cout << "&gs_i = " << &gs_i << endl;//cout << "&local_static_i = " << &local_static_i << endl; //函数体内定义的局部静态变量是在栈空间cout << "&Test::ms_num = " << &Test::ms_num << endl;cout << endl;

.data段:存储已初始化内的置数据类型的全局变量、全局静态变量、类的静态成员变量

.bss段:存储未初始化的全局变量、全局静态变量,以及抽象数据类型的全局常量、全局静态常量、类的静态成员常量和已初始化的抽象数据类型的全局变量、全局静态变量、类的静态成员变量。

.data段往上是.bss段,因为打印出的已初始化的变量的内存地址隔了一定的地址空间,并且都比未初始化的变量的内存地址小。

4.堆区(heap)

地址范围:0x0000000001e1f000 - 0x0000000001e40000

    cout << "---------堆区--------" << endl;int *p1 = new int(1); //堆中开辟空间int *p2 = new int(2); //堆中开辟空间cout << "p1 = " << p1 << endl;cout << "p2 = " << p2 << endl;delete p1;p1 = NULL;delete p2;p2 = NULL;cout << endl;

p1和p2的内存地址范围都在0x0000000001e1f000 - 0x0000000001e40000堆区范围内,说明是在堆区开辟的空间

堆的内存地址是往上生长的,p2后new的内存地址比p1先new的内存地址大

5.栈区(stack)

地址范围:0x007ffd65b75000 - 0x007ffd65b96000

    cout << "---------栈区--------" << endl;int temp1 = 1; //局部变量,存储在栈区int temp2 = 2; //局部变量,存储在栈区const int ctemp = 3; //局部常量,存储在栈区cout << "&temp1 = " << &temp1 << endl;cout << "&temp2 = " << &temp2 << endl;cout << "&ctemp = " << &ctemp << endl;cout << endl;

temp1、temp2、ctemp这3个临时变量的地址范围都在0x007ffd65b75000 - 0x007ffd65b96000栈区范围内,说明是在栈区分配的空间。

temp1、temp2、ctemp这3连续在栈上开辟的临时变量的地址,依次减少,说明栈是向下生长的

6.命令行参数,环境变量

    cout << "---命令行参数,环境变量---" << endl;cout << "environ = " << environ << endl;  //环境变量cout << "argv = " << argv << endl; //命令行参数cout << endl; 

environ、argv的地址范围都在0x007ffd65b75000 - 0x007ffd65b96000栈区范围内,说明存储命令行参数和环境变量的地址也是在栈区分配的。

相关内容

热门资讯

富印新材:副总入职次年即获股权... 《金证研》南方资本中心 瑾攘/作者 南江 映蔚/风控2025年1-9月,安徽富印新材料股份有限公司(...
主动投案!原华融湘江银行行长涉... 出品|达摩财经1月8日,中央纪委国家监委网站发布消息显示,湖南省农村信用社联合社党委副书记、副理事长...
国家出手调查!美团、淘宝、京东... 欢迎关注我的好朋友:杠杆游戏!撰文|蜜妹这是@闺蜜财经的第1744篇原创图片来源|AI自动生成利剑出...
雷军,尝到了造车的苦 订阅 快刀财经 ▲ 做您的私人商学院造车的苦,雷军算是尝到了一种。作者:邱鑫浩来源:邱处机(ID:q...
这12本理财入门书,真的看得懂... 点击 “简七读财” ,发送消息“ 小狗钱钱 ”共读理财经典~晚上好,我是简七~最近很多朋友都在做新...
时隔一年,广发证券拟61亿港元... 本文来源:时代周报 作者:特约记者 井爽2026年开年,又见券商拟大手笔增资子公司。1月6日,广发证...
美股芯片股集体走高 1月9日,美股三大指数开盘集体上涨,道琼斯指数涨0.35%,标普500指数涨0.17%,纳斯达克综合...
尚界Z7概念图发布,引发网友激... 红星资本局1月9日消息,1月7日,尚界发布旗下首款轿跑尚界Z7的概念图,配文“比新一代,更期待”,剑...
美元基金组团收购中国创新药,海... 中国创新药对外授权交易在2025年创下超过1300亿美元总额战绩。2026年开年,新一轮的对外授权交...
光伏出口退税全面取消,企业拟加... 1月9日晚间,财政部、税务总局发布关于出口退税政策的公告,提到2026年4月1日起,将取消光伏等产品...