【逐步剖C++】-第三章-C++内存管理

news2024/12/24 2:30:57

一、C/C++内存分布

C/C++的内存分布主要分为如下这么四个区域:

1、栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。

2、 堆区(heap):一般由程序员申请分配与释放, 若程序员不释放,程序结束时可能由操作系统回收 。

3、数据段(静态区)(static):存放全局变量、静态数据。程序结束后由系统释放。

4、代码段:存放函数体(类成员函数和全局函数)的二进制代码

这里可通过代码和相关问题来认识一下各类型数据在内存中的分布,请看:

int globalVar = 1;
static int staticGlobalVar = 1;
void Test()
{
	static int staticVar = 1;
	int localVar = 1;
	int num1[10] = { 1, 2, 3, 4 };
	char char2[] = "abcd";
	const char* pChar3 = "abcd";
	int* ptr1 = (int*)malloc(sizeof(int) * 4);
	int* ptr2 = (int*)calloc(4, sizeof(int));
	int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 4);
	free(ptr1);
	free(ptr3);
}

(1)判断变量的存储位置
选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)

globalVar在哪里?C
staticGlobalVar在哪里? C
staticVar在哪里?C
localVar在哪里? A
num1 在哪里?A
char2在哪里?A
*char2在哪里?A
pChar3在哪里?A
*pChar3在哪里?D
ptr1在哪里?A
*ptr1在哪里?B

这里的易错点是const char*类型的指针变量pChar,看到const可能就想着其会在常量区,其实不然;其本质还是一个局部变量,所以其存储位置在栈区上;实际上,加不加const都不影响变量本身的存储位置,加const只是为了“适配”const指向的内容,如const int a = 1;,a仍存储在栈区。

二、C++内存管理

1、C内存管理

关于C内存管理大家可以阅读笔者的这一篇文章:【逐步剖C】-第十一章-动态内存管理,这里不再具体说明啦。

2、C++内存管理

C++内存管理是在C内存管理基础上的更新,以便适应C++面向对象编程的特点。C++通过newdelete两个操作符进行动态内存管理。
newdelete对内置类型和自定义类型进行操作时的行为不同:
(1)对内置类型的操作

  • 动态申请一个int类型的空间和释放
int* ptr = new int;
delete ptr;
  • 动态申请一个int类型的空间并初始化为10和释放
int* ptr = new int(10);
delete ptr;
  • 动态申请10个int类型的空间和释放
int* ptr = new int[10];
delete[] ptr;

使用注意事项:申请和释放单个元素的空间时,使用newdelete操作符;申请和释放连续的空间,使用new[]delete[],要匹配起来成对使用;

(2)对自定义类型的操作
与内置类型不同的是,new在为自定义类型开空间时还会调用其构造函数,相应地,delete调用其析构函数

class A
{
public:
    A(int a = 0)
        : _a(a)
    {
        cout << "A():" << this << endl;
    }
    ~A()
    {
        cout << "~A():" << this << endl;
    }
private:
    int _a;
};

int main()
{
    //动态申请和释放一个A类对象大小的空间并将其成员_a初始化为1
    A* ptra = new A(1);
    delete ptra;

    return 0;
}

运行结果:
在这里插入图片描述

三、new和delete本质

1、operator new 和 operator delete函数

operator newoperator delete是系统提供的全局函数new操作符在底层调用operator new全局函数来申请空间,delete操作符在底层通过operator delete全局函数来释放空间。

(1)operator new
该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,尝试执行用户设置的空间不足应对措施,若成功执行则继续申请;否则抛异常(PS:抛异常是面向对象语言处理错误的方式

(2)operator delete
该函数最终会调用一个名为_free_dbg的函数进行空间释放,而free函数本身其实也是这个函数的宏定义函数

相关源码如下,供参考:

void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
    // try to allocate size bytes
    void* p;
    while ((p = malloc(size)) == 0)

        if (_callnewh(size) == 0)
        {
            // report no memory
            // 如果申请内存失败了,这里会抛出 bad_alloc 类型异常
            static const std::bad_alloc nomem;
            _RAISE(nomem);
        }
    return (p);
}

void operator delete(void* pUserData)
{
    _CrtMemBlockHeader* pHead;
    RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
    if (pUserData == NULL)
        return;
    _mlock(_HEAP_LOCK); /* block other threads */
    __TRY
        /* get a pointer to memory block header */
        pHead = pHdr(pUserData);
    /* verify block type */
    _ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
    _free_dbg(pUserData, pHead->nBlockUse);		//释放空间
    __FINALLY
        _munlock(_HEAP_LOCK); /* release other threads */
    __END_TRY_FINALLY
        return;
}

//free的实现,本质为宏函数

#define free(p) _free_dbg(p, _NORMAL_BLOCK)

2、new和delete的实现原理

(1)对内置类型
newmalloc类似,而deletefree类似,不同的地方是:
操作符new/delete申请和释放的是单个元素的空间,若申请的是连续空间需要用操作符new[]delete[];但这两组操作符的本质都是调用了operator newoperator delete函数

(2)对自定义类型

  • new的原理
    调用operator new函数先进行空间的申请
    接着在申请的空间上执行构造函数,完成对象的构造
  • delete的原理
    先在空间上执行析构函数,完成对象中资源的清理工作
    接着调用operator delete函数释放对象的空间(归还使用权)
  • new[]的原理
    调用operator new[]函数,而实际在operator new[]中实际又调用operator new函数先完成N个对象空间的申请
    接着在申请的空间上执行N次构造函数
  • delete[]的原理
    先在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
    接着调用operator delete[]释放空间,而实际在operator delete[]中又调用operator delete来释放空间

可通过反汇编进行简单的验证:
用于验证的代码:

class A
{
public:
    A(int a = 0)
        : _a(a)
    {
        cout << "A():" << this << endl;
    }
    ~A()
    {
        cout << "~A():" << this << endl;
    }
private:
    int _a;
};


int main()
{
    A* a = new A(1);
    delete a;

    return 0;
}

验证结果如下图:

  • 对于new
    在这里插入图片描述
  • 对于delete
    在这里插入图片描述
    这里编译器对其做了进一步的封装,F11跳转进去后:
    在这里插入图片描述

对new和delete行为补充理解:
若有这么一个类,在初始化时需为其成员进行内存资源的分配,如:

class A
{
public:
    A(int a = 0)
        : _a(a)
    {
        _pa = new int[5];        //为pa成员申请5个整型大小的空间
        cout << "A():" << this << endl;
    }
    ~A()
    {
        delete pa;
        cout << "~A():" << this << endl;
    }
private:
    int _a;
    int* _pa;
};

int main()
{
    A* a = new A(1);
    delete a;

    return 0;
}

那么要先为整个类的对象开空间,才能通过类的构造函数为该成员开空间;
同理,在对象销毁时,需先调用析构函数把为成员开的空间资源进行清理释放后,才释放整个对象的空间。若先释放了空间,那么申请的资源就会“丢了”,即会造成内存泄漏

这里也能侧面说明malloc要配合free使用,new要配合delete使用的原因;从上面的代码来看,如果用malloc给A类对象申请空间,而用delete来进行空间释放就会出现问题:malloc函数仅进行空间分配,不会调用构造函数,即A类对象的成员_pa所指向的空间是未定义(随机)的,此时用delete进行空间释放时,会先调用析构函数来回收和释放_pa成员所指向的空间,如此一来就造成了非法的内存访问

3、定位new

由于定位new在学习阶段用得不多,所以这里仅进行一定简单的说明
(1)定义
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象

使用格式:

new (place_address) type 
或 
new (place_address) type(initializer-list)

其中place_address必须是一个指针initializer-list是类型的初始化列表(PS:可以理解为类似初始化数组用的{}
使用示例:

class A
{
public:
    A(int a1 = 1, int a2 = 2)
        : _a1(a1)
        ,_a2(a2)
    {
        cout << "A():" << this << endl;
    }
    ~A()
    {
        cout << "~A():" << this << endl;
    }
private:
    int _a1;
    int _a2;
};

int main()
{
    A* p0 = new A;      //new:开空间+构造

    A* p1 = (A*)malloc(sizeof(A)); 
    //p1现在指向的只不过是与A对象相同大小的一段空间,还不能算是一个对象,因为构造函数没有执行
    new(p1)A{1,2};
    //通过定位new调用其构造函数,{1,2}即为初始化列表,当然也可写为new(p1)A(1, 2);
    p1->~A();           //显示调用析构,然后再free,即和delete操作符执行的顺序保持一致
    free(p1);           //不能直接delete,因为分配空间用的是malloc,要配套使用

    A* p2 = (A*)operator new(sizeof(A));        
    //operator new函数本质调用malloc开空间,并不会调用构造
    new(p2)A(10);
    p2->~A();
    operator delete(p2);

    return 0;
}

(2)使用场景
定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如
果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。

关于池化技术,大家可以阅读一下本站的这篇文章:什么是池化技术

四、C/C++内存管理常见问题

1、malloc、free和new、delete的区别

malloc/free和new/delete的共同点:
都是从堆上申请空间,并且需要用户手动释放。
不同点:

  • malloc和free是函数newdelete操作符
  • malloc仅能申请空间无法满足初始化的功能;而new有手动初始化的功能
  • malloc申请空间时,需要计算空间大小并传递;new只需在其后跟上空间的类型即可,
    如果是多个对象,在new[][]中指定对象个数即可
  • malloc的返回值为void*, 在使用时必须强转new不需要,因为new后直接跟了空间的类型
  • malloc申请空间失败时,返回的是NULL,因此使用时必须判空new不需要,但是new
    要捕获异常
  • 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数;而new
    申请空间后会调用构造函数完成对象的初始化delete先调用析构函数完成空间中资源的清理然后释放空间

2、关于内存泄漏

(1)内存泄漏的定义
内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。

(2)内存泄漏的分类
C/C++程序中一般我们关心两种方面的内存泄漏:
堆内存泄漏(Heap leak):
堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。
系统资源泄漏
指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定

(3)内存泄漏的检测与预防
关于内存泄漏的检测与预防,这里放上几篇优质文章供大家阅读:
Linux下几款C++程序中的内存泄露检查工具
内存泄露检测工具比较

本章完。

看完觉得有觉得帮助的话不妨点赞收藏鼓励一下,有疑问或看不懂的地方或有可优化的部分还恳请朋友们留个评论,多多指点,谢谢朋友们!🌹🌹🌹

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

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

相关文章

电机控制、运动控制、过程控制三者关系

1. 电机控制 { 单电机参数&#xff1a;位置/角位移(方向)环、速度环(加减速、最大速度、启停速)、扭/转矩环三个控制环 } Motor Control 主要关注的是&#xff0c;控制单个电机的转距(torque control mode)、速度(speed control mode)、位置(position control mode)中的一个或…

自学(黑客)技术方法————网络安全

如果你想自学网络安全&#xff0c;首先你必须了解什么是网络安全&#xff01;&#xff0c;什么是黑客&#xff01;&#xff01; 1.无论网络、Web、移动、桌面、云等哪个领域&#xff0c;都有攻与防两面性&#xff0c;例如 Web 安全技术&#xff0c;既有 Web 渗透2.也有 Web 防…

若依分离版-前端使用

1 执行 npm install --registryhttps://registry.npm.taobao.org&#xff0c;报错信息如下 npm ERR! code ERESOLVE npm ERR! ERESOLVE unable to resolve dependency tree npm ERR! npm ERR! While resolving: ktg-mes-ui3.8.2 npm ERR! Found: vue2.6.12 npm ERR! node_modu…

102-视频与网络应用篇-环境搭建

1.开发环境 本栏目开发所采集的操作系统是windows10Vmware上安装的Ubuntu18&#xff0c;关于Vmware和Ubuntu操作系统的安装过程本文不详细描述&#xff0c;具体安装步骤大家可以自己百度&#xff0c;有很丰富的安装教程。 本栏目查看资料、代码编辑、均在windows端&#xff0c;…

Unittest接口自动化测试

我又来了&#xff0c;来分享年前的unittest接口自动化实战啦。这次自动化接口框架比较简单&#xff0c;但是五脏俱全。(注:项目是针对我们公司内部系统的测试&#xff0c;我就不分享链接了。&#xff09; 项目简介 项目名称&#xff1a;****名片系统 项目目的&#xff1a;实…

wails window下打包安装包

官方文档https://wails.io/zh-Hans/docs/guides/windows-installer 版本&#xff1a;Wails CLI v2.6.0 问题 打包命令 wails build 打包出来的exe文件是直接打开的&#xff0c;不能包含程序安装功能 解决办法 1、chocolatey 安装包管理器工具安装 管理员身份打开powershell&a…

基于旗鱼优化的BP神经网络(分类应用) - 附代码

基于旗鱼优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码 文章目录 基于旗鱼优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码1.鸢尾花iris数据介绍2.数据集整理3.旗鱼优化BP神经网络3.1 BP神经网络参数设置3.2 旗鱼算法应用 4.测试结果&#xff1a;5.M…

vue解决:Parsing error: No Babel config file detected for ....

报错信息 Parsing error: No Babel config file detected for C:\Users\Admin\Desktop\shabi\work\src\App.vue. Either disable config file checking with requireConfigFile: false, or configure Babel so that it can find the config files. 分析错误&#xff1a;没有检测…

Pyside6 安装和简单界面开发

Pyside6 安装和简单界面开发 Pyside6介绍Pysied6开发环境搭建Python安装Pysied6安装 Pyside6界面开发简单界面设计界面设计界面编译 编写界面初始化代码软件打包 Pyside6介绍 对于Python的GUI开发来说&#xff0c;Python自带的可视化编程模块的功能较弱&#xff0c;PySide是跨…

嵌入式处理趋势,第一部分:超集成MCU

当今的嵌入式微控制器&#xff08;MCU&#xff09;是协同和创新的惊人例子。单个芯片上可容纳30,000至2百万个门&#xff0c;直到最近&#xff0c;各种集成的组件和模块都被视为独立的高级IC。 例如&#xff0c;当前典型的MCU设备&#xff08;下面的图1&#xff09;可能包含以…

【Kubernetes】深入了解 Kubernetes:现代容器编排与管理平台

前言 kubernetes&#xff0c;简称K8s&#xff0c;是用8代替名字中间的8个字符“ubernete”而成的缩写。是一个开源的&#xff0c;用于管理云平台中多个主机上的容器化的应用&#xff0c;Kubernetes的目标是让部署容器化的应用简单并且高效&#xff08;powerful&#xff09;,Kub…

【红日靶场】vulnstack3-完整渗透过程

系列文章目录 【红日靶场】vulnstack1-完整渗透过程 【红日靶场】vulnstack2-完整渗透过程 【红日靶场】vulnstack3-完整渗透过程 文章目录 系列文章目录基本信息环境配置开始渗透信息收集暴力破解漏洞利用绕过内网信息收集尝试上线msf上线msf横向移动msf 传达会话给cs横向到域…

关于IDEA中gradle项目bootrun无法进入断点以及gradle配置页面不全的解决方案

问题背景 在使用gradle编写的bootrun&#xff0c;采用debug方式启动项目时&#xff0c;无法进入断点&#xff0c;程序正常运行 并发现象1 此处无法识别为大象图标 点击右键后&#xff0c;没有圈中的这个选项 并发现象2 图片圈中的位置缺失 问题原因 正常的 run 命令是通过…

【状态估计】将Transformer和LSTM与EM算法结合到卡尔曼滤波器中,用于状态估计(Python代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

蓝牙技术|Matter或能改变中国智能家居市场,蓝牙技术将得到进一步应用

近年来&#xff0c;智能家居开放协议标准Matter&#xff08;目前版本 1.1&#xff09;由连接标准联盟发布&#xff0c;该联盟是一个由数百家公司组成的全球性机构&#xff0c;旨在提供与物联网 (IoT) 相关的标准。例如&#xff0c;Matter 用于允许 Amazon Alexa、Apple Home、G…

【java源码】二甲医院his系统全套源码 云HIS系统源码

基层医院云HIS系统源码 一款满足基层医院各类业务需要的云HIS系统。该系统能帮助基层医院完成日常各类业务&#xff0c;提供病患挂号支持、病患问诊、电子病历、开药发药、会员管理、统计查询、医生站和护士站等一系列常规功能&#xff0c;还能与公卫、PACS等各类外部系统融合&…

样式组件-样式失效问题

问题&#xff1a; 正常步骤&#xff08; npm install 安装样式组件引入到代码中使用less文件中&#xff0c;引入样式文件 import ‘~dtd/lib/style/themes/default.less’; &#xff09; 步骤之后&#xff0c;样式与网页展示不一致&#xff0c;样式没有正常显示 解决&#x…

第八课 二分

文章目录 第八课 二分lc704.二分查找--简单题目描述代码展示 二分模版lc34.排序数组中查找元素的第一个和最后一个位置--中等题目描述代码展示 lc69.x的平方根--简单题目描述代码展示 lc74.搜索二维矩阵--中等题目描述代码展示 lc153.寻找旋转排序数组中的最小值--中等题目描述…

剑指offer——JZ33 二叉搜索树的后序遍历序列 解题思路与具体代码【C++】

一、题目描述与要求 二叉搜索树的后序遍历序列_牛客题霸_牛客网 (nowcoder.com) 题目描述 输入一个整数数组&#xff0c;判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则返回 true ,否则返回 false 。假设输入的数组的任意两个数字都互不相同。 数据范围&#xff…

12P2532X162-233A KJ3222X1-BA1 CE4003S2B3 EMERSON CONTROLLER

12P2532X162-233A KJ3222X1-BA1 CE4003S2B3 EMERSON CONTROLLER EDGEBoost I/O模块是一种可扩展的模块化解决方案&#xff0c;集成到Premio的工业计算机中&#xff0c;通过即插即用的可扩展性提供增强的可靠性。这些附加模块有助于解决在加固边缘出现的设计限制和兼容性问题。…