深入剖析 C 与 C++ 动态内存管理之术

news2024/10/23 21:40:42

亲爱的读者朋友们😃,此文开启知识盛宴与思想碰撞🎉。

快来参与讨论💬,点赞👍、收藏⭐、分享🥰,共创活力社区。

🔥🔥🔥【C++】进阶:类相关特性的深入探讨

               【C++】类的默认成员函数:深入剖析与应用(下)


目录

💯前言

💯C/C++内存分布

💯C 语言中动态内存管理方式

(一)malloc函数

(二)calloc函数

(三)realloc函数

(四)free函数

💯C++ 中动态内存管理

(一)new操作符

(二)delete操作符

(三) new/delete操作内置类型 

(四)new和delete操作自定义类型 

💯operator new与operator delete函数(重要点进行讲解) 

(一)operator new函数

(二)operator delete函数

💯new和delete的实现原理

(一)new的实现原理

(二)delete的实现原理

💯定位new表达式 (placement - new)

(一)使用方法

(二)注意事项

💯总结


💯前言

在编程的广阔天地中,内存管理犹如一座坚实的基石,支撑着程序的稳定运行。无论是 C 语言还是 C++ 语言,动态内存管理都是一项关键技能,它赋予程序员在运行时灵活分配和释放内存的能力。

🎦本文将深入探讨 C 语言中的动态内存管理方式、C++ 中的动态内存管理方式,以及 C++ 中特有的operator newoperator delete函数、newdelete的实现原理以及定位new表达式(placement - new)。


💯C/C++内存分布

😃每日思考:

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 在哪里?在 char2 数组所分配的栈空间中
pChar3 在哪里?A(栈)
*pChar3 在哪里?常量区("abcd"存储在常量区,pChar3 指向这里)
ptr1 在哪里?A(栈)
*ptr1 在哪里?B(堆)(ptr1 指向通过 malloc 分配在堆上的空间)

2. 填空题:
sizeof(num1) = 40(假设 int 占 4 个字节,10 个 int 元素)
sizeof(char2) = 5(4 个字符加上结束符'\0')
strlen(char2) = 4(不包括结束符的字符长度)
sizeof(pChar3) = 8(或其他指针大小,取决于平台)
strlen(pChar3) = 4
sizeof(ptr1) = 8(或其他指针大小,取决于平台)

3. sizeof 和 strlen 区别?
sizeof 是运算符,在编译时确定其操作数的大小,可用于各种类型,包括数组时返回整个数组占用字节数,对于指针返回指针本身大小。
strlen 是函数,在运行时计算以空字符结尾的字符串长度,通过遍历字符串直到遇到空字符'\0'来确定长度,只能用于以空字符结尾的字符串。

 📌【说明】

  1. 又叫堆栈--非静态局部变量/函数参数/返回值等等,栈是向下增长的。
  2. 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。(Linux课程如果没学到这块,现在只需要了解一下)
  3. 用于程序运行时动态内存分配,堆是可以上增长的。
  4. 数据段--存储全局数据和静态数据。
  5. 代码段--可执行的代码/只读常量。

💯C 语言中动态内存管理方式

C 语言主要通过malloccallocreallocfree函数来进行动态内存管理。

(一)malloc函数

void *malloc(size_t size)用于分配指定大小的内存空间。返回值为指向分配内存的指针,如果分配失败则返回NULL

int *ptr = (int *)malloc(sizeof(int));
if (ptr == NULL) {
    printf("内存分配失败!\n");
    return;
}
*ptr = 10;
printf("分配的整数为:%d\n", *ptr);
free(ptr);

(二)calloc函数

void *calloc(size_t num, size_t size)用于分配num个大小为size的内存空间,并将其初始化为 0。

int *arr = (int *)calloc(5, sizeof(int));
if (arr == NULL) {
    printf("内存分配失败!\n");
    return;
}
for (int i = 0; i < 5; i++) {
    printf("数组元素 %d:%d\n", i, arr[i]);
}
free(arr);

(三)realloc函数

void *realloc(void *ptr, size_t size)用于调整已分配内存空间的大小。如果ptrNULL,则相当于malloc(size);如果调整成功,返回指向新分配内存的指针,否则返回NULL

int *old_arr = (int *)calloc(3, sizeof(int));
int *new_arr = (int *)realloc(old_arr, 5 * sizeof(int));
if (new_arr == NULL) {
    printf("内存调整失败!\n");
    return;
}
for (int i = 0; i < 5; i++) {
    new_arr[i] = i;
    printf("新数组元素 %d:%d\n", i, new_arr[i]);
}
free(new_arr);

(四)free函数

void free(void *ptr)用于释放由malloccallocrealloc分配的内存空间。


💯C++ 中动态内存管理

C++ 中除了可以使用 C 语言的动态内存管理函数外,还提供了newdelete操作符来进行动态内存管理。

(一)new操作符

用于动态分配内存并调用构造函数初始化对象。

class MyClass {
public:
    MyClass(int value) : data(value) {}
    int getData() const { return data; }
private:
    int data;
};

MyClass *obj = new MyClass(42);
std::cout << "对象的数据:" << obj->getData() << std::endl;
delete obj;

对于数组的动态分配:

int *arr = new int[5];
for (int i = 0; i < 5; i++) {
    arr[i] = i * 2;
}
for (int i = 0; i < 5; i++) {
    std::cout << "数组元素 " << i << ":" << arr[i] << std::endl;
}
delete[] arr;

(二)delete操作符

用于释放由new分配的内存空间,并调用析构函数。如果是单个对象,使用delete;如果是数组,使用delete[]

(三) new/delete操作内置类型 

void Test() {
    // 动态申请一个 int 类型的空间
    int* ptr4 = new int;
    // 动态申请一个 int 类型的空间并初始化为 10
    int* ptr5 = new int(10);
    // 动态申请 10 个 int 类型的空间
    int* ptr6 = new int[10];

    delete ptr4;
    delete ptr5;
    delete[] ptr6;
}

❗注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[],注意:匹配起来使用。

(四)new和delete操作自定义类型 

#include <iostream>

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

int main() {
    /* new/delete 和 malloc/free 最大区别是 new/delete 
    对于【自定义类型】除了开空间还会调用构造函数和析构函数*/
    A* p1 = (A*)malloc(sizeof(A));
    A* p2 = new A(1);
    free(p1);
    delete p2;

    // 内置类型是几乎是一样的
    int* p3 = (int*)malloc(sizeof(int));
    int* p4 = new int;
    free(p3);
    delete p4;

    A* p5 = (A*)malloc(sizeof(A) * 10);
    A* p6 = new A[10];
    free(p5);
    delete[] p6;

    return 0;
}

🎒注意:在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与free不会。


💯operator new与operator delete函数(重要点进行讲解) 

 在 C++ 中,可以自定义operator newoperator delete函数来实现自定义的内存分配和释放策略。

(一)operator new函数

原型为void* operator new(size_t size),用于分配指定大小的内存空间。可以重载这个函数以满足特定的内存分配需求。

class CustomAllocator {
public:
    void* operator new(size_t size) {
        void* ptr = malloc(size);
        if (ptr == NULL) {
            throw std::bad_alloc();
        }
        return ptr;
    }
    void operator delete(void* ptr) {
        free(ptr);
    }
};

CustomAllocator *obj = new CustomAllocator();
delete obj;

(二)operator delete函数

原型为void operator delete(void* ptr),用于释放由operator new分配的内存空间。同样可以重载这个函数来实现特定的内存释放策略。


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

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

通过上述两个全局函数的实现知道,operator new 实际也是通过malloc来申请空间,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。operator delete 最终是通过free来释放空间的。


💯newdelete的实现原理

(一)new的实现原理

首先调用operator new函数分配足够的内存空间。然后检查是否分配成功,如果失败则抛出std::bad_alloc异常。如果分配成功,对于单个对象,调用构造函数初始化对象;对于数组,调用每个元素的默认构造函数初始化数组元素。

(二)delete的实现原理

对于单个对象,首先调用对象的析构函数。然后调用operator delete函数释放内存空间。对于数组,先调用每个元素的析构函数,然后释放内存空间。


💯定位new表达式 (placement - new)

定位new表达式允许在已分配的内存空间上构造对象,而无需重新分配内存。

(一)使用方法

首先使用malloc或其他方式分配一块内存空间。

void *buffer = malloc(sizeof(MyClass));
MyClass *obj = new (buffer) MyClass(33);
std::cout << "定位 new 对象的数据:" << obj->getData() << std::endl;
obj->~MyClass();
free(buffer);

(二)注意事项

当使用定位new表达式构造对象后,必须显式调用对象的析构函数来销毁对象,而不能直接使用delete,因为内存不是通过operator new分配的。定位new表达式通常用于特定的场景,如内存池管理、对象的序列化和反序列化等。


💯总结

C 语言和 C++ 语言的动态内存管理方式各有特点。🌠C 语言的malloccallocreallocfree函数提供了基本的内存操作手段。🌠而 C++ 在其基础上,通过newdelete操作符以及可自定义的operator newoperator delete函数,进一步增强了内存管理的灵活性和可控性。定位new表达式则为特定场景下的内存管理提供了独特的解决方案。

🚩掌握这些动态内存管理技术,有助于程序员编写出更加高效、稳定的程序。🚩


以后我将深入研究继承、多态、模板等特性,并将默认成员函数与这些特性结合,以解决更复杂编程问题!欢迎关注我👉【A Charmer】 

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

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

相关文章

Java 二分查找算法详解及通用实现模板案例示范

1. 引言 二分查找&#xff08;Binary Search&#xff09;是一种常见的搜索算法&#xff0c;专门用于在有序数组或列表中查找元素的位置。它通过每次将搜索空间缩小一半&#xff0c;从而极大地提高了查找效率。相比于线性查找算法&#xff0c;二分查找的时间复杂度为 O(log n)&…

利用Docker搭建一套Mycat2+MySQL8一主一从、读写分离的最简单集群(保姆教程)

文章目录 1、Mycat介绍1.1、mycat简介1.2、mycat重要概念1.3、Mycat1.x与Mycat2功能对比1.2、主从复制原理 2、前提准备3、集群规划4、安装和配置mysql主从复制4.1、master节点安装mysql8容器4.2、slave节点安装mysql8容器4.2、配置主从复制4.3、测试主从复制配置 5、安装mycat…

【TDA】持续同调的矢量化方法

Topological deep learning: a review of an emerging paradigm 持续同调与深度学习协同下的3D点云分类_苏潇 Applications of Topology to Data Analysis Computational Topology: An Introduction 持续同调对于特征的方式有条形码PB和持续图表PD两种形式,它们包含了持续同调的…

Qt开发技巧(十八):新窗口控件用智能指针,将一些配置类变量封装起来,Qt窗体的Z序叠放,子窗体的释放,Qt中的事件发送,Qt的全局头文件

继续讲一些Qt开发中的技巧操作&#xff1a; 1.新窗口控件用智能指针 通过对Qt自带Examples的源码研究你会发现&#xff0c;越往后的版本&#xff0c;越喜欢用智能指针QScopedPointer来定义对象&#xff0c;这样有个好处就是用的地方只管new就行&#xff0c;一直new下去&#xf…

2025 年最佳的 Retool 开源替代方案

自 2017 年推出以来&#xff0c;Retool 已迅速成为开发者的热门选择。 Retool 的出现&#xff0c;填补了当时企业在快速构建内部工具上的空白。传统的应用开发往往需要耗费大量时间和资源&#xff0c;尤其是对于定制的内部业务应用。而 Retool 提供了一个灵活的平台&#xff0…

element设置时间和日期框早于现在的时间和日期禁用

效果: 今日此时此刻之前的日期、时间禁止选用&#xff0c;切换日期和时间为“2024-10-19 00:00:00"&#xff0c;再切换为”2024-10-18 00:00:00"时&#xff0c; 会给form.time默认赋值为今日此时此刻&#xff08;日期时间少于今日此时此刻则重新赋值&#xff09; 安…

datax连接池泄漏问题排查及解决

1、问题描述 频繁调用datax服务&#xff08;从oracle同步到mysql&#xff09;出现报错&#xff0c;获取不到连接 oracle读取时报错信息 "errorMessage": "Code:[DBUtilErrorCode-10], Description:[连接数据库失败. 请检查您的 账号、密码、数据库名称、IP、…

print_hex_dump调试内核,嘎嘎香

本文首发于我的公众号 皮塞作坊 专注于干货分享&#xff0c;号欢迎大家关注,二维码文末可以扫。 公众号: 使用print_hex_dump调试内核/驱动&#xff0c;太香了 最近在验证芯片功能的过程中发现了一个好用的内核调试接口&#xff0c;print_hex_dump&#xff0c;除了直接打印16…

【AIGC】关键词智能匹配:AI驱动的RAG知识库检索技术全解析

随着大语言模型的快速发展&#xff0c;AI在知识获取和生成中的应用越发广泛。RAG&#xff08;Retrieval-Augmented Generation&#xff09;模型通过结合外部知识库&#xff0c;提升了生成文本的质量与准确性&#xff0c;而关键词搜索是其关键组成部分。本文将深入探讨AI如何通过…

【java】数组(超详细总结)

目录 一.一维数组的定义 1.创建数组 2.初始化数组 二.数组的使用 1.访问数组 2.遍历数组 3.修改数据内容 三.有关数组方法的使用 1.toString 2. copyOf 四.查找数组中的元素 1.顺序查找 2.二分查找binarySearch 五.数组排序 1.冒泡排序 2.排序方法sort 六.数组逆置…

LabVIEW伺服压机是如何实现压力位移的精度?

LabVIEW伺服压机通过精确的压力和位移控制&#xff0c;实现了高精度的压装操作。为了达到这种精度&#xff0c;系统通常依赖于多个硬件和软件模块的协同工作&#xff0c;包括伺服电机、压力传感器、位移传感器以及LabVIEW的实时控制和数据处理功能。以下是LabVIEW伺服压机如何实…

Linux修改npm的镜像源为淘宝镜像

起因&#xff1a;使用官方镜像源下载软件包速度太慢 1.查看npm当前镜像源命令 npm get registry 执行结果 2.还原为官方镜像源命令 npm config set registry https://registry.npmjs.org/ 3.修改为淘宝镜像命令 npm config set registry https://registry.npmmirror.com …

【你也能从零基础学会网站开发】 SQL Server结构化查询语言数据操作应用--DML篇 delete语句数据删除操作的使用方法

&#x1f680; 个人主页 极客小俊 ✍&#x1f3fb; 作者简介&#xff1a;程序猿、设计师、技术分享 &#x1f40b; 希望大家多多支持, 我们一起学习和进步&#xff01; &#x1f3c5; 欢迎评论 ❤️点赞&#x1f4ac;评论 &#x1f4c2;收藏 &#x1f4c2;加关注 delete介绍与语…

关于武汉芯景科技有限公司的限流开关芯片XJ6288开发指南(兼容SY6288)

一、芯片引脚介绍 1.芯片引脚 二、系统结构图 三、功能描述 1.EN引脚控制IN和OUT引脚的通断 2.OCB引脚指示状态 3.过流自动断开

NC 单据模板自定义项 设置参照,比如部门参照、自定义参照等

NC 单据模板自定义项 设置参照&#xff08;自定义参照&#xff09; 一、如图下图&#xff0c;NC 单据模板自定义项 设置自定义参照&#xff1a; 1、选择需要设置参照的自定义字段&#xff0c;选择高级属性页签&#xff0c;在类型设置中&#xff0c;数据类型选择参照信息&#…

使用JUC包的AtomicXxxFieldUpdater实现更新的原子性

写在前面 本文一起来看下使用JUC包的AtomicXxxxFieldUpdater实现更新的原子性。代码位置如下&#xff1a; 当前有针对int&#xff0c;long&#xff0c;ref三种类型的支持。如果你需要其他类型的支持的话&#xff0c;也可以照葫芦画瓢。 1&#xff1a;例子 1.1&#xff1a;普…

Maven的进阶

目录 一、pom.xml文件 二、坐标 2.1 坐标的概念 2.2 坐标的意义 2.3 坐标的含义 2.4 在IDEA中查看项目的坐标 三、依赖 3.1 依赖的意义 3.2 依赖的使用 3.3 第三方依赖的查找使用方法 3.4 依赖的范围 3.5 依赖传递和可选依赖 3.5.1 依赖传递 3.5.2 依赖范围对传…

【前端】如何制作自己的网站(7)

以下内容接上文。 结合图片的超链接 将img元素作为内容&#xff0c;放在a元素中。即可为图片添加一个超链接。 例如右边的代码&#xff0c;点击头像就会打开“aboutme.html“。 点击右边的图片试试&#xff5e; 两个非文本元素——图片与超链接。 从现在开始&#xff0…

蘑菇书(EasyRL)学习笔记(1)

1、强化学习概述 强化学习&#xff08;reinforcement learning&#xff0c;RL&#xff09;讨论的问题是智能体&#xff08;agent&#xff09;怎么在复杂、不确定的环 境&#xff08;environment&#xff09;里面去最大化它能获得的奖励。如下图所示&#xff0c;强化学习…

【Petri网导论学习笔记】Petri网导论入门学习(七) —— 1.5 并发与冲突

导航 1.5 并发与冲突1.5.1 并发定义 1.14定义 1.15 1.5.2 冲突定义 1.17 1.5.3 一般Petri网系统中的并发与冲突定义 1.18一般网系统中无冲撞概念阻塞&#xff08;有容量函数K的P/T系统&#xff0c;类似于冲撞&#xff09;一般Petri网中并发与冲突共存情况 1.5 并发与冲突 Petr…