动态内存管理

前言:

  通常我们使用数组的时候:必须提前用一个常量来指定数组的长度,同时它的内存空间在编译的时候就已经被分配了。但是有时候数组的长度只有在运行的时候才能知道。因此,一种简单的解决方案就是提前申请一块较大的数组,可以容纳可能出现的最多的元素;

  使用这种放的的优点就是非常简单;但是也存在很大的缺点,1、人为因素太大,如果元素数量超过声明的长度,就会发生数组越界,避免的方法就是在声明大一点,很浪费内存;2、如果实际使用的元素比较少的话,就会有大量的内存被浪费掉了;

  需要注意的一个小问题是,当定义的数组为局部变量时,会存放在栈上面,栈的系统默认的大小为1M,如果数组过大,会出现内存溢出;如果将数组生命在全局存储区不会出现这个限制;

  对于动态内存的分配与释放,c函数库提供了两个函数,malloc和free,分别用于执行动态内存分配和释放。这些函数维护一个可用内存。

  注意,所有动态申请的内存都存在堆上面,用户通过保存在栈上面的一个指针来使用该内存空间;

malloc函数

  malloc的全称是memory allocation,中文叫动态内存分配。作用是向系统申请分配指定size个字节的内存空间,函数原型为:

extern void *malloc(unsigned int num_bytes);

  void*表示未确定类型的指针,C,C++规定,void* 类型可以通过类型转换强制转换为任何其它类型的指针。

  free函数:void free(void *FirstByte): 该函数是将之前用malloc分配的空间还给程序或者是操作系统,也就是释放了这块内存,让它重新得到自由。

  其功能为在堆中申请一块连续的可用的内存;使用中有如下需要关注的点:

  1.malloc分配的内存大小至少为size参数所指定的字节数,由编译器决定.

  2.malloc的返回值是一个指针,指向一段可用内存的起始地址.

  3.多次调用malloc所分配的地址不能有重叠部分,除非某次malloc所分配的地址被释放掉.

  4.malloc应该尽快完成内存分配并返回.

  5.实现malloc时应同时实现内存大小调整和内存释放函数(即realloc和free).

用法示例:

#include <stdio.h>
#include <stdlib.h>
#include<malloc.h>
int main() {
    char *ptr;
    ptr = (char*)malloc(8);   //返回类型为void*,强制转换
    if (NULL == ptr)          //申请后务必检查是否成功,因为对内存不足等原因有可能申请失败
    { 
          exit (1); 
    } 

    if(ptr)  
        printf("Memory Allocated at: %x/n",ptr);
    free(ptr);                //申请同时释放
    ptr = NULL;               //free释放的只是内存,ptr指针还在,所以记得将ptr指向NULL
    return 0;
}

malloc函数使用时需要注意的要点:

  1、如果有足够空间用于扩大mem_address指向的内存块,则分配额外内存,并返回mem_address.  这里说的是“扩大”,我们知道,realloc是从堆上分配内存的,当扩大一块内存空间时, realloc()试图直接从堆上现存的数据后面的那些字节中获得附加的字节,如果能够满足,自然天下太平。也就是说,如果原先的内存大小后面还有足够的空闲空间用来分配,加上原来的空间大小= newsize,得到的是一块连续的内存。

  2、如果原先的内存大小后面没有足够的空闲空间用来分配,那么从堆中另外找一块newsize大小的内存。  并把原来大小内存空间中的内容复制到newsize中。返回新的mem_address指针。  

  3、虽然malloc()函数的类型是(void *),任何类型的指针都可以转换成(void *),但是最好还是在前面进行强制类型转换,因为这样可以躲过一些编译器的检查。

  4、malloc函数的特殊情况

  如果mem_address为null,则realloc()和malloc()类似。分配一个newsize的内存块,返回一个指向该内存块的指针。

  如果newsize大小为0,那么释放mem_address指向的内存,并返回null。

  如果没有足够可用的内存用来完成重新分配(扩大原来的内存块或者分配新的内存块),则返回null而原来的内存块保持不变。

  5、申请了内存空间后,必须检查是否分配成功

  6、当不需要再使用申请的内存时,记得释放;释放后应该把指向这块内存的指针指向NULL,防止程序后面不小心使用了它;当程序运行过程中malloc了,但是没有free的话,会造成内存泄漏.一部分的内存没有被使用,但是由于没有free,因此系统认为这部分内存还在使用,造成不断的向系统申请内存,使得系统可用内存不断减少.但是内存泄漏仅仅指程序在运行时,程序退出时,OS将回收所有的资源.

  7、这两个函数应该是配对。如果申请后不释放就是内存泄露;如果无故释放那就是什么也没有做。释放只能一次,如果释放两次及两次以上会出现错误(释放空指针例外,释放空指针其实也等于啥也没做,所以释放空指针释放多少次都没有问题)。

malloc()函数和free()函数的机制说明

malloc函数原理

  malloc()到底从哪里分配到内存空间?答案是从堆里面获得空间。也就是说函数返回的指针是指向堆里面的一块内存。操作系统中有一个记录空闲内存地址的链表。当操作系统收到程序的申请时,就会遍历该链表,然后就寻找第一个空间大于所申请空间的堆结点,然后就将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。关于堆的知识呢可以查询数据结构方面的知识。在使用malloc()分配内存空间后,一定要记得释放内存空间,否则就会出现内存泄漏。

free函数原理

  free()到底释放了什么?free()释放的是指针指向的内存!注意!释放的是内存,不是指针!指针并没有被释放,指针仍然指向原来的存储空间。指针是一个变量,只有程序结束时才被销毁。释放了内存空间后,原来指向这块空间的指针还是存在!只不过现在指针指向的内容的垃圾,是未定义的,所以说是垃圾。因此,释放内存后把指针指向NULL,防止指针在后面不小心又被解引用了。free()的函数原型非常简单,只有一个参数,只要把指向申请空间的指针传递给free()中的参数就可以完成释放工作!这里要追踪到malloc()的申请问题了。申请的时候实际上占用的内存要比申请的大。因为超出的空间是用来记录对这块内存的管理信息。大多数实现所分配的存储空间比所要求的要稍大一些,额外的空间用来记录管理信息——分配块的长度,指向下一个分配块的指针等等。这就意味着如果写过一个已分配区的尾端,则会改写后一块的管理信息。这种类型的错误是灾难性的,但是因为这种错误不会很快就暴露出来,所以也就很难发现。将指向分配块的指针向后移动也可能会改写本块的管理信息。

除了malloc函数,还有两个内存分配函数,calloc和realloc。

原型如下:

  void *calloc(size_t n, size_t size);

  功能: 在内存的动态存储区中分配n个长度为size的连续空间,函数返回一个指向分配起始地址的指针;如果分配不成功,返回NULL。

  extern void *realloc(void *mem_address, unsigned int newsize);

  功能先判断当前的指针是否有足够的连续空间,如果有,扩大mem_address指向的地址,并且将mem_address返回,如果空间不够,先按照newsize指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,而后释放原来mem_address所指内存区域(注意:原来指针是自动释放,不需要使用free),同时返回新分配的内存区域的首地址。即重新分配存储器块的地址。

说明

  1.calloc也用于分配内存。malloc和calloc之间的区别是后者在返回指向内存的指针之前把它初始化为0。但如果程序只是想把一些值存储到数组中,那么这个初始化过程纯属浪费时间。

  2.calloc和malloc之间另一个较小的区别是它们请求内存数量的方式不同。calloc的参数包括所需元素的数量和每个元素的字节数。根据这些值,它能够计算出总共需要分配的内存。

  3.realloc函数用于修改一个原先已经分配的内存块大小

返回的是一个void类型的指针,调用成功。(这就再你需要的时候进行强制类型转换)

  4、realloc返回NULL,当需要扩展的大小(第二个参数)为0并且第一个参数不为NULL,此时原内存变成了“freed(游离)”的了。

  返回NULL,当没有足够的空间可供扩展的时候,此时,原内存空间的大小维持不变。

注意事项

  1、使用realloc这个函数,你可以使一块内存扩大或缩小。如果它用于扩大一个内存块,那么这块内存原先的内容依然保留,新增加的内存添加到原先内存块的后面,新内存并未以任何方法进行初始化。

  2、如果realloc用于缩小一块内存块,该内存块尾部的部分内存便被拿掉,剩余部分内存的原先内容依然保留。

  3、如果原先的内存块无法改变大小,realloc将分配另一块正确大小的内存,并把原先那块内存的内容复制到新的块上。因此,在使用realloc之后,就不能再使用指向旧内存的指针,而是应该该用realloc所返回的新指针

  4、如果realloc函数的第一个参数是NULL,那么它的行为就和malloc一模一样

  5、传递给realloc的指针必须是先前通过malloc(), calloc(), 或realloc()分配的

  6、如果size为0,realloc效果等同于free()。这里需要注意的是只对指针本身进行释放,例如对二维指针**a,对a调用realloc时只会释放一维,使用时谨防内存泄露。

 new运算符:

  动态创建对象时,只需指定其数据类型,而不必为该对象命名,new表达式返回指向该新创建对象的指针,我们可以通过指针来访问此对象。

int *pi=new int;

  这个new表达式在堆区中分配创建了一个整型对象,并返回此对象的地址,并用该地址初始化指针pi 。

  动态创建的对象可以用初始化变量的方式初始化。

  如果不提供显示初始化,对于类类型,用该类的默认构造函数初始化;而内置类型的对象则无初始化

也可以对动态创建的对象做值初始化:

int *pi=new int( );//初始化为0
int *pi=new int;//pi 指向一个没有初始化的int
string *ps=new string( );//初始化为空字符串 (对于提供了默认构造函数的类类型,没有必要对其对象进行值初始化)

delete运算符

  释放指针指向的地址空间。

  delete p; //执行完该语句后,p变成了不确定的指针,在很多机器上,尽管p值没有明确定义,但仍然存放了它之前所指对象的地址,然后p所指向的内存已经被释放了,所以p不再有效。此时,该指针变成了悬垂指针(悬垂指针指向曾经存放对象的内存,但该对象已经不存在了)。悬垂指针往往导致程序错误,而且很难检测出来。

  一旦删除了指针所指的对象,立即将指针置为0,这样就非常清楚的指明指针不再指向任何对象。(零值指针:int *ip=0;)

区分零值指针和NULL指针

  零值指针,是值是0的指针,可以是任何一种指针类型,可以是通用变体类型void*也可以是char*,int*等等。
  空指针,其实空指针只是一种编程概念,就如一个容器可能有空和非空两种基本状态,而在非空时可能里面存储了一个数值是0,因此空指针是人为认为的指针不提供任何地址讯息

new分配失败时,返回什么?

  1993年前,c++一直要求在内存分配失败时operator new要返回0,现在则是要求operator new抛出std::bad_alloc异常。很多c++程序是在编译器开始支持新规范前写的。c++标准委员会不想放弃那些已有的遵循返回0规范的代码,所以他们提供了另外形式的operator new(以及operator new[])以继续提供返回0功能。这些形式被称为“无抛出”,因为他们没用过一个throw,而是在使用new的入口点采用了nothrow对象:

malloc与new的区别:

  1、new 返回指定类型的指针,并且可以自动计算所需要大小。而 malloc 则必须要由我们计算字节数,并且在返回后强行转换为实际类型的指针

int* ptr 1= new int [100];  //返回类型为int*,分配大小为sizeof(int)*100字节;

int* ptr2 = (int *)malloc(100); //返回类型为void*,需要强制转换为int*,分配大小为100字节;

  2、malloc 只管分配内存,并不能对所得的内存进行初始化,所以得到的一片新内存中,其值将是随机的。除了分配及最后释放的方法不一样以外,通过malloc或new得到指针,在其它操作上保持一致。

有malloc/free为什么还需要new/delete?

  1) malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。

  2) 对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free

  因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数

  我们不要企图用malloc/free来完成动态对象的内存管理,应该用new/delete。由于内部数据类型的“对象”没有构造与析构的过程,对它们而言malloc/free和new/delete是等价的。

  3) 既然new/delete的功能完全覆盖了malloc/free,为什么C++不把malloc/free淘汰出局呢?这是因为C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。

  如果用free释放“new创建的动态对象”,那么该对象因无法执行析构函数而可能导致程序出错。如果用delete释放“malloc申请的动态内存”,结果也会导致程序出错,但是该程序的可读性很差。所以  new/delete必须配对使用,malloc/free也一样。

常见的动态内存错误:

1、对NULL指针进行解引用操作;

2、对分配的内存进行操作时越过边界;

3、释放并非动态分配的内存;

4、试图释放一块动态分配的内存的一部分以及一块内存被释放之后被继续使用;

 参考资料:

http://www.169it.com/it-c-cpp/article-malloc-3931680852687529154

http://blog.jobbole.com/109234/

http://blog.csdn.net/shuaishuai80/article/details/6140979

http://blog.csdn.net/tigerjibo/article/details/7226611

原文地址:https://www.cnblogs.com/jhmu0613/p/6915437.html