C语言之malloc动态分配内存和free释放
作者:朱又炖粉条
malloc动态分配内存和free释放
先看一个例子
#include<stdio.h> int max=10; //data区,不产生具体的可执行代码。 void main() { //局部变量都在栈区。 栈内存自动分配,释放。堆需要手动malloc,free int a=10; //mov dword ptr[a], 0x0a; 在函数体内,产生具体的可执行代码。 }
malloc动态分配的内存在堆区,其空间并不连续。函数返回的指针是指向堆里面的一块内存。操作系统中有一个记录空闲内存地址的链表。
当操作系统收到程序的申请时,就会遍历该链表,然后就寻找第一个空间大于所申请空间的堆结点,然后就将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。
什么是堆?说到堆,又忍不住说到了栈!什么是栈?
下面就另外开个小部分专门而又简单地说一下这个题外话:
- 什么是堆:
堆是大家共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。
堆在操作系统对进程 初始化的时候分配,运行过程中也可以向系统要额外的堆,但是记得用完了要还给操作系统,要不然就是内存泄漏。
- 什么是栈:
栈是线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立。每个函数都有自己的栈,栈被用来在函数之间传递参数。
操作系统在切换线程的时候会自动的切换栈,就是切换SS/ESP寄存器。栈空间不需要在高级语言里面显式的分配和释放。
通过上面对概念的描述,可以知道:
栈是由编译器自动分配释放,存放函数的参数值、局部变量的值等。操作方式类似于数据结构中的栈。堆一般由程序员分配释放,若不释放,程序结束时可能由OS回收。
注意这里说是可能,并非一定。所以我想再强调一次,记得要释放!注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
所以,举个例子,如果你在函数上面定义了一个指针变量,然后在这个函数里申请了一块内存让指针指向它。
实际上,这个指针的地址是在栈上,但是它所指向的内容却是在堆上面的!这一点要注意!所以,再想想,在一个函数里申请了空间后,比如说下面这个函数:
// code... void Function(void) { char *p = (char *)malloc(100 * sizeof(char)); }
就这个例子,千万不要认为函数返回,函数所在的栈被销毁指针也跟着销毁,申请的内存也就一样跟着销毁了!这绝对是错误的!因为申请的内存在堆上,而函数所在的栈被销毁跟堆完全没有啥关系。所以,还是那句话:记得释放!
1. 函数原型及说明
void *malloc(long NumBytes)
:该函数分配了NumBytes个字节,并返回了指向这块内存的指针。如果分配失败,则返回一个空指针(NULL)。关于分配失败的原因,应该有多种,比如说空间不足就是一种。void free(void *FirstByte)
: 该函数是将之前用malloc分配的空间还给程序或者是操作系统,也就是释放了这块内存,让它重新得到自由。
2. 关于函数使用需要注意的地方
A、申请了内存空间后
必须检查是否分配成功。
B、当不需要再使用申请的内存时
记得释放;释放后应该把指向这块内存的指针指向NULL,防止程序后面不小心使用了它。
#include<stdio.h> #include<malloc.h> void main() { int n; int *p=NULL; scanf("%d",&n); p=(int *)malloc(sizeof(int)*n); //n=10 for(int i=0;i<n;++i) { p[i]=i; } for(int j=0;j<n;++j) { printf("%d\n",p[j]); } free(p); p=NULL; //free释放堆空间后,必须把无效指针变为空。(分手要清空前任的一切) }
如何表示这一块堆内存已经被占用,避免别的程序又来分配我的空间呢。用标志位来标记,所以加上了标志位之后n=10时堆中开辟的空间肯定不止40字节。
注意 free释放的是指针指向的内存,不是指针。指针只是一个变量,只有程序结束时才被销毁。释放了内存空间后,原来指向这块空间的指针还是存在。
当空间释放(free)时,标志位状态就会发生变化,此时又允许其他程序分配我原本的内存空间。此时指针变为无效指针,必须把无效指针变为空。
否则失效指针仍可对内存操作,但有可能这一块内存被分配给别的程序,就会使无效指针修改别人的数据。
麻烦大了。 如:
void main() { int a=10; int *p=NULL; p=(int *)malloc(sizeof(int)); *p=100; printf("%d \n",*p); free(p); //指针仍指向那块,但系统已经将这块空间标记为未用空间。 //失效指针。 printf("%d \n",*p); *p=200; printf("%d \n",*p); p=NULL; //第一次打印100 第二次打印随机值 第三次打印200 }
C、这两个函数应该是配对
如果申请后不释放就是内存泄露;如果无故释放那就是什么也没有做。
释放只能一次,如果释放两次及两次以上会出现错误(释放空指针例外,释放空指针其实也等于啥也没做,所以释放空指针释放多少次都没有问题)。
如:
void main() { int a=10; int *p=(int *)malloc(sizeof(int)); *p=200; printf("%d \n",*p); free(p); printf("%d \n",*p); //p=NULL; 先置空再free,系统不出错。什么也不做。(本来就空指针去离婚,人不搭理你。) free(p); //系统报错。不能对同一个空间重复进行释放。(重复离婚,放狗咬人。) }
D、malloc申请的指针不要动它
可以动它的值,但自身不能动。
这里涉及malloc()以及free()的机制
“大多数实现所分配的存储空间比所要求的要稍大一些,额外的空间用来记录管理信息——分配块的长度,指向下一个分配块的指针等等。这就意味着如果写过一个已分配区的尾端,则会改写后一块的管理信息。这种类型的错误是灾难性的,但是因为这种错误不会很快就暴露出来,所以也就很难发现。将指向分配块的指针向后移动也可能会改写本块的管理信息。”
以上这段话已经给了我们一些信息了。malloc()申请的空间实际我觉得就是分了两个不同性质的空间。一个就是用来记录管理信息的空间,另外一个就是可用空间了。而用来记录管理信息的实际上是一个结构体。
在C语言中,用结构体来记录同一个对象的不同信息是天经地义的事!
下面看看这个结构体的原型:
struct mem_control_block { int is_available; //这是一个标记 int size; //这是实际空间的大小 };
free()就是根据这个结构体的信息来释放malloc()申请的空间,malloc()申请空间后返回一个指针应该是指向第二种空间,也就是可用空间。
看看free()的源代码
void free(void *ptr) { struct mem_control_block *free; free = ptr - sizeof(struct mem_control_block); free->is_available = 1; return; }
看一下函数第二句,这句非常重要和关键。
其实这句就是把指向可用空间的指针倒回去,让它指向管理信息的那块空间,因为这里是在值上减去了一个结构体的大小!
那么,我之前有个错误的认识,就是认为指向那块内存的指针不管移到那块内存中的哪个位置都可以释放那块内存!但是,这是大错特错!释放是不可以释放一部分的!首先这点应该要明白。
而且,从free()的源代码看,ptr只能指向可用空间的首地址,不然,减去结构体大小之后一定不是指向管理信息空间的首地址。
所以,要确保指针指向可用空间的首地址!不信吗?自己可以写一个程序然后移动指向可用空间的指针,看程序会不会崩!
#include<stdio.h> #include<malloc.h> void main() { int n; int *p=NULL; scanf("%d",&n); p=(int *)malloc(sizeof(int)*n); //此时p指向第一个空间元素 for(int i=0;i<n;i++) { *p=i; p+=1; } //此时p指向第n个元素。 free(p); //释放时就找不到标志位了。 p=NULL; }
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。