[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.hqwc.cn/news/681470.html

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

相关文章

SRM系统供应链库存协同提升企业服务水平

SRM系统供应链库存协同是一种以提高供应链整体效率和竞争力为目标的管理方法。它涉及到企业与供应商之间的紧密合作&#xff0c;以实现库存优化、成本降低、风险分担和灵活响应市场变化等目标。 一、SRM供应链库存协同的概念和特点 SRM供应链库存协同是指企业与供应商之间通过…

Core_Air724UG学习

产品描述 Core_Air724UG核心板是基于Air724UG cat1模板制作的开发实验板。 该模块支持Lua二次开发或AT指令&#xff0c;方便开发者根据自己的需求灵活选择。 Core_Air724UG核心板专注于小型化&#xff0c;PCB尺寸4246mm&#xff0c;有12x22哥标准2.54mm排针管脚&#xff0c;其…

一对一WebRTC视频通话系列(五)——综合调试和功能完善

本系列博客主要记录一对一WebRTC视频通话实现过程中的一些重点&#xff0c;代码全部进行了注释&#xff0c;便于理解WebRTC整体实现。 本专栏知识点是通过<零声教育>的音视频流媒体高级开发课程进行系统学习&#xff0c;梳理总结后写下文章&#xff0c;对音视频相关内容感…

Unity VR在编辑器下开启Quest3透视(PassThrough)功能

现在有个需求是PC端串流在某些特定时候需要开启透视。我研究了两天发现一些坑,记录一下方便查阅,也给没踩坑的朋友一些思路方案。 先说结论,如果要打PC端或者在Unity编辑器中开启,那么OpenXR当前是不行的可能还需要一个长期的过程,必须需要切换到Oculus。当然Unity官方指…

VisualGDB:Linux静态库项目创建、编译及库的使用

接上篇《VisualGDB&#xff1a;Linux动态库项目创建、编译及库的使用》&#xff0c;静态库的创建和使用与动态库基本无差别&#xff0c;唯一需要做的就是指定项目生成静态库。 一、指定项目生成静态库 二、重新构建和编译项目 这里注意&#xff0c;同样要copy一个libxxx.so格式…

AlphaFold 3来了!闭源但可供科研使用,DeepMind 子公司近水楼台先推进商用?

这里是引用 当地时间 5 月 8 日&#xff0c;Google DeepMind 联合其子公司 Isomorphic Labs 重磅发布 AlphaFold 3。 DeepMind 表示&#xff0c;AlphaFold 3 以前所未有的精确度成功预测了所有生命分子&#xff08;蛋白质、DNA、RNA、配体等&#xff09;的结构和相互作用。与现…

企业级通用业务 Header 处理方案

目录 01: 处理 PC 端基础架构 02: 通用组件&#xff1a;search 搜索框能力分析 03: 通用组件&#xff1a;search 搜索框样式处理 04: 通用组件&#xff1a;Button 按钮能力分析 05: 通用组件&#xff1a;Button 按钮功能实现 06: 通用组件&#xff1a;完善 search 基本…

地图位置的二维码怎么做?在线制作地图二维码的方法

怎么定位一个位置做成二维码呢&#xff1f;随着互联网的不断发展&#xff0c;现在通过扫描二维码来获取导航位置的方式有很多的场景都在应用。这种方式的好处在于其他人都可以通过这个二维码来获取位置&#xff0c;有利于分享。 导航地图二维码可以在电脑的二维码生成器上快速…

推荐 3 个 yyds 的开源项目!

本期推荐开源项目目录&#xff1a; 1. AI 搜索引擎 2. 大模型聊天框架 3. 模仿抖音的移动端短视频 01 AI 搜索引擎 Perplexica 是一个开源的、由 AI 驱动的搜索引擎。它深入互联网寻找答案&#xff0c;不仅搜索网络&#xff0c;还理解您的问题。 Perplexica 受到 Perplexity AI…

安卓使用Fiddler抓包 2024

简介 最近试了一下安卓使用fiddler 抓包&#xff0c;发现https包基本都会丢失。原因是Anandroid 7版本针对ssl安全性做了加强&#xff0c;不认可用户的证书。我们要做的就是把fiddler导出的证书进过处理后放置到系统证书目录下面&#xff0c;这样才能抓包https请求。 这里使用…

分割模型Maskformer系列

maskformer&#xff1a;Per-Pixel Classification is Not All You Need for Semantic Segmentation 论文地址&#xff1a;https://arxiv.org/pdf/2107.06278 1.概述 传统的语义分割方法通常采用逐像素分类&#xff08;per-pixel classification&#xff09;&#xff0c;而实…

Ubuntu20.4中复现Graspness

Ubuntu20.4中复现Graspness 文章目录 Ubuntu20.4中复现Graspness1.安装cuda和cudnn2.安装pytorch3.安装MinkowskiEngine4.编译graspnetAPI5. RuntimeError: "floor" "_vml_cpu" not implemented for IntRefernece &#x1f680;非常重要的环境配置&#x1…