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

news2024/11/16 0:04:32

目录

前言        

正文

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; // 在栈上分配局部变量a
    if (n > 0) {
        test(n - 1); // 递归调用
    }
}

int main() {
    int x = 5; // 在栈上分配局部变量x
    test(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分配一个整数并赋值为42
    cout << "动态分配的整数:" << *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.coloradmin.cn/o/1656614.html

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

相关文章

ps5电玩计时收费系统软件教程,电玩店适合的计时器,电脑定时语音提醒

ps5电玩计时收费系统软件教程&#xff0c;电玩店适合的计时器&#xff0c;电脑定时语音提醒 一、前言 以下软件操作教程以&#xff0c;佳易王电玩计时计费管理软件为例说明 软件文件下载可以点击最下方官网卡片——软件下载——试用版软件下载 1、计时计费功能&#xff1a;只…

Linux内存管理——Swap

swap space 一个磁盘区域&#xff0c;作为内存使用。当系统内存不足时&#xff0c;会将一些很久不使用的数据转移到swap space中。 优点&#xff1a;扩展了内存空间 缺点&#xff1a;用磁盘做内存&#xff0c;读写效率降低。 swappiness swappiness的值表示建议swap space替…

GtkButton事件处理、事件的捕获、鼠标事件等

事件 事件处理 GTK 所提供的工具库与其应用程序都是基于事件触发机制来管理&#xff0c; 所有的应用程序都是基于事件驱动。 如果没有事件发生&#xff0c; 应用程序将处于等待状态&#xff0c; 不会执行任何操作&#xff0c; 一旦事件发生&#xff0c; 将根据不同的事件做出…

使用 YOLO 进行自定义对象检测

使用 YOLO 进行自定义对象检测 1. 创建数据集 机器是通过数据集学习的。数据集必须包含图像和标签。例如&#xff0c;让我的目标是创建一个检测坦克的系统。 我准备了从网上下载的坦克图片。然后我们需要使用第三方工具对图像进行标记&#xff0c;例如&#xff1b;LabelImg、…

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

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

团结引擎+OpenHarmony 记录 (持续更新中)

1 TuanjiePlayerAbility.ts 中获取 content 引用 globalThis.AbilityContext 在 TuanjiePlayerAbility.ts 中是可以获取到的 但是在 tslib 或者中 globalThis.AbilityContext 是无法获取到的GetFromGlobalThis(‘AbilityContext’); 同样 在 TuanjiePlayerAbility.ts 中是可以…

文献速递:深度学习医学影像心脏疾病检测与诊断--基于深度学习的PET图像重建与运动估计

Title 题目 Deep Learning Based Joint PET Image Reconstruction and Motion Estimation 基于深度学习的PET图像重建与运动估计 01 文献速递介绍 正电子发射断层扫描&#xff08;PET&#xff09;成像是一种非侵入性成像技术&#xff0c;通过使用放射性示踪剂在活体内可视化…

UE4_摄像机_使用摄像机的技巧

学习笔记&#xff0c;不喜勿喷&#xff01;祝愿生活越来越好&#xff01; 知识点&#xff1a; a.相机跟随。 b.相机抖动。 c.摄像机移动 d.四元数插值&#xff08;保证正确旋转方向&#xff09;。 e.相机注视跟踪。 1、新建关卡序列&#xff0c;并给小车添加动画。 2、创…

C++奇迹之旅:string类接口详解(上)

文章目录 &#x1f4dd;为什么学习string类&#xff1f;&#x1f309; C语言中的字符串&#x1f309;string考察 &#x1f320;标准库中的string类&#x1f309;string类的常用接口说明&#x1f320;string类对象的常见构造 &#x1f6a9;总结 &#x1f4dd;为什么学习string类…

Unity Navigation 入门(新版)

概述 在游戏的制作过程中&#xff0c;寻路功能一定是非常重要的部分&#xff0c;他可以为主角寻路&#xff0c;也可以运用到敌人追击等&#xff0c;相比于自己实现的难度&#xff0c;使用寻路组件就显得简单的多&#xff0c;那接下来就开始学习这部分的内容吧 1.安装AI Naviga…

vue3打开页面后文本框自动获得焦点

字符串写法 <script setup> import { ref, onMounted } from vue import ./index.cssconst input ref(null)onMounted(() > {input.value.focus() }) </script><template><div class"m-home-wrap"><input ref"input" />…

[NSSRound#1 Basic]basic_check

[NSSRound#1 Basic]basic_check 开题什么都没有&#xff0c;常规信息搜集也无效 发现题目允许PUT的两种做法&#xff1a; 1、 CURL的OPTIONS请求方法查看允许的请求方式 curl -v -X OPTIONS http://node4.anna.nssctf.cn:28545/index.php2、 kali自带的nikto工具扫描网址 Nik…

已经有 Prometheus 了,还需要夜莺?

谈起当下监控&#xff0c;Prometheus 无疑是最火的项目&#xff0c;如果只是监控机器、网络设备&#xff0c;Zabbix 尚可一战&#xff0c;如果既要监控设备又要监控应用程序、Kubernetes 等基础设施&#xff0c;Prometheus 就是最佳选择。甚至有些开源项目&#xff0c;已经内置…

css z-Index 详解--子元素盖在父元素的兄弟元素上

前置知识 1、z-index 只有在定位元素上才会生效&#xff08;即非static定位的元素上&#xff09; 2、同级元素&#xff0c;无论是z-index 相同还是没设置。后面的元素层级比前面 3、元素上有 transform 属性 z-index 会失效 dom结构如下 // dom部分 <div><div id&quo…

数学建模资料|历年数维杯数学建模竞赛真题及获奖论文汇总

2024年第九届数维杯大学生数学建模挑战赛&#xff1a;2024年5月10日08:00-5月13日09:00举行&#xff0c;为了更好的帮助参赛同学了解竞赛的赛制及赛题特点&#xff0c;数乐君今天给大家整理了历年数维杯国赛真题及优秀论文&#xff0c;方便同学们赛前巩固训练&#xff0c;掌握解…

私人健身教练预约管理小程序开发源码现成案例(小程序+APP+H5 源码部署)

一、私人健身教练预约管理系统-环境介绍 1.1 私人健身教练预约管理系统-运行环境 开发语言&#xff1a;PHP 数据库&#xff1a;MySQL 系统架构&#xff1a;TP 后端&#xff1a;SpringBoot 前端&#xff1a;Vue 2. 私人健身教练预约管理系统-系统介绍。 2.1私人健身教练预约管…

实在Agent智能体:引领智能自动化新纪元

在数字化转型的浪潮中&#xff0c;实在智能科技有限公司凭借其前沿技术&#xff0c;推出了实在Agent智能体——一款革命性的超自动化智能体。它不仅代表了人工智能技术的新高度&#xff0c;更预示着未来工作方式的变革。 什么是实在Agent智能体&#xff1f; 实在Agent智能体是…

拼多多投产比怎么计算?

拼多多投产比&#xff08;ROI&#xff09;的计算公式为&#xff1a;ROI 成交金额 / 花费 100%。也可以简单理解为&#xff1a;ROI 点击量 * 转化率 * 客单价 / (点击量 * 平均点击花费)。 拼多多推广可以使用3an推客。3an推客&#xff08;CPS模式&#xff09;给商家提供的营…

acronym 数据集

raise gl.ContextException(Could not create GL context) conda install -c conda-forge libstdcxx-ng ImportError: trimesh.viewer.windowed requires pip install "pyglet<2" pip install pyglet1.5.28github上的示例 3D 场景下的 RGBD Mask

Fortinet的安全愿景SASO概述

FTNT SASE的独特方法&#xff0c;使其成为一家适应性极强的厂商&#xff0c;能够应对不断变化的网络和网络安全环境。FTNT开发了一种名为Secure Access Service Omni&#xff08;SASO&#xff09;的变体&#xff0c;以更准确地反映FTNT在融合网络和安全功能方面的实力。我们预计…