malloc函数详解以及和new的区别

C++ Program

Posted by Johnny on March 1, 2016

今天偶然看到一个面试经验中提到malloc和new的区别,突然发现自己虽然两个都用过,但是至于区别,真的不是很明白 ,所以就仔细查了一些资料,算是对这个点彻底地了解一下,现在把我所学到的记录下来。

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

1.malloc函数初探

首先我们要知道malloc是一个函数,malloc的全称是memory allocation,中文叫动态内存分配。它的原型是:

void *malloc(int size);

说明:malloc 向系统申请分配指定size个字节的内存空间,返回类型是 void* 类型。void* 表示未确定类型的指针。C,C++规定,void* 类型可以强制转换为任何其它类型的指针。    在这里注意: (1) void* 表示未确定类型的指针,更明确的说是指申请内存空间时还不知道用户是用这段空间来存储什么类型的数据(比如是char还是int或者…) (2) 使用malloc向系统申请内存时可能分配失败。如果分配失败,则返回一个空指针(NULL)。关于分配失败的原因,有很多种,比如说空间不足就是一种。 一个对应的释放内存的函数:

void free(void *FirstByte);

该函数是将之前用malloc分配的空间还给程序或者是操作系统,也就是释放了这块内存,让它重新得到自由。 至于用法,其实这两个函数用起来倒不是很难,也就是malloc()之后觉得不用了需要释放把它给free()了,举个简单例子:

char *Ptr = NULL; 
Ptr = (char *)malloc(100 * sizeof(char)); 
if (NULL == Ptr){ 
	exit (1); 
} 
gets(Ptr);  
free(Ptr); 
Ptr = NULL; 

当然,具体情况要具体分析以及具体解决。比如说,你定义了一个指针,在一个函数里申请了一块内存然后通过函数返回传递给这个指针,那么也许释放这块内存这项工作就应该留给其他函数了。只要保证每个malloc()之后必须有一个free()与之对应。

  1. 关于这个函数的用法需要注意的一些地方:
  2. 申请了内存空间后,必须检查是否分配成功。
  3. 当不需要再使用申请的内存时,记得释放;释放后应该把指向这块内存的指针指向NULL,防止程序后面不小心使用了它。
  4. 这两个函数应该是配对。如果申请后不释放就是内存泄露;如果无故释放那就是什么也没有做。释放只能一次,如果释放两次及两次以上会出现错误(释放空指针例外,释放空指针其实也等于啥也没做,所以释放空指针释放多少次都没有问题)。
  5. 虽然malloc()函数的类型是void,任何类型的指针都可以转换成void,但是最好还是在前面进行强制类型转换,因为这样可以躲过一些编译器的检查。

2. malloc函数深入

看了以上的内容我们大致知道malloc函数的初步内容以及它的用法,但是我们不知道malloc函数是怎么实现动态分配内存的。那它是怎么动态分配内存的呢?

答案是从堆里面获得空间。也就是说函数返回的指针是指向堆里面的一块内存。操作系统中有一个记录空闲内存地址的链表。当操作系统收到程序的申请时,就会遍历该链表,然后就寻找第一个空间大于所申请空间的堆结点,然后就将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。

说到这里就不得不提堆,什么是堆呢?堆是大家共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程 初始化的时候分配,运行过程中也可以向系统要额外的堆,记住一点:从堆申请的内存用完了要还给操作系统,也就是释放,如果不还的话会发生内存泄露

而所谓内存泄露就好比你去食堂吃饭,你吃完了之后没把盘子拿走,这在别人看来是这个座有人占了,可怕的是你回头吃饭又占了一个新座,你吃完之后还是没把盘子拿走……终于,整个食堂的座位都被占满了,食堂也乱套了。(比喻不太恰当,但大致是这个意思)

平常我们老说堆栈、堆栈,那栈又是什么呢?栈是线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立。每个函数都有自己的栈,栈被用来在函数之间传递参数。操作系统在切换线程的时候会自动的切换栈,就是切换SS/ESP寄存器。栈空间不需要在高级语言里面显式的分配和释放。

通过上面对概念的描述,可以知道:

栈是由编译器自动分配释放,存放函数的参数值、局部变量的值等。操作方式类似于数据结构中的栈。

堆一般由程序员分配释放,若不释放,程序结束时可能由操作系统回收。注意这里说是可能,并非一定。所以堆一定要释放!

3.new运算符

3.1 C++中,用new和delete动态创建和释放数组或单个对象

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

int *pi=new int;

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

3.2 动态创建对象的初始化

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

int *pi=new int(100); //指针pi所指向的对象初始化为100
string *ps=new string(10,'9');//*ps 为“9999999999”

如果不提供显示初始化,对于类类型,用该类的默认构造函数初始化;而内置类型的对象则无初始化。 也可以对动态创建的对象做值初始化:

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

3.3 撤销动态创建的对象

delete表达式释放指针指向的地址空间。

delete pi ;// 释放单个对象
delete [ ]pi;//释放数组

如果指针指向的不是new分配的内存地址,则使用delete是不合法的。

3.4 在delete之后,重设指针的值

delete p;

执行完该语句后,p变成了不确定的指针,在很多机器上,尽管p值没有明确定义,但仍然存放了它之前所指对象的地址,然后p所指向的内存已经被释放了,所以p不再有效。此时,该指针变成了悬垂指针(悬垂指针指向曾经存放对象的内存,但该对象已经不存在了)。悬垂指针往往导致程序错误,而且很难检测出来。 一旦删除了指针所指的对象,立即将指针置为0,这样就非常清楚的指明指针不再指向任何对象。(零值指针:int *ip=0;

3.5 区分零值指针和NULL指针

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

4.malloc和new的区别

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

//new
int *p;   
p = new int; //返回类型为int* 类型(整数型指针),分配大小为 sizeof(int); 
int* parr;   
parr = new int [100]; //返回类型为 int* 类型(整数型指针),分配大小为sizeof(int) * 100;

//malloc
int* p;   
p = (int *) malloc (sizeof(int)*128);//分配128个(可根据实际需要替换该数值)整型存储单元,并将这128个连续的整型存储单元的首地址存储到指针变量p中  
double *pd=(double *) malloc (sizeof(double)*12);//分配12个double型存储单元,并将首地址存储到指针变量pd中

(2)malloc 只管分配内存,并不能对所得的内存进行初始化,所以得到的一片新内存中,其值将是随机的。new创建的对象可以用初始化变量的方式初始化。

除了分配及最后释放的方法不一样以外,通过malloc或new得到指针,在其它操作上保持一致。

5.参考资料

  1. C++Primer
  2. 浅谈 C++ 中的 new/delete 和 new[]/delete[]
  3. new和malloc的区别深入解析
  4. malloc/free和new/delete的区别
  5. 浅谈C中的malloc和free
  6. malloc函数详解

// mathjax