[C++核心编程-01]----C++内存四区详细解析

目录

前言        

正文

01-内存区域简介    

02-全局区    

03-栈区    

04-堆区    

05-new操作符    

总结


前言        

        当程序运行时,操作系统会为程序分配一块内存空间,这块内存空间被划分为不同的区域,每个区域有其独特的作用和管理方式。四个区域分别为:堆、栈、全局/静态存储区和常量存储区。每个区域都有不同的作用和特点,下面分别进行详细介绍。

正文

01-内存区域简介    

        当程序运行时,操作系统会为程序分配一块内存空间,这块内存空间被划分为不同的区域,每个区域有其独特的作用和管理方式。下面详细说明一下四个内存区域:

        堆(Heap)

        a、堆是由程序员进行手动管理的内存区域,可以动态分配和释放内存。

        b、在堆上分配内存使用 new 关键字,释放内存使用 delete 关键字。

        c、堆上的内存分配由程序员控制,可以动态调整大小,但需要确保正确释放分配的内存,否则会出现内存泄漏问题。堆上的内存分配速度较慢,不适合频繁分配小块内存。

        栈(Stack)

        a、栈是由系统自动管理的内存区域,用于存储函数调用信息和局部变量。

        b、每次函数调用时,系统会为函数分配一块栈帧,用于存储该函数的局部变量和返回地址等信息。

        c、栈上的内存是有限的,栈的大小在程序启动时就已经确定,递归调用过多或者分配过大的局部变量可能会导致栈溢出。

        全局/静态存储区(Global/Static Storage Area)

        a、全局变量和静态变量存储在这个区域中,其生命周期从程序开始到程序结束。

        b、全局变量存储在全局初始化数据段中,静态变量存储在全局未初始化数据段中。

        c、全局变量可以在任何地方访问,但应该避免过度使用全局变量,因为全局变量容易导致程序的耦合度增加。

        常量存储区(Constant Storage Area)

        a、常量数据(如字符串常量)存储在常量存储区,是只读的,程序不能修改这些数据。

        b、常量存储区通常位于全局/静态存储区的静态内存中,也可能位于代码段中,取决于编译器和操作系统的实现。

02-全局区    

        全局区(Global/Static Storage Area)是C++程序中存储全局变量和静态变量的内存区域。在程序运行期间,全局区中的变量始终存在,其生命周期从程序开始到程序结束。全局区分为两部分:全局初始化数据段和全局未初始化数据段。

        全局初始化数据段:存储已经初始化的全局变量和静态变量的数值,这些变量在编译时就被定义并初始化了。

        全局未初始化数据段:存储未初始化的全局变量和静态变量,这些变量会在程序运行时被初始化为0或者空值。

        下面给出一部分代码展示全局变量在全局区中的使用:在这个示例中,global_init_varstatic_global_init_var是全局初始化变量,它们的值在编译时已经确定;global_uninit_var是全局未初始化变量,它会在程序运行时被自动初始化为0。在main函数中,我们可以随时访问和修改这些全局变量的值。

#include <iostream>
using namespace std;// 全局变量,在全局初始化数据段
int global_init_var = 10;// 静态全局变量,在全局初始化数据段
static int static_global_init_var = 20;// 全局未初始化变量,在全局未初始化数据段,会被自动初始化为0
int global_uninit_var;int main() {// 在main函数中访问全局变量cout << "Global Initialized Variable: " << global_init_var << endl;cout << "Static Global Initialized Variable: " << static_global_init_var << endl;cout << "Global Uninitialized Variable: " << global_uninit_var << endl;// 修改全局变量的值global_init_var = 30;// 在main函数中再次访问全局变量cout << "Global Initialized Variable (after modification): " << global_init_var << endl;system("pause");return 0;
}

        下面给出具体代码,使用查看变量地址的方式查看是否属于同一内存区域:

        注:全局变量和静态变量(static关键字修饰)以及常量(包含全局常量和字符串常量)都在全局区,也就是地址在全局区。局部变量和const修饰的局部变量(也就是局部常量)在非全局区

        下面这个示例程序展示了C++中的全局区和非全局区的变量存储方式。在程序中分别定义了全局变量、静态变量、常量以及局部变量和const修饰的局部变量,然后输出它们的地址。

        a、全局变量g_ag_b、静态变量static int s_as_b、全局常量c_g_ac_g_b以及字符串常量"hello world"都在全局区,这些变量的地址是固定的,它们存储的地址是相对靠近的。

        b、普通局部变量ab、const修饰的局部变量c_l_ac_l_b在非全局区,它们的地址是在栈上动态分配的,每次函数调用结束后就会被释放。

        通过输出各个变量的地址,可以看到全局区和非全局区变量的存储方式及其地址的关系。

#include<iostream>
using namespace std;int g_a = 10;
int g_b = 10;const int c_g_a = 10;  //全局常量
const int c_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;//静态变量  在普通变量前加static 属于静态变量  它与全局变量地址比较相近static int s_a = 10;static int s_b = 10;cout << "静态变量s_a的地址为:" << (int)&s_a << endl;cout << "静态变量s_b的地址为:" << (int)&s_b << endl;// 常量// 字符串常量  "hello world"cout << "字符串常量hello world的地址为:" << (int)&"hello world" << endl;// const常量// const修饰的全局变量cout << "全局常量c_g_a的地址为:" << (int)&c_g_a << endl;cout << "全局常量c_g_b的地址为:" << (int)&c_g_b<< endl;// const修饰的局部变量const int c_l_a = 10;const int c_l_b = 10;cout << "局部常量c_l_a的地址为:" << (int)&c_l_a << endl;cout << "局部常量c_l_b的地址为:" << (int)&c_l_b << endl;system("pause");return 0;}

        示例运行结果如下图所示:

03-栈区    

        栈区(Stack)是由系统自动管理的内存区域,用于存储函数调用信息和局部变量。每次函数调用时,系统会为函数分配一块栈帧,用于存储该函数的局部变量、函数参数和返回地址等信息。栈的特点包括以下几个方面:

        a、栈上的内存是有限的,栈的大小在程序启动时就已经确定,通常较小。因此,过多的递归调用或者分配过大的局部变量可能会导致栈溢出(stack overflow)问题。

        b、栈上的内存分配和释放是按照后进先出(LIFO)的原则进行的,也就是说最后分配的内存最先释放,这使得栈区非常高效。

        c、在函数调用时,局部变量会占用栈上的一定空间,并在函数退出时自动释放,无需程序员手动管理。

        下面给出示例代码,展示栈区中局部变量的使用:在这个示例中,main函数中的局部变量x和test函数中的局部变量a都是在栈上分配的。每次函数调用时,都会为函数分配一块栈帧,在其中存储局部变量和其他函数调用信息。在递归调用中,每次函数调用都会在栈上分配新的局部变量,直到达到递归结束条件才会释放栈帧。通过这个示例可以更加直观地理解栈区的特点和局部变量的生命周期。

#include <iostream>
using namespace std;// 递归函数,调用自身多次
void test(int n) {int a = 10; // 在栈上分配局部变量aif (n > 0) {test(n - 1); // 递归调用}
}int main() {int x = 5; // 在栈上分配局部变量xtest(3); // 调用递归函数system("pause");return 0;
}

        下面给出具体代码示例,分析栈区的作用:

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

        在下面这个示例中,函数func中定义了一个局部变量a,并返回了该局部变量的地址。在C++中,局部变量的生命周期在函数执行结束后就会结束,所以局部变量a在函数func执行完毕后就会被销毁。然而,在main函数中,通过调用func函数并将返回的地址保存在指针p中,尝试输出指针p对应地址的值。

        由于局部变量a在函数func执行结束后被销毁,再次通过指针p访问已经销毁的局部变量的地址,会导致未定义行为,这可能导致程序输出的结果是不可预测的,也就是乱码。

        在这种情况下,程序虽然能够编译通过,但会在运行时出现未定义的行为和潜在的错误。因此,应该避免返回本地局部变量的地址或使用已销毁的内存,以确保程序运行的正确性和稳定性。

#include<iostream>
using namespace std;int * func()  // 形参数据也会放在栈区
{ int  a = 10;  //局部变量return &a;  // 返回局部变量的地址,这就相当于在一个自定义函数中定义了一些局部变量,// 并且带有参数,最后该函数是不能返回这个局部变量的,而且定义函数时,不能用void
}int main()
{int *p = func(); // // 之所以第一次可以打印正确数字,是因为编译器做了保留cout << *p << endl;  // 10  只写p返回的是地址,用*p使用了解引用,直接输出对应的数值,更加直观的观测出输出结果cout << *p << endl;  // 乱码system("pause");return 0;}

        示例运行结果如下图所示:

04-堆区    

        堆区(Heap)是用于动态分配内存的内存区域,由程序员手动管理。在堆区上分配的内存不会在函数退出时被释放,而是需要程序员手动释放。堆区的特点包括以下几点:

        a、堆区的内存大小不固定,程序可以根据需要动态地申请或释放内存。

        b、堆上的内存分配和释放不是按照后进先出的原则,而是由程序员决定何时向操作系统申请内存和什么时候释放内存。因此,需要谨慎处理内存泄漏和内存访问错误问题。

        c、对于堆上的内存,需要程序员手动调用new来申请内存并调用delete来释放内存。

        下面给出一个示例代码,展示堆区的使用过程:在这个示例中,通过new在堆区动态分配了一个整数的内存,并将该地址保存在指针p中,然后给整数赋值并输出。最后通过delete释放了p指向的整数内存。这个示例展示了堆区的动态内存分配和释放,以及程序员手动管理堆内存的过程。要注意在使用堆内存时,需要手动管理内存的分配和释放,避免内存泄漏和悬空指针问题。

#include <iostream>
using namespace std;int main() {// 动态分配内存int *p = new int; // 在堆区分配一个整数的内存if (p == nullptr) {cout << "内存分配失败" << endl;return 1;}*p = 42; // 给堆中的整数赋值cout << "动态分配的整数:" << *p << endl;// 释放内存delete p; // 释放p指向的整数内存system("pause");return 0;
}

        下面给出具体代码示例,分析堆区的作用:

        注:在堆区开辟数据。指针本质也是局部变量,放在栈上,指针保存的数据是放在了堆区。

        在下面这个示例中,func函数通过new关键字在堆区动态分配了一个整数的内存,并将该地址返回。在main函数中,通过调用func函数获取到堆区分配的整数内存的地址,并将其保存在指针p1中。然后通过解引用p1指针来输出堆区中存储的整数值。

        由于堆区中的内存是手动管理的,并且在使用new分配内存后,只有调用delete释放内存才能避免内存泄漏问题。在本例中,虽然没有手动释放分配的内存(即没有调用delete),但程序运行过程中只是输出数据,没有产生内存泄漏问题。

#include<iostream>
using namespace std;int *func()
{// 利用new关键字,可以将数据开辟到堆区int *p =  new int(10);return p;}int main()
{int *p1 = func();  // 在这里定义了一个p1指针,它对应于开辟的堆区的数据10的地址,然后下面输出的时候,使用解引用,便可得到数值cout << *p1 << endl;  // 无论输出多少次,都是可以正常输出数值cout << *p1 << endl;system("pause");return 0;}

         示例运行结果如下图所示:

05-new操作符    

        new操作符是C++中用于在堆区动态分配内存的操作符。它用于在堆区中分配一块指定大小的内存,并返回该内存的地址。new操作符的一般语法为:

T *ptr = new T;

        其中,T可以是任意数据类型,例如intdouble、自定义类等。通过new T,会在堆区中分配大小为T的内存,并返回一个指向该内存的指针ptr

        使用new操作符动态分配内存的好处在于,可以根据程序需要动态地分配内存,并在不需要时手动释放该内存,避免静态内存无法满足动态需求的问题。另外,使用new操作符在堆区中分配内存也可以避免函数调用结束时出现局部变量被销毁而无法再访问的问题。

        下面给出一个示例代码,展示new操作符的使用:在这个示例中,通过new操作符动态分配了一个整数内存和一个字符数组内存,并分别输出了整数和字符串。注意,当动态分配了数组内存时,需要使用delete[]来释放内存。

#include <iostream>
using namespace std;int main() {// 动态分配整数内存int *p = new int(42); // 通过new分配一个整数并赋值为42cout << "动态分配的整数:" << *p << endl;// 动态分配字符数组内存char *str = new char[10]; // 通过new分配一个长度为10的字符数组内存strcpy(str, "Hello");cout << "动态分配的字符串:" << str << endl;// 释放动态分配的内存delete p; // 释放整数内存delete[] str; // 释放字符数组内存system("pause");return 0;
}

        下面给出具体代码示例,分析new操作符的作用:

        在下面这个示例中,定义了一个函数func,通过new关键字在堆区动态分配了一个整数内存,并将该地址返回。在main函数中,通过调用func函数获取到堆区分配的整数内存的地址,并将其保存在指针p中。然后通过解引用p指针来输出堆区中存储的整数值。

        堆区中的内存需要程序员手动管理,即在使用new分配内存后必须使用delete来释放内存。在这个示例中,在输出完堆区中的数据后,使用delete释放了p指向的堆区内存。值得注意的是,一旦内存被释放,再次访问该内存会导致未定义行为,因此在释放内存后再次解引用p指针会产生问题,所以这行代码被注释掉了。

#include<iostream>
using namespace std;// new 的基本语法
int * func()
{// 在堆区创建整型数据// new int(10);这句代码会返回一个定义的int类型的10的地址,因此可以定义一个同类型的指针接收该地址int *p = new int(10);return p;
}int main()
{int *p = func();cout << *p << endl;cout << *p << endl;// 堆区开辟的数据只能使用delete关键字由程序员释放,delete p;
//	cout << *p << endl;  // 只能输出前两个system("pause");return 0;}

        示例运行结果如下图所示:

总结

        经过上述分析,总结如下:

        a、堆是由程序员分配和释放的内存区域,可以动态调整大小,使用new和delete管理内存。

        b、栈是由系统自动分配和释放的内存区域,用于存储函数的局部变量和函数调用信息。

        c、全局/静态存储区存储全局和静态变量,在程序运行过程中一直存在。

        d、常量存储区存储常量数据,是只读的,程序不能修改。

        正确理解和管理这四个内存区域对于编写高效、安全的C++程序至关重要。合理地利用堆、栈、全局/静态存储区和常量存储区,可以提高程序的性能并减少内存泄漏和溢出等问题的发生。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

赶紧收藏!2024 年最常见 100道 Java 基础面试题(四十二)

上一篇地址&#xff1a;赶紧收藏&#xff01;2024 年最常见 100道 Java 基础面试题&#xff08;四十一&#xff09;-CSDN博客 八十三、OSI的七层模型都有哪些&#xff1f; OSI&#xff08;Open Systems Interconnection&#xff09;参考模型是一个七层的网络通信模型&#xf…

python自定义x坐标名称

在画完图后加上 x[0.1,0.5,1.0,2.0,4.0,6.0,8.0] plt.xticks(x) import matplotlib.pyplot as pltx [1, 2, 3, 4, 5] y [2, 4, 6, 8, 10]plt.plot(x, y) plt.xticks(x, [A, B, C, D, E]) # 设置x轴坐标位置和标签 plt.show()要自定义x坐标名称&#xff0c;你可以使用matplo…

Unity图形图表XChart插件使用

最近做了一款数字孪生项目,其中涉及到了图形图表的应用,网上找了一下,找到了XChart插件,使用起来蛮方便的,不过还有待继续研究,很多细节性的知识点需要进行学习探索。以下是项目中的应用。 官方应用: ![](https://img-blog.csdnimg.cn/direct/ab9de8e84e7b4be4a50ea…

双指针算法(判断子序列,最长连续不重复子序列)

一.判断子序列 编程题 题目描述&#xff1a; 给定一个长度为 n 的整数序列 a1,a2,…,an 以及一个长度为 m 的整数序列 b1,b2,…,bm。 请你判断 a 序列是否为 b 序列的子序列。 子序列指序列的一部分项按原有次序排列而得的序列&#xff0c;例如序列 {a1,a3,a5} 是序列 {a1…

vs2019 cpp20 规范的线程头文件 <thread> 注释并探讨两个问题

&#xff08;1&#xff09;学习线程&#xff0c;与学习其它容器一样&#xff0c;要多读 STL 库的源码。很多知识就显然而然的明白了。也不用死记硬背一些结论。上面上传了一份注释了一下的 源码。主要是补充泛型推导与函数调用链。基于注释后的源码探讨几个知识点。 STL 库的多…

哈希(构造哈希函数)

哈希 哈希也可以叫散列 画一个哈希表 哈希冲突越多&#xff0c;哈希表效率越低。 闭散列开放定址法: 1.线性探测&#xff0c;依次往后去找下一个空位置。 2.二次探测&#xff0c;按2次方往后找空位置。 #pragma once #include<vector> #include<iostream> #i…

Linux重定向及缓冲区理解

重定向&#xff1a; 在上一期虚拟文件系统中讲到了每个进程在打开后&#xff0c;都会默认打开3个文件&#xff0c;如下&#xff1a; stdin 标准输入&#xff08;键盘&#xff09; 文件描述符&#xff1a;0 stdout 标准输出&#xff08;显示器&#xff09;文件描述符&a…

每日OJ题_贪心算法四⑤_力扣354. 俄罗斯套娃信封问题

目录 力扣354. 俄罗斯套娃信封问题 解析代码1_动态规划&#xff08;超时&#xff09; 解析代码2_重写排序贪心二分 力扣354. 俄罗斯套娃信封问题 354. 俄罗斯套娃信封问题 难度 困难 给你一个二维整数数组 envelopes &#xff0c;其中 envelopes[i] [wi, hi] &#xff0…

25计算机考研院校数据分析 | 中南大学

中南大学&#xff08;Central South University&#xff09;&#xff0c;位于湖南省长沙市&#xff0c;是中华人民共和国教育部直属的全国重点大学 &#xff0c;中央直管副部级建制&#xff0c;位列国家“双一流”、“985工程”、“211工程”&#xff0c;入选国家“2011计划”牵…

陪玩系统APP小程序H5音视频社交系统陪玩系统源码,陪玩app源码,陪玩源码搭建陪玩社交系统开发(现成,可定制)线下陪玩系统项目开发搭建

线下陪玩系统项目的设计 在需求分析完成后&#xff0c;接下来进行系统设计。系统设计主要包括以下几个部分&#xff1a; 1. 数据库设计&#xff1a;根据需求分析的结果&#xff0c;设计数据库结构&#xff0c;包括用户信息表、服务信息表、订单信息表等。 2. 界面设计&#…

阮怀俊参与五龙乡黄沙村村企联办“强村公司”

为走好海岛县高质量发展共同富裕特色之路&#xff0c;探索村级集体经济发展新路径、扶持新模式、运行新机制&#xff0c;嵊泗县五龙乡黄沙村股份经济合作社与杭州山舍乡建乡村产业发展有限责任公司联办成“强村公司”。 创始人阮怀俊表示&#xff0c;双方就融合乡域发展和文旅产…

训练时候查看gpu溢出情况

训练时候查看gpu溢出情况 pytorchtensorflow 训练时候查看gpu溢出情况 #深度学习/gpu相关代码 pytorch 我可以帮您了解如何在 GPU 溢出的时候给您提示。 使用 try-except 语句来捕获 CUDA out of memory 的异常&#xff0c;并在发生异常时打印提示信息。例如&#xff0c;您可…

MFC桌面应用中窗口的客户区与非客户区的

在MFC&#xff08;Microsoft Foundation Class&#xff09;中&#xff0c;窗口被分为客户区和非客户区。理解这两个概念对于设计和开发Windows应用程序至关重要。 客户区&#xff08;Client Area&#xff09;&#xff1a; 客户区是窗口中用于显示应用程序内容的区域。它是窗口…

PXE高效批量装机

一、PXE的概述 PXE是由Inter 公司开发的网络引导技术&#xff0c;工作在Client / Server 模式。允许客户机通过网络从远程服务器下载引导镜像&#xff0c;并加载安装文件或者整个操作系统。 1.1PXE优点 规模化&#xff1a;同时装配多台服务器 自动化&#xff1a;安装系统&am…

Flutter “Hello World“ 开发教程

Flutter “Hello World” 开发教程 简介 Flutter 是 Google 开发的一个开源移动应用 SDK&#xff0c;用于帮助开发者快速高效地构建跨平台的移动应用。它使用 Dart 语言&#xff0c;并且支持热重载功能&#xff0c;可以即时查看代码更改的效果。本教程将带你创建一个简单的 F…

Vue3知识总结-3

标题# Vue3知识总结-3 模版引用 需要直接访问底层的dom元素&#xff0c;要使用ref这个attribute,挂载结束后引用都会暴露在this.$refs上面 <template> <div ref"container" class"container">{{content}}</div><input type"t…

力扣10.正则表达式匹配

前言&#xff1a; 由于今天面试前端&#xff0c;面试官问对正则表达式的匹配理解吗&#xff1f; 当时脑袋发热&#xff0c;我说就是对字符串的替换。。。。 太抽象了&#xff0c;于是我面试结束后马上打开力扣&#xff0c;解了正则表达式的匹配算法题(四种语言)&#xff1b; 下…

03c++重载运算符

1、深入理解new和delete原理 #include<iostream> using namespace std;/* new 和 delete 1、malloc和new的区别 new 内存开辟构造函数 2、free和 delete的区别 delete 内存回收析构函数 开辟失败malloc返nullptr ,new抛出bad_alloc异常new->operator new delete -&…

TCL/TK GUI in Python 浅试笔记

TK GUI 学习 Tk 使用单线程、事件驱动的编程模型. 所有 GUI 代码、事件循环和 应用程序在同一线程中运行。因此&#xff0c;强烈建议不要进行任何阻止事件处理程序的调用或计算。 耗时操作的处理&#xff1a; 如果需要从另一个线程与运行 Tkinter 的线程进行通信&#xff0c…

【Java代码审计】敏感信息泄漏篇

【Java代码审计】敏感信息泄漏篇 敏感信息泄露概述 敏感信息泄露概述 敏感信息是业务系统中对保密性要求较高的数据&#xff0c;通常包括系统敏感信息以及应用敏感信息 系统敏感信息指的是业务系统本身的基础环境信息&#xff0c;例如系统信息、中间件版本、代码信息&#xff…