C++笔记---内存管理

news2024/9/20 5:59:05

1. 内存分布

在对操作系统有更加深入的了解之前,在写代码的层面我们需要对下面的几个内存区域有所了解:

1. 栈又叫堆栈--非静态局部变量/函数参数/返回值等等,栈是向下增长的。

2. 堆--用于程序运行时动态内存分配,堆是可以上增长的。

3. 数据段--存储全局数据和静态数据。

4. 代码段--可执行的代码/只读常量。

 简单来说,直接定义的变量存放在栈中,动态申请的空间存放在堆中,全局或静态变量存放在数据段,字面量存放在代码段。

值得注意的是以下这段代码:

 const char* pChar = "abcd";
 int* ptr = (int*)malloc(sizeof(int) * 4);

这段代码中,“pChar”和“ptr”作为直接定义的变量,都是存放在栈中的。

而“abcd”作为只可读不可写的字面量存放在代码段,“pChar”中存放的是这个字符串的首元素地址。

malloc动态申请的空间存放在堆中,“ptr”只是存放其地址。

需要与上面的“pChar”形成区分的是:

char charArr[] = "abcd";

这段代码并非将“abcd”这一字面量字符串定义为了一个数组,而是以该字符串的内容为蓝本,在栈上定义了一个数组。

2. C++内存管理方式(new/delete)

在C语言中,我们使用malloc/calloc/realloc/free四个函数来进行内存管理。

C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因 此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。

申请/释放单个对象:

Type* pName = new Type(value);

delete pName;

// 动态申请一个int类型的空间
int* p1 = new int;

// 动态申请一个int类型的空间,并将其初始化为10
int* p2 = new int(10);

delete p1;
delete p2;

申请/释放数组:

Type* pName = new Type[num];

delete[] pName;

// 动态申请10个int类型的空间
int* p3 = new int[10];

delete[] p3;

3. new/delete与malloc/free的区别

最主要的区别就是,new/delete在申请和释放自定义类型的对象时,会调用对应类的构造和析构函数,而malloc/free则不会。

究其本质,new/delete是按照类型来处理空间,malloc/free是按照大小来处理空间。

换句话来说,malloc并不知道自己申请的这块空间是用作何用的,准确来说是这块空间根本没有类型,只不过我们将malloc返回的指针进行了强转,并通过这个指针来对这块空间进行操作,使其具有了对应类型的性质。

由于空间本身不存在任何类型,自然在被申请和被释放时都不会有某个类的构造函数和析构函数参与。

用new/delete来进行内存管理,可以让类的封装性更强(确保类的每个对象都会经历构造和析构函数)

二者区别总结:

共同点:都是从堆上申请空间,并且需要用户手动释放。

不同点

1. malloc和free是函数,new和delete是操作符;

2. malloc申请的空间不会初始化,new可以初始化;

3. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可, 如果是多个对象,[]中指定对象个数即可;

4. malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型;

5. malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需 要捕获异常;

6. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new 在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理释放。

 可以通过下面的方式来捕获异常:

try
{
    int* p = new int;
}
catch(const exception& e)
{
    cout << e.what() << endl;
}

 exception是C++的一种内置类型,what()函数会返回异常信息。

4. operator new与operator delete函数

new/delete在工作时,会经历“申请/释放空间”和“调用构造/析构函数”两步。

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

operator new函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,尝试执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。

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);
}

同理,operator delete函数实际也是通过free来释放空间的。

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_dbg"实际上就是free,头文件中free的实现如下:

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

用operator new和operator delete对malloc和free进行包装的目的是为了适应C++的异常抛出与捕获机制。

5.  new和delete的实现原理

new的原理:

1. 调用operator new函数申请空间;

2. 在申请的空间上执行构造函数,完成对象的构造。

delete的原理:

1. 在空间上执行析构函数,完成对象中资源的清理工作;

2. 调用operator delete函数释放对象的空间。

 new Type[num]的原理:

1. 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请;

2. 在申请的空间上执行N次构造函数。

 delete[]的原理:

1. 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理;

2. 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释 放空间。

6. 定位new表达式(placement-new)

定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。

由于析构函数可以显式调用,所以不存在“定位delete表达式”。

使用格式:

new (place_address) Type或者new (place_address) Type(initializer-list)

place_address必须是一个指针,initializer-list是类型的初始化列表

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

int main()
{
    A* p1 = (A*)malloc(sizeof(A));
    new(p1)A(1);// 有默认构造时也可不要初始化列表
    p1->~A();
    free(p1);

    A* p2 = (A*)operator new(sizeof(A));
    new(p2)A(10);
    p2->~A();
    operator delete(p2);    

    return 0;
}

使用场景:

定位new表达式在实际中一般是配合内存池使用。

因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显式调构造函数进行初始化。

7. 申请释放方式不匹配

前面讲到,单个对象和申请数组有自己对应的申请释放方式。

自然,我们不建议混用,但是如果混用会出现什么问题呢?

由于new/delete的底层是malloc/free,所以内置类型进行混用不会出现问题,但自定义类型会。

1. new + free

析构函数不会被调用,如果对象中有动态资源的话,这些动态资源就没有被释放,会导致内存泄漏。

2. malloc + delete

构造函数不会被调用,对象不会进行初始化,未进行初始化的对象在调用成员函数时可能出现野指针或空引用等错误。

3. new [] + free

无析构函数时,正常;有析构函数时,程序会直接崩溃

new []还存在一个隐藏特性:如果类中有显式实现的析构函数,new []在开辟数组空间时,会在数组前额外开辟四个字节的空间,用于存储数组的元素个数,该数据存在的目的是告诉delete[]有多少个元素需要被释放。 

而返回的地址依然是数组首元素的地址。

由于这整个数组及数组前的空间是new利用malloc一并申请的连续的空间,所以当我们给free传入数组首元素的地址时,就会发生报错(free不能释放连续空间的某部分)。

4. malloc + delete[]以及new + delete[]

无析构函数时,正常;有析构函数时,由于缺少数组前的数据,会一直重复调用析构函数,具体机理不清楚(vs2022)。

 5. new [] + delete

delete单独释放首元素,导致与3相似的错误。

 由于是先调用析构函数再释放,所以此处调用了一次析构函数之后才发生报错。

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

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

相关文章

【数据结构3】哈希表、哈希表的应用(集合与字典、md5算法和文件的哈希值)

1 哈希表 哈希表一个通过哈希函数来计算数据存 储位置的数据结构&#xff0c;通常支持如下操作: 插入(键&#xff0c;值):插入键值对(键&#xff0c;值) Get(key):如果存在键为键的键值对则返回其值&#xff0c;否则返回空值 删除(键):删除键为键的键值对哈希表(Hash Table&am…

数据仓库系列 2:数据仓库的核心特点是什么?

想象一下,你正站在一座巨大的数据金矿前。这座金矿蕴含着海量的商业洞察,可以帮助你的公司做出精准决策,提升效率,远超竞争对手。但是,如何高效地开采、提炼和利用这些数据黄金呢?答案就是:数据仓库。 目录 什么是数据仓库?数据仓库的核心特点面向主题的组织集成性非易失性…

RTL-SDR SpectrumPy频谱显示

GITHUB大佬开源的基于RTL-SDR的python频谱显示程序链接&#xff0c;下载下来后&#xff0c;安装必要的库&#xff0c;编译运行&#xff0c;运行报错。 修改了以下两个地方&#xff1a; 修改点1&#xff1a; 修改前&#xff1a; self.spinBoxFrequency.setValue(self.center_fr…

【Python从入门到进阶】63.Pandas如何实现数据的Merge

接上篇《62、Pandas中DataFrame对象案例实践》 上一篇我们延续之前学习的DataFrame对象的知识&#xff0c;结合一个数据案例进行了实践操作。本篇我们来学习Pandas如何实现数据的Merge。 一、引言 在当今数据驱动的时代&#xff0c;数据分析已成为各行各业不可或缺的一部分。…

【JAVA基础】四则运算符

文章目录 四则运算结合运算符自增运算符关系和boolean运算符 四则运算 在java当中&#xff0c;使用运算符、-、*、/ 表示加减乘除&#xff0c;当参与 / 运算的两个操作数都是整数的时候&#xff0c;表示整数除法&#xff1b;否则表示浮点数。整数的求余操作用 % 表示。 Syste…

【Java】/* 与树有关的一些概念 */

一、关于树的一些概念 1. 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看 起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的。它具有以下的特点&#xff1a;…

记录一次经历:使用flask_sqlalchemy集成flask造成循环导入问题

前言&#xff1a; 工作需求&#xff0c;写一个接口&#xff0c;用Python来编写&#xff0c;我首先想到用flask小型框架来支撑&#xff0c;配置sqlalchemy来实现&#xff0c;但是在实现的过程中&#xff0c;发生循环导入问题 我想到用蓝图来解决此问题&#xff0c;但是仍然会出死…

UI测试使用webdriver-manager免安装浏览器驱动

引言&#xff1a; selenium传统的方式是下载浏览器对应的driver&#xff08;驱动&#xff09;&#xff0c;放到本地的指定位置&#xff0c;然后写代码加载这个driver&#xff08;驱动&#xff09;再执行相应的操作。 弊端&#xff1a; 传统方法存在两个麻烦的地方: 1.需要下…

安全面试常见问题任意文件下载

《网安面试指南》http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247484339&idx1&sn356300f169de74e7a778b04bfbbbd0ab&chksmc0e47aeff793f3f9a5f7abcfa57695e8944e52bca2de2c7a3eb1aecb3c1e6b9cb6abe509d51f&scene21#wechat_redirect 1.1 任意文件下…

Git的使用教程及常用语法03

七.如何从版本库中删除文件 第一种方式&#xff1a;直接在工作区删除文件&#xff0c;然后提交 rm ffile1.txt (注意&#xff1a;这个不是git命令&#xff0c;而是linux命令) 看到状态发现&#xff0c;文件file1.txt已经被删除&#xff0c;提示需要提交到暂存区。 因为我们只…

蓝牙对象交换协议(OBEX) - 概念介绍

零.声明 本专栏文章我们会以连载的方式持续更新&#xff0c;本专栏计划更新内容如下&#xff1a; 第一篇:蓝牙综合介绍 &#xff0c;主要介绍蓝牙的一些概念&#xff0c;产生背景&#xff0c;发展轨迹&#xff0c;市面蓝牙介绍&#xff0c;以及蓝牙开发板介绍。 第二篇:Trans…

SpringBoot集成kafka-监听器注解

SpringBoot集成kafka-监听器注解 1、application.yml2、生产者3、消费者4、测试类5、测试 1、application.yml #自定义配置 kafka:topic:name: helloTopicconsumer:group: helloGroup2、生产者 package com.power.producer;import com.power.model.User; import com.power.uti…

Windows系统上进行项目管理工具VisualSVN Server服务端的保姆级安装教程与配置和SVN客户端保姆级安装教程和使用

一、VisualSVN Server简介 Subversion Server for Windows | VisualSVN ServerGet an easy to use Subversion (SVN) server for Windows. It works out-of-the-box and is suitable both for small business and enterprises. Available for free!https://www.visualsvn.com/…

4.Redis单线程和多线程

1.Redis的单线程 Redis的单线程主要是指Redis的网络IO和键值对读写是由一个线程完成的&#xff0c;Redis在处理客户端的请求时包括获取&#xff08;Socket读&#xff09;、解析、执行、内容返回&#xff08;Socket写&#xff09;等都由一个顺序串行的主线程处理&#xff0c;这…

Linux 下命令行参数和环境变量

Linux 下命令行参数和环境变量 命令行参数为什么要有命令行参数谁可以做到结论 环境变量一些现象查看环境变量添加环境变量添加内存级环境变量永久有效 其他环境变量HOMEPWDSHELLHISTSIZE 自定义环境变量定义取消 本地变量整体理解环境变量环境变量的组织方式Linux 代码获取环境…

SpringBoot集成kafka接收对象消息

SpringBoot集成kafka接收对象消息 1、生产者2、消费者3、工具类4、消息实体对象5、配置文件6、启动类7、测试类8、测试结果 1、生产者 package com.power.producer;import com.power.model.User; import com.power.util.JSONUtils; import org.springframework.kafka.core.Kaf…

UEStudio V24 中文授权版

UEStudio是一款集成开发环境&#xff08;IDE&#xff09;软件&#xff0c;主要用于编写和编辑各种类型的代码&#xff0c;包括C/C、Java、HTML、PHP、Perl、Python等。 软件截图&#xff1a; 使用说明&#xff1a; 解压后&#xff0c;双击start_UEStudio.bat来运行软件 下载地…

【计算机组成原理】计算机系统概述<1>

学习目标&#xff1a; 掌握计算机组成原理的基础知识巩固 例如&#xff1a; 信息化世界的组成 计算机系统概述 计算机硬件基本组成 各个硬件的工作原理 计算机软件 计算机系统的多层次结构 计算机系统的工作原理 计算机性能指标 学习内容&#xff1a; 1.0、初入计算机组成原…

Apollo9.0 PNC源码学习之Planning模块—— Lattice规划(七):横纵向运动轨迹的优选

参考文章: (1)Apollo6.0代码Lattice算法详解——Part 7: 获得最优轨迹 (2)Lattice算法详解 0 前言 // 优选出cost最小的trajectory// 7. always get the best pair of trajectories to combine; return the first// collision-free trajectory.size_t constraint_failure…

Latent-OFER:使用潜在向量进行检测、屏蔽和重建,以实现遮挡的面部表情识别

论文&#xff1a;Latent-OFER: Detect, Mask, and Reconstruct with Latent Vectors for Occluded Facial Expression Recognition 摘要&#xff1a;所提出的方法Latent-OFER可以检测遮挡&#xff0c;将面部被遮挡的部分恢复为未被遮挡的部分&#xff0c;并识别它们&#xff0…