【C/C++内存管理】——我与C++的不解之缘(六)

2024-09-07 15:44
文章标签 c++ 内存 管理 不解之缘

本文主要是介绍【C/C++内存管理】——我与C++的不解之缘(六),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

        最近开学了,更新有些迟缓了;

现在来学C/C++中的内存管理

一、C/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、又叫做堆栈 -- 非静态局部变量、函数参数和返回值等,栈是向下增长的。

2、用于程序运行时动态内存分配,堆是向上增长的。

3、数据段(静态区) -- 存储全局数据和静态数据。

4、代码段(常量区) -- 可执行的代码/只读常量(比如,上述的常量字符串"abcd")。

5、内存映射段 是高效的 I/O映射方式,用于装载一个共享的动态内存库,用户可以使用接口创建共享内存,做进程间通信(后面再学习这一块的知识)。

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

        在C语言中,学习过C语言的动态内存管理方式:malloc /calloc /realloc /free

这里就不过多的讲解,如有遗忘就去重温一下C语言——动态内存管理

三、C++中内存管理方式

        由于C++是兼容C语言的,所以C语言的内存管理方式在C++当中也可以继续使用;但是,在一些方面,C语言的malloc /calloc /realloc /free使用起来就比较麻烦;

        因此C++提出了自己的内存管理方式:通过 new和 delete 操作符进行动态内存管理

        3.1、new / delete 操作内置类型

void test()
{//动态申请一个int大小的空间int* p1 = new int;//动态申请一个int大小的空间,并初始化为520;int* p2 = new int(520);//动态申请十个int大小的空间(数组)int* p3 = new int[10];//动态申请十个int大小的空间(数组),并初始化int* p4 = new int[10] {1, 2, 3, 4, 5, 6, 7, 8, 9};delete p1;delete p2;//释放数组(多个内置类型)delete[] p3;delete[] p4;
}

注意:

        申请和释放单个元素的空间,使用new和delete操作符;申请和释放连续的空间,使用new[ ]和delete[ ]。(配套使用)

        3.2、new和delete操作自定义类型

        对于内置类型,new/delete 和malloc/free 差别不是很大;

        而对于自定义类型,最大的区别就是,new和delete除了会开辟空间还会调用自定义类型的构造函数和析构函数。

class A
{
public:A(int a = 1):_a(a){std::cout << "A()" << std::endl;}~A(){std::cout << "~A()" << std::endl;}
private:int _a;
};int main()
{A* p1 = (A*)malloc(sizeof(A));free(p1);A* p2 = new A;delete p2;return 0;
}

      可以看到,malloc和free并没有调用构造函数和析构函数;

这里,new和delete调用了自定义类型的构造函数和析构函数。

四、operator new与operator delete函数

        4.1、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 bytesvoid* 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_FINALLYreturn;
}
/*
free的实现
*/
#define free(p) _free_dbg(p, _NORMAL_BLOCK)

        以上是库里面的源代码,可以大致看出operator new 实际上也是通过malloc来申请空间,如果申请成功,就直接返回;如果失败就执行用户通过的空间不足应对措施,如果用户没有提供就抛异常 operator delete 最终是通过free来释放空间的。

        4.2、抛异常

        这里简单使用一下抛异常和捕获异常(抛异常也是new和malloc的区别之一)

先看一下代码,这里在x86(32位)环境下动态开辟空间,直到申请失败抛异常

#include<iostream>
int main()
{int* p;do{p = new int[1024 * 1024];std::cout << p << std::endl;} while (p);return 0;
}

这里提示未经处理的异常,说明new动态申请空间失败了,抛异常。现在使用try catch捕获异常并输出。

#include<iostream>
#include<exception>
int main()
{try{int* p;do{p = new int[1024 * 1024];std::cout << p << std::endl;} while (p);}catch (const std::exception& e){std::cout << e.what() << std::endl;}return 0;
}

五、new 和delete 的实现原理

        5.1、内置类型

        如果动态申请内置类型的空间,new和malloc、delete和free 基本相似;

不同的是:

1、new/delete申请和释放的是单个元素的空间,new[ ] 和delete[ ] 申请和释放的是连续的空间。

2、new在申请空间失败会抛异常,而malloc会返回NULL。        

        5.2、自定义类型

5.2.1、new的原理

1、先调用operator new 函数申请空间。

2、在申请的空间上调用自定义类型的构造函数,完成对象的构造。

5.2.2、delete的原理

1、在空间上调用自定义类型的析构函数,完成对象中资源的清理

2、调用operator delete 函数释放空间。

5.2.3、new T[N] 的原理

1、调用operator new[ ]函数,在operator中实际调用了operator new 完成N个对象的申请

2、在申请的空间上调用N次构造函数。

5.2.4、delete[ ] 的原理

1、在空间上调用N次析构函数,完成N个对象中资源的清理。

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

六、定位 new 表达式(placement - new)

        所谓定位new 表达式是在已经申请好的内存空间中调用构造函数初始化一个对象。

使用方法:

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

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

使用场景:

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

#include<iostream>
class A
{
public:A(int a = 1):_a(a){std::cout << "A()" << std::endl;}~A(){std::cout << "~A()" << std::endl;}
private:int _a;
};
int main()
{//动态申请空间,malloc不会初始化A* p1 = (A*)malloc(sizeof(A));//new表达式,显示调用构造函数new(p1)A(99);//显示调用析构函数p1->~A();//释放空间free(p1);return 0;
}

七、malloc/free 和 new/dalete的区别

        首先:malloc/free 和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在释放空间前会调用析构函数完成空间中资源的清理。

八、内存泄露

        在C语言内存管理中,提到过内存泄露,这里简单理解一下,在之后学习中会深入学习内存泄露以及避免内存泄露。

        内存泄露:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

        内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。

感谢各位大佬支持并指出问题,

                        如果本篇内容对你有帮助,可以一键三连支持以下,感谢支持!!!

这篇关于【C/C++内存管理】——我与C++的不解之缘(六)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1145474

相关文章

Python如何使用__slots__实现节省内存和性能优化

《Python如何使用__slots__实现节省内存和性能优化》你有想过,一个小小的__slots__能让你的Python类内存消耗直接减半吗,没错,今天咱们要聊的就是这个让人眼前一亮的技巧,感兴趣的... 目录背景:内存吃得满满的类__slots__:你的内存管理小助手举个大概的例子:看看效果如何?1.

C++ 中的 if-constexpr语法和作用

《C++中的if-constexpr语法和作用》if-constexpr语法是C++17引入的新语法特性,也被称为常量if表达式或静态if(staticif),:本文主要介绍C++中的if-c... 目录1 if-constexpr 语法1.1 基本语法1.2 扩展说明1.2.1 条件表达式1.2.2 fa

C++中::SHCreateDirectoryEx函数使用方法

《C++中::SHCreateDirectoryEx函数使用方法》::SHCreateDirectoryEx用于创建多级目录,类似于mkdir-p命令,本文主要介绍了C++中::SHCreateDir... 目录1. 函数原型与依赖项2. 基本使用示例示例 1:创建单层目录示例 2:创建多级目录3. 关键注

C++从序列容器中删除元素的四种方法

《C++从序列容器中删除元素的四种方法》删除元素的方法在序列容器和关联容器之间是非常不同的,在序列容器中,vector和string是最常用的,但这里也会介绍deque和list以供全面了解,尽管在一... 目录一、简介二、移除给定位置的元素三、移除与某个值相等的元素3.1、序列容器vector、deque

C++常见容器获取头元素的方法大全

《C++常见容器获取头元素的方法大全》在C++编程中,容器是存储和管理数据集合的重要工具,不同的容器提供了不同的接口来访问和操作其中的元素,获取容器的头元素(即第一个元素)是常见的操作之一,本文将详细... 目录一、std::vector二、std::list三、std::deque四、std::forwa

C++字符串提取和分割的多种方法

《C++字符串提取和分割的多种方法》在C++编程中,字符串处理是一个常见的任务,尤其是在需要从字符串中提取特定数据时,本文将详细探讨如何使用C++标准库中的工具来提取和分割字符串,并分析不同方法的适用... 目录1. 字符串提取的基本方法1.1 使用 std::istringstream 和 >> 操作符示

C++原地删除有序数组重复项的N种方法

《C++原地删除有序数组重复项的N种方法》给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度,不要使用额外的数组空间,你必须在原地修改输入数组并在使用O(... 目录一、问题二、问题分析三、算法实现四、问题变体:最多保留两次五、分析和代码实现5.1、问题分析5.

C++ 各种map特点对比分析

《C++各种map特点对比分析》文章比较了C++中不同类型的map(如std::map,std::unordered_map,std::multimap,std::unordered_multima... 目录特点比较C++ 示例代码 ​​​​​​代码解释特点比较1. std::map底层实现:基于红黑

C++中函数模板与类模板的简单使用及区别介绍

《C++中函数模板与类模板的简单使用及区别介绍》这篇文章介绍了C++中的模板机制,包括函数模板和类模板的概念、语法和实际应用,函数模板通过类型参数实现泛型操作,而类模板允许创建可处理多种数据类型的类,... 目录一、函数模板定义语法真实示例二、类模板三、关键区别四、注意事项 ‌在C++中,模板是实现泛型编程

nvm如何切换与管理node版本

《nvm如何切换与管理node版本》:本文主要介绍nvm如何切换与管理node版本问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录nvm切换与管理node版本nvm安装nvm常用命令总结nvm切换与管理node版本nvm适用于多项目同时开发,然后项目适配no