C++程序在内存中的模型

进程(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 <iostream>
#include <unistd.h>
#include <string>
#include <cstdio>
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栈区范围内,说明存储命令行参数和环境变量的地址也是在栈区分配的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/1214.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

面试官想看我写一篇关于“原型链”和“构造函数”深入理解的文章

前言&#xff1a; 在参加工作的面试过程中&#xff0c;我搬出了我的个人掘金账号写在了简历里&#xff0c;面试官很感兴趣&#xff0c;他不仅关注了我的账号&#xff0c;还想让我写一篇《原型链》的见解&#xff0c;由于老早就想总结一篇关于原型的文章&#xff0c;奈何自己刚开…

07平衡负载:gRPC是如何进行负载均衡的?

负载均衡(Load Balance),其含义就是指将请求负载进行平衡、分摊到多个负载单元上进行运行,从而协同完成工作任务。 负载均衡的主要作用: 提升并发性能:负载均衡通过算法尽可能均匀的分配集群中各节点的工作量,以此提高集群的整体的吞吐量。 提供可伸缩性:可添加或减少服…

Springboot新手开发 Cloud篇

前言&#xff1a; &#x1f44f;作者简介&#xff1a;我是笑霸final&#xff0c;一名热爱技术的在校学生。 &#x1f4dd;个人主页&#xff1a;个人主页1 || 笑霸final的主页2 &#x1f4d5;系列专栏&#xff1a;后端专栏 &#x1f4e7;如果文章知识点有错误的地方&#xff0c;…

汇编语言与微机原理(1)基础知识

前言&#xff08;1&#xff09;本人使用的是王爽老师的汇编语言第四版和学校发的微机原理教材配合学习。&#xff08;2&#xff09;推荐视频教程通俗易懂的汇编语言&#xff08;王爽老师的书&#xff09;&#xff1b;贺老师C站账号网址&#xff1b;&#xff08;3&#xff09;文…

在visual studio 2022 C++中配置最新版OpenCV和可能错误解决方案

前面我们写了一篇博文有关在C#中配置OpenCV&#xff0c;但C#版本的OpenCV的学习资源相对较少&#xff0c;C版的和Python版的比较多。这里先说说C版的如何配置吧&#xff01;总共完成四步即可使用起来。 文章目录一、下载并安装OpenCV1、下载OpenCV2、安装OpenCV二、配置环境1、…

【python】喜欢XJJ?这不得来一波大采集?

前言 大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 俗话说的好&#xff1a;技能学了~就要用在自己喜欢得东西上&#xff01;&#xff01; 这我不得听个话~我喜欢小姐姐&#xff0c;跳舞的小姐姐 这不得用python把小姐姐舞采集下来~嘿嘿嘿 完整源码、素材皆可点击文章下方名片…

条款20:当std::shared_ptr可能悬空时使用std::weak_ptr

自相矛盾的是&#xff0c;如果有一个像std::shared_ptr&#xff08;见条款19&#xff09;的但是不参与资源所有权共享的指针是很方便的。换句话说&#xff0c;是一个类似std::shared_ptr但不影响对象引用计数的指针。这种类型的智能指针必须要解决一个std::shared_ptr不存在的问…

Mysql 竟然还有这么多不为人知的查询优化技巧,还不看看?

前言 Mysql 我随手造200W条数据&#xff0c;给你们讲讲分页优化 MySql 索引失效、回表解析 今天再聊聊一些我想分享的查询优化相关点。 正文 准备模拟数据。 首先是一张 test_orde 表&#xff1a; CREATE TABLE test_order (id INT(11) NOT NULL AUTO_INCREMENT,p_sn VARCHA…

Spring事务和事务传播机制

目录 Spring中事务的实现 1、通过代码的方式手动实现事务 2、通过注解的方式实现声明式事务 2.1、Transactional作用范围 2.2、Transactional参数说明 2.3、注意事项 2.4、Transactional工作原理 事务隔离级别 1、事务特性 2、Spring中设置事务隔离级别 2.1、MySQL事…

Linux:函数指针做函数参数

#include <stdio.h> #include <stdlib.h> //创建带有函数指针做参数的函数框架api //调用者要先实现回调函数 //调用者再去调用函数框架 //所谓的回调是指 调用者去调用一个带有函数指针做参数的函数框架&#xff0c;函数框架反过来要调用调用者提供的回调函数 …

蓝桥杯冲击-02约数篇(必考)

文章目录 前言 一、约数是什么 二、三大模板 1、试除法求约数个数 2、求约数个数 3、求约数之和 三、真题演练 前言 约数和质数一样在蓝桥杯考试中是在数论中考察频率较高的一种&#xff0c;在省赛考察的时候往往就是模板题&#xff0c;难度大一点会结合其他知识点考察&#x…

全面剖析OpenAI发布的GPT-4比其他GPT模型强在哪里

最强的文本生成模型GPT-4一、什么是GPT-4二、GPT-4的能力三、和其他GPT模型比较3.1、增加了图像模态的输入3.2、可操纵性更强3.3、复杂任务处理能力大幅提升3.4、幻觉、安全等局限性的改善3.6、风险和缓解措施改善更多安全特性3.7、可预测的扩展四、与之前 GPT 系列模型比较五、…

QT入门Item Views之QListView

目录 一、QListView界面相关 1、布局介绍 二、代码展示 1、创建模型&#xff0c;导入模型 2、 设置隔行背景色 3、删除选中行 三、源码下载 此文为作者原创&#xff0c;创作不易&#xff0c;转载请标明出处&#xff01; 一、QListView界面相关 1、布局介绍 先看下界面…

高完整性系统工程(三): Logic Intro Formal Specification

目录 1. Propositions 命题 2.1 Propositional Connectives 命题连接词 2.2 Variables 变量 2.3 Sets 2.3.1 Set Operations 2.4 Predicates 2.5 Quantification 量化 2.6 Relations 2.6.1 What Is A Relation? 2.6.2 Relations as Sets 2.6.3 Binary Relations as…

ZYNQ硬件调试-------day2

ZYNQ硬件调试-------day2 1.ILA&#xff08;Integrated Logic Analyzer &#xff09; 监控逻辑内部信号和端口信号;可以理解为输出。可单独使用 2.VIO&#xff08;Virtual Input/Output &#xff09; 实时监控和驱动逻辑内部信号和端口信号&#xff0c;可以理解为触发输入。不可…

第十四届蓝桥杯三月真题刷题训练——第 14 天

目录 第 1 题&#xff1a;组队 题目描述 运行限制 代码&#xff1a; 第 2 题&#xff1a;不同子串 题目描述 运行限制 代码&#xff1a; 思路&#xff1a; 第 3 题&#xff1a;等差数列 题目描述 输入描述 输出描述 输入输出样例 运行限制 代码&#xff1a; 思…

Dubbo原理简介

Dubbo缺省协议采用单一长连接和NIO异步通讯&#xff0c;适合于小数据量大并发的服务调用&#xff0c;以及服务消费者机器数远大于服务提供者机器数的情况。 作为RPC&#xff1a;支持各种传输协议&#xff0c;如dubbo,hession,json,fastjson&#xff0c;底层采用mina,netty长连接…

nginx详解(概念、Linux安装、配置、应用)

1.nginx是什么 百度百科 看百度百科的解释&#xff0c;第一句话就是错的。“Nginx (engine x) 是一个高性能的HTTP和反向代理web服务器”&#xff0c;从语法来看&#xff0c;去掉形容词就是&#xff1a;Nginx是服务器&#xff0c;nginx怎么会是服务器呢&#xff0c;nginx只是一…

Matlab进阶绘图第8期—聚类/分类散点图

聚类/分类散点图是一种特殊的特征渲染散点图。 聚类/分类散点图通过一定的聚类、分类方法&#xff0c;将特征相近的离散点划分到同一个类别中&#xff0c;进而将每个离散点赋予类别标签&#xff0c;并利用不同的颜色对不同的类别进行区分。 本文使用Matlab自带的gscatter函数…

C语言变量和数据类型的使用

文章目录前言一、将变量输出打印到控制台1.整形变量的输出2.浮点型变量的输出1.flaot的输出2.doble的输出3.float和double输出的区别4.%f,%10.2f......二、数据类型的大小总结前言 上一篇文章我们学习了C语言变量和数据类型的基本概念那么今天我们就具体的来看看如何在代码中使…