【C++基础】内存分区模型

内存分区模型

C++程序在执行时,将内存大致划分为 4个区域

  • 代码区:存放函数体的二进制代码,由操作系统进行管理
  • 全局区:存放 全局变量静态变量以及 常量
  • 栈区:由 编译器自动分配释放,存放函数的 参数值局部变量
  • 堆区:由 程序员分配和释放,若程序员不释放,程序结束时由操作系统回收

代码区:所有的代码的英文字母、注释

内存四区意义:

不同区域存放的数据,赋予不同的生命周期,给我们更大的灵活编程

栈区数据由编译器管理,堆区由程序员管理

程序运行前,内存划分了两个区域是 代码区全局区

程序运行后,内存中才会有 栈区堆区

程序运行前

在程序编译后,生成了后缀名为exe的可执行程序, 未执行可执行程序前分为两个区域

代码区

  • 存放CPU执行的机器指令(就是所写的代码)
  • 代码区是 共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可,不造成资源浪费

    所写的程序只要生成成功,就只有一份代码,不会因为每次要执行可执行程序,就再生成一份代码

  • 代码区是 只读的,使其只读的原因是防止程序意外地修改了它的指令

全局区

  • 全局变量静态变量存放在此
  • 全局区还包含了 常量区字符串常量全局常量(const修饰的全局变量)也存放在此
  • 该区域的数据在程序结束后由操作系统释放

全局区

全局变量:没有写在函数体中的变量

全局变量与普通局部变量的地址比较:

// 全局变量
int g_a = 10;
int g_b = 10;

int main() {
    // 局部变量
    int a = 10;
    int b = 10;

    // 地址比较
    cout << "局部变量 a 的地址:" << (int)&a << endl;
    cout << "局部变量 b 的地址:" << (int)&b << endl;

    cout << "全局变量 g_a 的地址:" << (int)&g_a << endl;
    cout << "全局变量 g_b 的地址:" << (int)&g_b << endl;
    /*
    局部变量 a 的地址为:11533284
    局部变量 b 的地址为:11533272
    全局变量 g_a 的地址为:1556532
    全局变量 g_b 的地址为:1556536
    局部变量和全局变量不在一个段里
    */

    return 0;
}

从输出内容可以看出,局部变量和全局变量没有放在一个段里

静态变量与全局变量、局部变量的地址比较:

// 全局变量
int g_a = 10;
int g_b = 10;

int main() {
    // 局部变量
    int a = 10;
    int b = 10;

    // 静态变量
    static int s_a = 10;
    static int s_b = 10;

    // 地址比较
    cout << "局部变量 a 的地址:" << (int)&a << endl;
    cout << "局部变量 b 的地址:" << (int)&b << endl;

    cout << "全局变量 g_a 的地址:" << (int)&g_a << endl;
    cout << "全局变量 g_b 的地址:" << (int)&g_b << endl;

    cout << "静态变量 s_a 的地址:" << (int)&s_a << endl;
    cout << "静态变量 s_b 的地址:" << (int)&s_b << endl;
    /*
    局部变量 a 的地址为:10287852
    局部变量 b 的地址为:10287840
    全局变量 g_a 的地址为:3784756
    全局变量 g_b 的地址为:3784760
    静态变量 s_a 的地址为:3784764
    静态变量 s_b 的地址为:3784768
    全局变量和静态变量放在一块的,放在同一个区域段中
    */

    return 0;
}

从输出可以看出来, 全局变量静态变量是放在一块的,属于同一个区段里,即都放在全局区中。

常量分为字符串常量、const修饰的两种变量(const修饰的全局变量:全局常量、const修饰的局部变量:局部常量)

但是保存在全局区的只有 字符串常量全局常量(const修饰的全局变量)

字符串常量与局部变量、全局变量、静态变量的地址比较:

// 全局变量
int g_a = 10;
int g_b = 10;

int main() {
    // 局部变量
    int a = 10;
    int b = 10;

    // 静态变量
    static int s_a = 10;
    static int s_b = 10;

    // 地址比较
    cout << "局部变量 a 的地址:" << (int)&a << endl;
    cout << "局部变量 b 的地址:" << (int)&b << endl;

    cout << "全局变量 g_a 的地址:" << (int)&g_a << endl;
    cout << "全局变量 g_b 的地址:" << (int)&g_b << endl;

    cout << "静态变量 s_a 的地址:" << (int)&s_a << endl;
    cout << "静态变量 s_b 的地址:" << (int)&s_b << endl;

    cout << "字符串常量的地址:" << (int)&("hello world") << endl;

    /*
    局部变量 a 的地址为:8124700
    局部变量 b 的地址为:8124688
    全局变量 g_a 的地址为:9158708
    全局变量 g_b 的地址为:9158712
    静态变量 s_a 的地址为:9158716
    静态变量 s_b 的地址为:9158720
    字符串常量的地址为:9149284
    常量区和全局变量、静态变量离得很近,全局变量和静态变量离得最近,常量区离他俩稍微有点距离
    */

    return 0;
}

可以看出来,常量区的字符串常量和全局变量、静态变量都离得很近,同属于全局区,但是要细看的话,常量区和全局变量、静态变量稍微有点距离

const修饰的变量有两种

  • const修饰的全局变量,称为 全局常量
  • const修饰的局部变量,称为 *局部常量

放在全局区的是 全局常量

全局常量与局部变量、全局变量、静态变量、字符串常量的地址比较:

// 全局变量
int g_a = 10;
int g_b = 10;

// 全局常量:const修饰的全局变量
const int c_g_a = 10;
const int c_g_b = 10;

int main() {
    // 局部变量
    int a = 10;
    int b = 10;

    // 静态变量
    static int s_a = 10;
    static int s_b = 10;

    // 地址比较
    cout << "局部变量 a 的地址:" << (int)&a << endl;
    cout << "局部变量 b 的地址:" << (int)&b << endl;

    cout << "全局变量 g_a 的地址:" << (int)&g_a << endl;
    cout << "全局变量 g_b 的地址:" << (int)&g_b << endl;

    cout << "静态变量 s_a 的地址:" << (int)&s_a << endl;
    cout << "静态变量 s_b 的地址:" << (int)&s_b << endl;

    cout << "字符串常量的地址:" << (int)&("hello world") << endl;

    cout << "全局常量 c_g_a 的地址:" << (int)&c_g_a << endl;
    cout << "全局常量 c_g_b 的地址:" << (int)&c_g_b << endl;

    /*
    局部变量 a 的地址为:6224956
    局部变量 b 的地址为:6224944
    全局变量 g_a 的地址为:4833332
    全局变量 g_b 的地址为:4833336
    静态变量 s_a 的地址为:4833340
    静态变量 s_b 的地址为:4833344
    字符串常量的地址为:4823908
    全局常量 c_g_a 的地址为:4824720
    全局常量 c_g_b 的地址为:4824724
    字符串常量和全局常量都在常量区中,所以都是482开头
    全局变量和静态变量都是483开头,与常量区离得都不远
    */

    return 0;
}

可以看出来,全局常量、字符串常量都放在全局区里很近的位置,全局变量、静态变量也放在全局区,只是与全局常量、字符串常量还有一定距离,但是离得不远

局部常量与局部变量、全局变量、静态变量、字符串常量的地址比较:

// 全局变量
int g_a = 10;
int g_b = 10;

// 全局常量:const修饰的全局变量
const int c_g_a = 10;
const int c_g_b = 10;

int main() {
    // 局部变量
    int a = 10;
    int b = 10;

    // 静态变量
    static int s_a = 10;
    static int s_b = 10;

    // 局部常量:const修饰的局部变量
    const int c_l_a = 10;
    const int c_l_b = 10;

    // 地址比较
    cout << "局部变量 a 的地址:" << (int)&a << endl;
    cout << "局部变量 b 的地址:" << (int)&b << endl;

    cout << "全局变量 g_a 的地址:" << (int)&g_a << endl;
    cout << "全局变量 g_b 的地址:" << (int)&g_b << endl;

    cout << "静态变量 s_a 的地址:" << (int)&s_a << endl;
    cout << "静态变量 s_b 的地址:" << (int)&s_b << endl;

    cout << "字符串常量的地址:" << (int)&("hello world") << endl;

    cout << "全局常量 c_g_a 的地址:" << (int)&c_g_a << endl;
    cout << "全局常量 c_g_b 的地址:" << (int)&c_g_b << endl;

    cout << "局部常量 c_l_a 的地址:" << (int)&c_l_a << endl;
    cout << "局部常量 c_l_b 的地址:" << (int)&c_l_b << endl;
    /*
    局部变量 a 的地址为:13629316
    局部变量 b 的地址为:13629304
    全局变量 g_a 的地址为:15581236
    全局变量 g_b 的地址为:15581240
    静态变量 s_a 的地址为:15581244
    静态变量 s_b 的地址为:15581248
    字符串常量的地址为:15571812
    全局常量 c_g_a 的地址为:15572624
    全局常量 c_g_b 的地址为:15572628
    局部常量 c_l_a 的地址为:13629292
    局部常量 c_l_b 的地址为:13629280
    有局部修饰的常量或变量,都不存放在全局区
    全局区存放:全局变量、全局常量、静态变量、字符串常量
    */

    return 0;
}

可以看到,局部常量并不存放在全局区,而是和局部变量存储在一块

综上,全局区里存储的数据: 全局变量静态变量全局常量字符串常量

不在全局区中的数据: 局部变量局部常量(const修饰的局部变量)

有局部两个字,就不会存放在全局区

全局区中的数据:全局变量、全局常量(const修饰的全局变量)、静态变量(static)、字符串常量

示意图:

【C++基础】内存分区模型
// 全局变量 --- 全局区
int g_a = 10;
int g_b = 10;

// 全局常量:const修饰的全局变量 --- 全局区
const int c_g_a = 10;
const int c_g_b = 10;

int main() {
    // 局部变量
    int a = 10;
    int b = 10;

    // 静态变量 --- 全局区
    static int s_a = 10;
    static int s_b = 10;

    // 局部常量:const修饰的局部变量
    const int c_l_a = 10;
    const int c_l_b = 10;

    return 0;
}

总结:

  • C++在程序运行前,分为 全局区代码区
  • 代码区的特点是 共享只读
  • 全局区中存放 全局变量静态变量常量(全局常量、字符串常量)
  • 常量区中存放 const 修饰的 全局常量 和 *字符串常量

程序运行后

栈区

栈区:由 编译器自动分配释放,存放 函数的参数值局部变量等。

注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放。

局部变量和形参都放在栈区

  • 对于形参,在调用函数时,形参会在栈区开辟数据空间
  • 对于局部变量,存放在栈区,栈区的数据在函数执行完后自动释放

定义在函数里的局部变量,在函数执行完后,会被编译器自动释放。如果我们在这个函数中返回这个局部变量的地址,那么编译器会保留一次这个地址的值,但是由于函数执行结束后,局部变量被释放,当第二次访问这个地址时,则无法访问到原本定义的值。

int * func() {
    int a = 10;
    return &a;
}

int main() {
    int *p = func();

    cout << *p << endl;
    cout << *p << endl;
    /*
    10
    2034547680 这是一个乱码
    */

    return 0;
}

从输出结果可以看出,第一次对返回的指针进行解引用,还可以访问到原本的数据,但是第二次就不行了。这是因为编译器对这个数据进行了一次保留,但是由于在func函数执行完后,局部变量a已经被编译器释放,栈上的数据就清空了,再次访问这个地址只会是乱码。

所以,不要返回局部变量的地址

堆区

堆区:由 程序员分配释放,若程序员不释放,程序结束时由操作系统回收

在C++中,程序员主要利用关键字 new在堆区开辟内存

这里的数据不会一直放在内存上,在程序运行期间,如果程序员不释放,则这个数据不会被释放,当程序结束时,会由操作系统回收

程序员自己开辟的内存,是由程序员自己管理的,存放在了堆区,所以函数执行完,会把栈区清空,但是不会影响到堆区。

语法: new &#x6570;&#x636E;&#x7C7B;&#x578B;(&#x503C;);

返回值:这段内存的地址

用同类型的指针去接收这个返回值。

int * func() {
    int a = 10;  // a是局部变量,开辟在栈区,函数执行完后,被编译器释放
    // 利用关键字new,将数据开辟到堆区
    // new int(10);
    int * p1 = new int(10);  // 用指针去接收这个返回值

    return p1;
}

int main() {
    int * p = func();
    cout << *p << endl;
    cout << *p << endl;
    cout << *p << endl;

    /*
    10
    10
    10
    */

    return 0;
}

此处需要注意:

在func中用于接收堆区内存地址的指针p1本质上也是一个局部变量,这个指针p1是开辟在栈区的,在func函数执行完后,它也会被释放。

所以我们在调用func函数时,已经在main函数中开辟了一个新的指针p来保存这个返回值,也就是保存了堆区的地址,所以即使func函数执行完,p1被释放了,但是堆区的地址我们已经获取到了,而且函数执行完时,堆区地址是不会释放的,所以我们可以多次访问这个堆区地址,并且每次输出都是一样的值。

若func返回的是局部变量的地址,这个指针所指向的内存是栈区,函数执行完,栈区清空后,这个地址的内存被释放了,所以即使我们在main函数中获取了这个地址也没用,编译器只会保留一次数据,第二次访问时就找不到这个数据了,已经被清空了。

new操作符

C++中利用new操作符在 堆区开辟数据,利用delete操作符在堆区释放数据

若开辟一个变量,语法: new &#x6570;&#x636E;&#x7C7B;&#x578B;()

若开辟一个数组,语法: new &#x6570;&#x636E;&#x7C7B;&#x578B;[&#x6570;&#x7EC4;&#x957F;&#x5EA6;]

利用new创建的数据,会返回该数据对应类型的指针,也就是存放在堆区的地址

堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符 delete

释放单个变量,语法: delete &#x53D8;&#x91CF;&#x5730;&#x5740;

释放数组,语法: delete[] &#x6570;&#x7EC4;&#x540D;

int * func() {
    // 在堆区创建整型数据
    // new返回的是该数据类型的指针,也是这块内存的地址
    int * p = new int(10);
    return p;
}

void test01() {
    int * p = func();
    cout << *p << endl;  // 程序员释放前
    delete p;  // 释放内存
    // cout << *p << endl;  // 程序员释放后
    // 内存已被释放,再次访问就是非法操作,会报错
}

// 在堆区利用new开辟数组
void test02() {
    // 在堆区,创建10个整型数据的数组
    int * arr = new int[10];  // 10代表数组有10个元素
    for (int i = 0; i < 10; i ++) {
        arr[i] = i;
    }

    for (int i = 0; i < 10; i ++) {
        cout << arr[i] << endl;
    }

    delete[] arr;
}

int main() {
    test01();
    test02();

    return 0;
}

12行代码报错如下:

【C++基础】内存分区模型

属于是非法访问。

Original: https://www.cnblogs.com/seansheep/p/15968996.html
Author: 在青青草原上抓羊
Title: 【C++基础】内存分区模型

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/608959/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Redis (error) NOAUTH Authentication required.

    首先查看redis设置密码没 表示没有设置密码,设置redis密码 这个时候查看密码是会报错的。 需要noauth身份验证。 修改密码 Original: https://www….

    Linux 2023年5月28日
    092
  • redis

    PHP-redis:http://pecl.php.net/package/redis PHP-redis中文文档(redis各种方法介绍):http://www.cnblogs….

    Linux 2023年5月28日
    076
  • 初识MySQL数据库

    一 、引言 假设现在你已经是某大型互联网公司的高级程序员,让你写一个火车票购票系统,来hold住双十一期间全国的购票需求,你怎么写? 由于在同一时段抢票的人数太多,所以你的程序不可…

    Linux 2023年6月14日
    0111
  • 防火墙NAT配置与DHCP下发

    该实验如果有做的不足的地方请见谅 实验目标: 按要求划分区域,公司内部办公区为trust,服务器区为dmz,外部网络为untrust。 PC1和PC2为公司内部办公区,需要从防火墙…

    Linux 2023年6月7日
    098
  • 记一次从源码泄露到getshell(一)

    0x00 前言 此次渗透中的所有修改已经复原,且漏洞已经提交至cnvd平台 0x01 源码泄露 在一个月黑风高的夜晚,闲来无事的我又开着脚本利用hunter进行互联网站点源码的扫描…

    Linux 2023年5月28日
    0103
  • jenkins使用shell脚本执行nohup java -jar包失败

    一、问题 通过jenkins执行shell脚本时,脚本中是通过nohup java -jar &的方式启动,显示执行成功,但是服务却没启动,脚本如下: #! /bin/ba…

    Linux 2023年5月28日
    0164
  • Xshell的快捷键【转】

    删除ctrl + d 删除光标所在位置上的字符相当于VIM里x或者dlctrl + h 删除光标所在位置前的字符相当于VIM里hx或者dhctrl + k 删除光标后面所有字符相当…

    Linux 2023年5月28日
    085
  • Hadoop伪分布式的搭建

    1.准备Linux环境1.1 开启网络,ifconfig指令查看ip 1.2 修改主机名为自己名字(hadoop) 1.3修改主机名和IP的映射关系 1.4关闭防火墙 1.5重启L…

    Linux 2023年5月27日
    063
  • 【C++基础】变量、常量、关键字、标识符命名

    四个步骤: 创建项目 创建文件 编写代码 运行程序 写代码前的框架: #include using namespace std; int main() { system(&quot…

    Linux 2023年6月13日
    087
  • 搭建Nginx七层反向代理

    基于https://www.cnblogs.com/Dfengshuo/p/11911406.html这个基础上,在来补充下七层代理的配置方式。简单理解下四层和七层协议负载的区别吧…

    Linux 2023年6月8日
    0107
  • 爱前端公开课学习笔记——JS02 字符串类型,布尔类型

    字符串是用引号包裹的,表示语言文字。 用双引号包裹的都是字符串 console.log(typeof 5); // number console.log(typeof "…

    Linux 2023年6月14日
    061
  • docker-compose安装redis-sentinel集群(1主+2副+2哨兵)

    前提:本试验环境已经提前安装了docker和docker-compose 说明:本次部署是单机伪集群,想要部署真正的集群,需要将秒个主件拆分到各个机器上去部署,只修改ip地址 1、…

    Linux 2023年5月28日
    083
  • 001.AD域控简介及使用

    一 AD概述 域(Domain)是Windows网络中独立运行的单位,域之间相互访问则需要建立信任关系。 当一个域与其他域建立了信任关系后,2个域之间不但可以按需要相互进行管理,还…

    Linux 2023年6月7日
    0119
  • [转帖]shell 学习之正则、别名以及管道重定向

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    Linux 2023年5月28日
    085
  • .Net MVC实现角色-API权限验证的一种方式

    阅文时长 | 1.15分钟字数统计 | 1844.8字符主要内容 | 1、引言&背景 2、部分设计分享 3、声明与参考资料『.Net MVC实现角色-API权限验证的一种方…

    Linux 2023年6月13日
    088
  • 电脑中图标变白色教你怎么修复

    复制一下代码到文本文档中 另存为 .bat 然后点击好的配置文件右键以管理员身份运行 就会解决桌面变白的问题 @echo off taskkill /f /im explorer….

    Linux 2023年6月7日
    084
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球