C/C++中的内存管理小结

前言

我们最初熟知的内存开辟方式:

  • int val = 20: 在栈空间上开辟4个字节
  • char array[10]: 在栈空间上开辟10个字节的连续空间

上述开辟空间的方式有两个特点:

  • 空间开辟大小是固定的。
  • 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。

但是对于空间的需求,不仅仅是上述的情况,有时候我们需要的空大小在程序运行时才能知道,那此时静态的开辟空间的方式就不能满足了,我们这时候只能试试动态内存开辟。

这篇博客就来带大家梳理一下C/C++中的内存管理。

一:C/C++内存分布

对内存分段是计算机的管理机制

1.栈又叫堆栈,存放非静态局部变量、函数参数和返回值等等,栈是向下增长的。,处理器的指令集中、效率高,但是分配内存的容量有限。(函数执行结束后这些存储单元自动释放)
2.内存映射段是高效的IO映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。
3.堆用于程序运行时动态内存分配,堆是向上增长的。(一般由人为分配释放,若没有人为释放则程序结束时可能由OS回收。)
4.数据段存储全局数据、静态数据。(程序结束后由系统自动释放)
5.代码段存储可执行的代码、只读常量。

注意:
栈区向下生长,先开辟的空间地址大于后开辟的空间地址。(int a = 10,int b = 20,&a>&b)
堆区向上生长,但是不保证后开辟的空间地址大于先开辟的空间地址,因为堆区存在人为的空间释放。

二:C语言中的内存管理方式

C语言提供了动态内存函数来进行内存的动态开辟工作:malloc、calloc、realloc、free

2.1 malloc

函数功能

void✳ malloc(size_t size以字节为单位的空间大小)

举个栗子:int* ptr = (int*) malloc(sizeof(int)*10);

malloc向内存申请一块大小为size的连续可用空间,并返回指向这块空间的指针。

函数特性
1.开辟成功,返回一个指向该空间的指针。
2.开辟失败,返回一个NULL指针,因此malloc的返回值一定要做检查。
3.返回值的类型是void✳,malloc函数并不知道开辟空间的数据类型,具体在使用的时候由使用者自己决定。
4.如果参数size为0,malloc的行为是标准未定义的,取决于编译器。

2.2 calloc

函数功能

void✳ calloc(size_t num元素个数,size_t size以字节为单位的空间大小)

举个栗子:int* ptr = calloc(10,sizeof(int));

calloc向内存为num个大小为size的元素开辟一块连续空间,并且把空间的每个字节都初始化为0。

函数特性
1.开辟成功,返回一个指向该空间的指针。
2.开辟失败,返回一个NULL指针,因此calloc的返回值一定要做检查。
3.返回值的类型是void✳,calloc函数并不知道开辟空间的数据类型,具体在使用的时候由使用者自己决定。
4.calloc会在返回地址之前把申请的空间每个字节都初始化为0(calloc适用于对申请空间的内容要求初始化的情况)

注意:对申请的空间初始化并不完全是好的事情,当我们要申请一个特别大的空间时,初始化会浪费很多很多的时间。

2.3 realloc

函数功能

void✳ realloc(void✳ ptr要调整的内存地址,size_t size调整之后的空间大小)

举个栗子:int* p = NULL; p = realloc(ptr,1000); if(p!=NULL)-> ptr = p;

realloc可以对动态开辟的内存空间大小进行灵活调整。

函数特性

1.返回值为调整之后内存空间的起始位置。
2.realloc在调整原内存空间大小的基础上,还会将原内存空间中的数据移动到新的空间。

realloc在调整内存空间时存在的两种情况
情况一:原有空间之后有足够大的空间

直接在原有内存空间之后追加空间,原来空间的数据不发生变化

情况二:原有空间之后没有足够大的空间

在堆空间上重新找一块合适大小的连续空间来使用,这样函数返回的是一个新的内存地址。

常见的动态内存错误

1、对NULL指针的解引用操作。

2、对动态开辟空间越界访问。

3、对非动态内存使用free释放。

4、释放一块动态开辟内存的一部分。

5、对同一块内存多次释放。

6、动态开辟内存忘记释放。

以上的错误都是十分常见的,因此我们在对内存进行操作的时候一定要万分小心。

典型内存泄漏的例子

int main(){
 int* p = (int*)malloc(sizeof(int));
 p = (int*)malloc(sizeof(int));
 free(p);
 p = NULL;
}

这个例子中我们明明进行了释放却也造成了内存泄漏,这是因为我们申请了两次内存空间,但是用同一个指针来接收,只释放了一次,因此造成了内存的泄漏。

进行动态的内存分配后一定不能忘记在使用完毕后将内存空间释放,并且将指针赋值为NULL,这一点是十分关键的,否则将造成内存泄漏和野指针,对程序造成很大的影响。

三:C++中的内存管理方式

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

在C++中我们使用new进行内存的申请,用delete进行内存的释放。

3.1 内置类型的内存分配与释放

new和malloc一样会在堆上开辟空间同时需要我们手动进行内存的释放,但是new的写法更加简单易于理解同时我们还可以对单个申请的变量进行初始化。

举个栗子帮助理解

#include <iostream>
using namespace std;
int main(){
 int* a = new int;//等同于int* a = (int*)malloc(sizeof(int));
 int* b = new int[10];//等同于int* b = (int*)malloc(sizeof(int) * 10);
 int* c = new int(10);//new还可以进行内置类型的初始化
 cout << *c << endl;

 delete a;//等同于free(a);
 delete[] b;//等同于free(b);(对于多个变量的空间释放要用delete[])
 delete c;//等同于free(c);

 return 0;
}

3.2 自定义类型的内存分配和释放

针对自定义类型的内存分配和释放,new不但可以在分配内存的时候手动调用指定的构造函数还会在分配多个对象的空间时自动调用默认构造函数,delete也会自动调用析构函数,而malloc和free却做不到这一点。因此可以理解为malloc和free分配出来的只不过是一个和类一样大小的空间,并不能称作是一个对象,而new和delete分配出来的才能被成为对象。

#include <iostream>
#include <stdlib.h>
using namespace std;
class Stu{
public:
 Stu(){
   cout << "default building" << endl;
  }

  Stu(int num, string name):_num(num), _name(name){
   cout << "custom building" << endl;
  }

  ~Stu(){
   cout << "destroying" << endl;
  }
private:
 int _num;
 string _name;
};

int main(){
 cout << "malloc:" << endl;
 Stu* a = (Stu*)malloc(sizeof(Stu));
 cout << "new:" << endl;
 Stu* b = new Stu(1, "张三");
 cout << "malloc:" << endl;
 Stu* c = (Stu*)malloc(sizeof(Stu) * 5);
 cout << "new:" << endl;
 Stu* d = new Stu[5];
 cout << "free:" << endl;
 free(a);
 cout << "delete:" << endl;
 delete b;
 cout << "free:" << endl;
 free(c);
 cout << "delete:" << endl;
 delete[] d;
}       

运行结果:

malloc:
 new:
 custom building
 malloc:
 new:
 default building
 default building
 default building
 default building
 default building
 free:
 delete:
 destroying
 free:
 delete:
 destroying
 destroying
 destroying
 destroying
 destroying

3.3 new和delete的实现原理

new和delete在C++中其实被定义为两个运算符,我们在使用这两个运算符的时候它会在底层调用全局函数operator new和operator delete。

operator new

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

operator delete

operator delete在底层实现的源代码

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

从源码中能看出的是operator new和operator delete在底层也是利用malloc和free分配内存的,因此可以说new和delete不过是malloc和free的一层封装。

针对内置类型

如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL。

针对自定义类型

1.new的原理: 调用operator new申请空间,调用构造函数完成初始化。
2.delete的原理: 调用析构函数完成清理,调用operator delete释放空间。

四:经典面试题

new | delete和malloc | free的相同点和不同点

相同点:
new、delete、malloc、free都是从堆上开辟空间,并且需要用户手动释放。

不同点:
1.new和delete是操作符,malloc和free是函数。

2.malloc申请空间不会进行初始化,new申请空间可以初始化。

3.malloc申请空间失败返回NULL,new申请空间失败会抛出异常。

4.针对自定义类型,new和delete会自动调用构造函数和析构函数处理。

五:内存泄漏

概念:内存泄漏指因为疏忽或错误造成程序已经不再使用的内存没有被释放的情况。

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

举个栗子帮助理解:

void MemoryLeaks(){
 // 1.内存申请了忘记释放
 int* p1 = (int*)malloc(sizeof(int));
 int* p2 = new int;

 // 2.异常安全问题
 int* p3 = new int[10];
 Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放.
 delete[] p3;
}

5.1 内存泄漏的分类

堆内存泄漏

程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,
用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生堆内存泄漏。

系统资源泄漏

程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统
资源的浪费,严重可导致系统效能减少,系统执行不稳定,产生了系统资源泄露。

5.2 如何检测内存泄露

在linux下内存泄漏检测
valgrind、mtrace、dmalloc、memwatch、mpatrol、dbgmem、Electric Fence

在windows下内存泄漏检测
VLD

5.3 如何避免内存泄漏

1.工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。

2.采用RAII思想或者智能指针来管理资源。

5.4 如何在堆上一次申请4G空间

原因:申请失败一般是因为进程地址空间不够大。

解决办法:换用64位的进程地址空间。

到此这篇关于C/C++中的内存管理小结的文章就介绍到这了,更多相关C++ 内存管理内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 老生常谈C/C++内存管理

    内存分配方式 简介 在C++中,内存分成5个区,他们分别是堆.栈.自由存储区.全局/静态存储区和常量存储区. 栈:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放.栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限. 堆:就是那些由 new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个 delete.如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收. 自由存储区:就是那些由mall

  • 养成良好的C++编程习惯之内存管理的应用详解

    开篇导读    虽然本系列文章定位为科普读物,但本座相信它们不但适合新手们学习借鉴,同时也能引发老鸟们的反思与共鸣.欢迎大家提出宝贵的意见和反馈 ^_^ 在开篇讲述本章主要内容之前,本座首先用小小篇幅论述一下一种良好的工作习惯 -- 积累.提炼与求精.在工作和学习的过程中,不断把学到的知识通过有效的方式积累起来,形成自己的知识库,随着知识量的扩大,就会得到从量变到质变的提升.另外还要不断地对知识进行提炼,随着自己知识面的扩大以及水平的提升,你肯定会发现原有知识库存在着一些片面.局限.笨拙甚至错误

  • C/C++中的内存管理小结

    前言 我们最初熟知的内存开辟方式: int val = 20: 在栈空间上开辟4个字节 char array[10]: 在栈空间上开辟10个字节的连续空间 上述开辟空间的方式有两个特点: 空间开辟大小是固定的. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配. 但是对于空间的需求,不仅仅是上述的情况,有时候我们需要的空大小在程序运行时才能知道,那此时静态的开辟空间的方式就不能满足了,我们这时候只能试试动态内存开辟. 这篇博客就来带大家梳理一下C/C++中的内存管理. 一:C/C

  • 模拟实现C语言中的内存管理

    这里模拟了C语言中的内存管理,当我们要创建或者使用一个对象时,那么这个对象会调用retain方法,计数+1,当我们要释放对象,我们会调用free,这里注意要对计数记性判断,如果是0的话,那么就会销毁. #import <Foundation/Foundation.h> int cnt = 0; void fun (charchar * p) { printf("%c\n",p[0]); } charchar * retain1(charchar * p) { //retai

  • 简述iOS属性中的内存管理参数

    一,assign 代表设置时候直接赋值,而不是复制或者保留它. 二,retain. 会在赋值的时候把新值保留.此属性只能用于Object-C对象类型. 三,copy 在赋值时,将新值复制一份,复制工作由copy执行,此属性只对那些实行了NSCopying协议的对象类型有效. 总结 以上所述是小编给大家介绍的iOS属性中的内存管理参数 ,希望对大家有所帮助,如果大家有任何疑问欢迎给我留言,小编会及时回复大家的! 您可能感兴趣的文章: 详解关于iOS内存管理的规则思考 详解iOS应用开发中的ARC内

  • Python中的内存管理之python list内存使用详解

    前言 使用 Python 的时候,我们知道 list 是一个长度可变对的数组, 可以通过 insert,append 和 extend 轻易的拓展其中的元素个数. 也可以使用运算符 如: [1] + [2] 生成新的数组[1, 2] extend(). "+"."+=" 的区别 "+"将两个 list 相加,会返回到一个新的 list 对象 append 在原 list 上进行修改,没有返回值 从以下代码可以看到, 调用 b = b + [3,

  • C语言中的内存管理详情

    目录 1.malloc 2.内存泄露 3.内存池 4.理论 5.代码数据结构 6.代码 7.blk->begin 8.总结 内容提要: 大家写C程序时,手工申请过内存吗?每次需要存储空间时都向操作系统申请吗?使用完申请到的内存后有把它还给操作系统吗?遇到过“段错误”吗?本文的主题和这一串问题有很大的关系. 1.malloc 手工申请内存使用malloc.先看一段例程. #include <stdio.h> #include <string.h> #include <st

  • C语言中动态内存管理图文详解

    目录 1.动态内存开辟的原因 2.动态内存函数的介绍 2.1malloc和free 2.2calloc 2.3realloc 3.常见的动态内存错误 3.1对NULL指针的解引用操作 3.2对动态开辟空间的越界访问 3.3对非动态开辟内存使用free 3.4使用释放一块动态开辟内存的一部分 3.5对同一块动态内存多次释放 3.6动态开辟内存忘记释放(内存泄漏) 4.练习 4.1练习1 4.1练习2 4.3练习3 4.4练习4 5.C/C++程序的内存开辟 总结 1.动态内存开辟的原因 常见的内存

  • 详解C语言中动态内存管理及柔性数组的使用

    目录 一.malloc 二.free 三.calloc 四.realloc 1.realloc在扩容时的情况 2.realloc也能实现malloc功能 五.使用动态内存的常见错误 1.free空指针 2.对动态开辟的空间越界访问 3.对非动态开辟内容free 4.只free动态开辟空间的一部分 5.对同一块内存多次free 6.动态内存空间忘记释放(内存泄漏) 六.柔性数组 1.柔性数组的概念 2.柔性数组的特点 3.柔性数组的使用场景 4.柔性数组的优点 一.malloc 这个函数向堆区申请

  • 一文详解C++中动态内存管理

    目录 前言 1.C/C++程序的内存开辟 2.C语言中动态内存管理方式:malloc/calloc/realloc/free 2.1malloc.calloc.realloc区别? 3.C++内存管理方式 3.1 new/delete操作内置类型 3.2 new和delete操作自定义类型 3.3new和malloc处理失败 4.operator new与operator delete函数 4.1 operator new与operator delete函数 4.1.1 我们看看operator

  • 解析PHP中的内存管理,PHP动态分配和释放内存

    摘要 内存管理对于长期运行的程序,例如服务器守护程序,是相当重要的影响:因此,理解PHP是如何分配与释放内存的对于创建这类程序极为重要.本文将重点探讨PHP的内存管理问题. 一. 内存在PHP中,填充一个字符串变量相当简单,这只需要一个语句"<?php $str = 'hello world '; ?>"即可,并且该字符串能够被自由地修改.拷贝和移动.而在C语言中,尽管你能够编写例如"char *str = "hello world ";&qu

  • 深入探讨PHP中的内存管理问题

    一. 内存 在PHP中,填充一个字符串变量相当简单,这只需要一个语句"<?php $str = 'hello world '; ?>"即可,并且该字符串能够被自由地修改.拷贝和移动.而在C语言中,尽管你能够编写例如"char *str = "hello world ";"这样的一个简单的静态字符串:但是,却不能修改该字符串,因为它生存于程序空间内.为了创建一个可操纵的字符串,你必须分配一个内存块,并且通过一个函数(例如strdup()

随机推荐