关于C++的动态内存分配

为什么要使用动态内存分配?

比较常见的情况是当我们使用一个数组时,我们需要去声明它,同时我们还需要提供给它一个编译时常量用于指定数组的长度。但是,我们有时候需要的数组并不是定长的。例如,我们要存储一个班级所有学生的数据,但是不同班的学生数量可能是不同的,我们虽然可以声明一个尽量大的数组来存储,但是这样会造成资源的浪费,而且也不利于扩展使用。所以,我们需要动态地去分配一个数组的空间。

malloc和free

在c函数库中有两个函数,malloc和free,在c++中同样是存在的,它们分别用于执行动态内存的分配和释放。这些函数维护一个可用内存池,当一个程序另外一些内存时,就调用malloc函数,malloc从内存池中提取一块合适的内存,并向该程序返回一个指向这块内存的指针。这块内存池并没有以任何形式进行初始化,如果需要被初始化,需要你手动进行初始化或者使用calloc。当此内存不再使用,便调用free函数把它归还给内存池。
函数原型如下:

void *malloc(size_t size);
void *free(void *pointer);

malloc函数的参数就是需要分配的内存字节数,如果内存池中的可用内存可以满足这个需求,则返回一个指向被分配的内存块起始位置的指针。malloc所分配的是一块连续的内存,而且实际分配的内存有可能会比你要求的多一些。
如果内存池的可用内存不满足程序的要求,那么malloc就会向操作系统请求,要求得到更多的内存,并在新内存上执行分配任务。如果操作系统无法提供更多的内存,malloc则返回一个NULL指针。
free函数的参数要么是一个NULL,要么是先前从malloc,calloc,realloc返回的值。(而不能是一个new得到的值),那么这里就有一个问题了,malloc函数指定了size,所以能知道要分配多少内存,free函数没有size,是如何知道它需要释放的内存容量的?其实在每一个内存块的首部都维护着一个mem_control_block(内存控制块),它是一个结构体,形式如下:

struct mem_control_block {
    //is_available指明此内存块是否可以被使用,1为可以,0为不可以
    int is_available;
    //指明当前内存块的大小
    int size;
};

在使用malloc分配内存时,如果malloc函数内部得到的内存区域的首地址为void *p,那么它返回给你的就是p + sizeof(mem_control_block),偏移到真正的内存块开始的位置,而当我们调用free的时候,则将p - sizeof(men_control_block),回到mem_control_block的头部,这里用*q代替,然后使用q->size,程序就能知道需要释放的内存是多少了。形如下图:
这里写图片描述
另外需要注意的就是free函数释放的是指针所指向的内存块,而不是指针本身,所以我们在释放了内存块之后,还需要将指针指向NULL,否则,就会出现野指针的现象。

calloc和realloc

在C中还有另外两个内存分配函数,calloc和realloc,它们的原型如下:
void *calloc(size_t num_elements,size_t element_size);
void *realloc(void *ptr, size_t new_size);
calloc也用于分配内存,malloc和calloc之间的区别就在于后者在返回指向内存的指针之前把它初始化为0,另外它们之间请求内存的方式也不一样,calloc的参数是所需的元素个数,和每个元素的字节数,根据这个计算出所需的总内存数,然后再去申请。
realloc用于修改一个原先已经分配的内存块的大小,使用这个函数,可以扩大或缩小一个内存块。如果使用它来扩大一个内存块,那么这个内存块原先的内容依然保留,新增加的内存添加到原先内存块的后面,新内存是未被初始化的。如果它被用于缩小一个内存块,则该内存块尾部的部分内存被拿掉,剩余部分内存的原先内容依然保留。
如果原先的内存块无法改变大小,realloc将分配另一块正确大小的内存,并把原先那块内存的内容复制到新的块上。因此,在使用realloc之后,你就不能再使用指向旧内存的指针,而是应该改用realloc所返回的新指针。
而如果,realloc的第一个参数是NULL,那么它的行为就和malloc一摸一样了。

new和delete

new和delete是C++中动态内存分配的操作符,在我们需要对一些类对象动态地分配内存空间和释放空间的时候,malloc和free就有些不够看了。因为我们为类对象动态分配空间的时候,同时需要调用类对象的构造方法对其进行初始化,释放空间的时候,同样要调用类对象的析构函数,所以我们就需要使用new和delete来做这些工作。
讲到new操作符,就经常牵涉到operator new 和new operator的话题,这两个是什么呢?例如:

int *p = new int;

这样一句简单的代码,我们看到的这个new就是new operator,然而在这句代码的背后,执行的步骤简单描述如下:
1. 为对象分配内存空间;
2. 调用对象的构造函数初始化内存空间;
在第一步中分配内存的部门就使用了operator new,详细的可以参考:c++ 中new 操作符是怎么实现的

new与malloc的区别?

  1. new是一个操作符,而malloc是一个库函数;
  2. new能够自动计算需要分配的内存空间,而malloc需要手工计算字节数;
  3. new计算空间是通过数据类型,而malloc通过字节大小;
  4. new和delete带具有具体类型的指针,而malloc和free则是void *;
  5. new是类型安全的,但是malloc不是,例如int *p = new float[2];这样编译器会报错,但是int *p = malloc(2*sizeof(int));这样是不会报错的;
  6. new操作符可以重载,而malloc不可以;
  7. new可以调用构造函数,但是malloc不可以;

从上面我们可以看出,new操作符的作用要比malloc的作用强大,而且malloc有的功能,使用new也可以做到,那么我们为什么还需要保留malloc呢?答案是在C++中,经常需要外部链接调用C的库,而C中管理动态内存需要使用malloc,所以我们就需要保留下malloc。

内存泄漏以及内存溢出?

什么是内存泄露?
内存泄漏是指内存被动态分配以后,当它不再使用时未被释放。内存泄漏会增加程序的体积,有可能导致程序或系统的奔溃。另外,内存泄漏还有可能导致内存不足的问题。所以,每次动态分配了内存空间的时候,当内存不再需要使用时,都要对其进行释放。
什么是内存溢出?
内存溢出简单理解就是你给一个程序的内存空间(或者你本身所拥有的内存)小于该程序运行现状所需要的内存空间,而且你又没有对这种情况做好处理措施,程序就会操作到本不应该被操作到的内存空间,这样的情况就是溢出现象。这种现象可能会被黑客利用攻击。
更详尽的可以参考:内存泄漏和内存溢出有啥区别?

总结

  1. 在使用malloc函数动态分配内存时,需要检查函数返回的指针是否为NULL;
  2. malloc和free,new和delete要成对使用;
  3. 在动态分配的内存空间不再使用时,要释放该空间;
  4. 释放空间之后,要将指向该内存空间的指针指向NULL,防止出现野指针的情况;
  5. 不要重复释放一块内存,否则可能会破坏自由空间;

参考

  1. https://segmentfault.com/q/1010000000160483
  2. https://www.zhihu.com/question/40560123
  3. 《Effective C++》,《C和指针》,《C++ Primer 第5版》

猜你喜欢

转载自blog.csdn.net/weixin_41713281/article/details/79838286