malloc()和free()的相关知识

来源:互联网 发布:淘宝网 子孙桶 编辑:程序博客网 时间:2024/05/23 01:35

转自:http://codeup.org/archives/212

之前有一篇文章是写全局变量、静态变量、局部变量、静态局部变量以及栈和堆在内存中的存储区别的,最近我又看了一篇关于C中Malloc函数和Free函数对内存操作的,《浅谈C中的malloc和free》,通过这篇文章,我对C的内存申请以及释放有了新的、全面的认识。我想,不仅是C,C++或者C#等其他语言的内存申请释放应该也是使用相近的方法吧。下面我简单总结了一下《浅谈》的主要内容,再加上一点我自己的理解。

之前,我对malloc和free的了解,仅仅是在C中使用这两个函数来申请和释放内存,只知道怎么使用,用malloc申请一段给定大小的内存,返回一个Void* 指针,当使用完这段内存后需要手动调用free来释放这段内存。

一、malloc() 和 free() 的基本概念和用法:

 

1、函数原型及说明:

void *malloc(long NumBytes): 该函数分配了NumBytes个字节,并返回了指向这块内存的指针。如果分配失败,则返回一个空指针(NULL)。

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

2、函数用法:

这两个函数的用法其实很简单,就是用malloc()申请内存,用完这段内存后交给free()来释放着段内存。简单例子:

malloc和free的示例程序
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
#include<stdio.h>
intmain()
{
    char*Ptr = NULL;
    Ptr = (char*)malloc(100 * sizeof(char));//Malloc
    if(NULL == Ptr)
    {
        printf("malloc failed");
        return1;
    }
    gets(Ptr);
    printf("%s",Ptr);
    //code...
 
    free(Ptr);//Free
    return0;
}

这是一个非常简单的情况,实际情况要具体问题具体分析,比如说你有一个函数专门负责申请内存,然后将地址指针返回给调用者,那么这段内存的释放就应该交给别人~

3、函数使用的注意事项:

A、malloc()调用后必须检查返回值,检查是否申请成功,malloc()申请失败则返回NULL

B、当申请的内存不再被使用后,要free释放内存,并将变量指针赋值为NULL,防止后面的程序不小心使用了他。

C、malloc()和free()是配对使用的,申请后不释放就是内存泄漏,如果释放多次则会引起错误(释放空指针除外,空指针可以释放多次)。

D、malloc()的返回值是Void* 类型的,可以赋值给任何指针,但是最好还是在前面使用强制类型转换,这样可以躲过一些编译器的检查,少点Warning还是不错的:)。

二、malloc做了什么?

 

1、malloc()从哪里得到的内存空间?

答案是从堆里得到的,函数返回值是指向堆里一块内存的指针。OS中有一个记录空闲内存地址的链表。当OS收到内存申请的时候,就会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后就将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。当然,具体的内存分配由OS的不同而略有不同。

2、什么是堆,什么是栈?

对于这个问题,我在之前的文章中有过解释,但是感觉没有《浅谈》作者解释的更透彻,故引用其原文于此以作补充:

什么是:堆是大家共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是记得用完了要还给操作系统,要不然就是内存泄漏。

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

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

堆一般由程序员分配释放,若不释放,程序结束时可能由OS回收。注意这里说是可能,并非一定。所以我想再强调一次,记得要释放!

但是需要注意的是,如果你在函数里面定义了一个指针变量,然后在函数中申请了一块内存让指针指向它。分析一下现在的情况,这个指针变量是存在于上面的,但是这个指针变量所指向的内存是在上面的。这是重点,你要明白,在函数执行完之后,这个栈上的指针变量被释放了,但是它所指向的堆上的内存并没有被释放!

1
2
3
4
voidFunction(void)
{
    char*Ptr = (char*)malloc(100 * sizeof(char));
}

这个例子中,千万不要因为函数执行完并返回了,函数所在的栈被销毁了,Ptr指针被销毁了,就以为malloc申请的这段内存也被释放了。这是错误的,申请的内存在堆上,和这个栈没有一毛钱的关系。记住一定要释放!

3、free()释放了什么

free()其实很简单,就是释放了指针指向的内存,注意,是内存而不是指针,指针本身并没有被改变,仅仅是指针所指向的内存现在变成了未定义的不可用的内存。所以,请在free了指针所指向的内存后,手动把指针指向NULL,防止后面的代码又去操作那段已经被释放的内存,不然的话会产生不可预计的严重错误。

三、malloc()和free()的机制

 

这一部分是重点中的重点,之上的部分,虽然以前不是特别明白,但是多少还是有些了解的。但是下面这些,以前真的是一点都不知道,现在有了这些了解,我对C语言的内存申请机制,有了很深的了解,终于明白是怎么回事了!这是转折性的认识。

通过上面free()的原型,我们可以看到,free()非常简单,只有一个指针参数,他是怎么知道要释放多少内存的呢?谁告诉它的呢?解释这个问题,我们要先了解malloc()的申请。申请一段内存的时候,实际上申请到的内存要比你申请的要大一些,多出来的这部分内存就是用来记录对这块内存的管理信息的。引用《UNIX环境高级编程》中第七章的一段话:

大多数实现所分配的存储空间比所要求的要稍大一些,额外的空间用来记录管理信息——分配块的长度,指向下一个分配块的指针等等。这就意味着如果写过一个已分配区的尾端,则会改写后一块的管理信息。这种类型的错误是灾难性的,但是因为这种错误不会很快就暴露出来,所以也就很难发现。将指向分配块的指针向后移动也可能会改写本块的管理信息。

malloc()申请到的内存实际上包含两部分,第一部分是一个记录管理信息的空间,然后才是可用空间。这部分记录管理信息的空间实际上保存着一个结构体,mem_control_block。

mem_control_block原型
1
2
3
4
structmen_control_block{
    intis_available;   //一个标记符
    intsize;           //这是实际空间大小
};

这里size就是实际空间的大小,malloc()申请得到的指针指向了一段size大小的内存,这个指针指向了实际可用的内存的第一个字节,在这个地址之前的就是这个mem_control_block结构。通过free()的源码,我们将更加容易理解。

1
2
3
4
5
6
7
voidfree(void*ptr)
{
    structmem_control_block *free;
    free= ptr - sizeof(structmem_control_block); //这是重点
    free->is_available = 1;
    return;
}

第二句是关键,它把指向可用空间的指针倒回去了一个mem_control_block大小,这样free指针就指向了这块内存的管理信息的那块空间。后面那句free->is_available = 1;就是将这块内存交还给系统,具体的释放操作由OS来执行,这个is_available标志符的意思大概就是通知OS紧跟着的size个字节的内存空间是可用空间了,可以被OS来重新分配了。这就是is_available的作用,它标志着这段内存是否可被重新分配。由此也可看出,具体的释放操作不是由free()函数来执行的而是交给了操作系统。

让我们再仔细观察第二句,free是直接从ptr减去一个结构的大小得到的,换句话说就是ptr必须指向实际可用空间的第一个字节,也就是说,free()的参数必须直接是malloc()的返回值,不能对这个指针进行移动操作,不然,减去结构体大小之后一定不是指向管理信息空间的首地址。不信的话你可以自己写一个程序然后移动指向可用空间的指针,看程序会有会崩!

 

本文很多内容引用自http://bbs.bccn.net/thread-81781-1-1.html