c语言中的动态内存分配函数malloc和free使用起来很灵活,但是也很容易导致错误,
如果知道了malloc和free的实现原理,那么,出错的机会就很小了。
malloc的实现原理:
操作系统维护了一个将可用的内存块连接为一个长长的列表的所谓空闲链表。
调用malloc函数时,操作系统沿链表寻找一个大到足以满足用户请求所需要的内存块。然后,将该内存块一分为二(一块的大小与用户请求的大小相等,另一块的大小就是剩下的字节)。接下来,将分配给用户的那块内存传给用户,并将剩下的那块(如果有的话)返回到链表上。调用free函数时,它将用户释放的内存块连接到空闲链表上。
free的实现原理:
操作系统在调用malloc函数时,会默认在malloc分配的物理内存前面分配一个数据结构,这个数据结构记录了这次分配内存的大小,在用户眼中这个操作是透明的。
那么当用户需要free时,free函数会把指针退回到这个结构体中,找到该内存的大小,这样就可以正确的释放内存了。
通过这种内存分配机制,可以解释很多c语言中的迷惑问题:
1)用户已经free了一段内存,为什么还可以调用该指针,并且编译器不报错?
因为,free函数的作用只是告诉操作系统该内存不用了,可以收回,操作系统就把该内存链接到链接表上,
但是这段内存用户还是可以访问到的,只是该内存的值可能已经发生了变化,这种情况也叫作野指针。
解决办法就是,在free后,把该指针指向NULL。
12 void Test(void) 13 { 14 char *str = (char *) malloc(100); 15 16 strcpy(str,"hell"); 17 18 free(str); 19 printf("str1=%s\n",str); 20 if(str != NULL) 21 { 22 strcpy(str,"world"); 23 printf("str=%s\n",str); 24 } 25 }上面程序会打印出什么?
会打印出:
str1=
str=world
free 只是释放的str指向的内存空间,它本身的值还是存在的.
所以free之后,有一个好的习惯就是将str=NULL.
此时str指向空间的内存已被回收,如果输出语句之前还存在分配空间的操作的话,这段存储空间是可能被重新分配给其他变量的,
尽管这段程序确实是存在大大的问题(上面各位已经说得很清楚了),但是通常会打印出world来。
这是因为,进程中的内存管理一般不是由操作系统完成的,而是由库函数自己完成的。
当你malloc一块内存的时候,管理库向操作系统申请一块空间(可能会比你申请的大一些),然后在这块空间中记录一些管理信息(一般是在你申请的内存前面一点),并将可用内存的地址返回。但是释放内存的时候,管理库通常都不会将内存还给操作系统,因此你是可以继续访问这块地址的,只不过。。。。。。。。最好别这么干。
2)用户在malloc时,分配了100字节,int *指针指向该内存,那么free时,是释放100字节还是释放4字节?
因为操作系统在malloc分配内存时,会在该内存的物理地址的前面维护一个数据结构,该数据结构记录了这次分配内存的大小,所以当用户free时,free函数会把该int *指针退回到该数据结构中,找到该内存的大小,这样free函数就可以正确的释放
该段内存了。
答案:释放100字节,而不是4字节。
3)用户malloc了100字节的内存,int *指针指向该内存,同时定义了char *的指针,也指向该内存,那么free(char *),是释放
1个字节还是释放100个字节?
原理同上面的问题一样,只是这个问题的更具隐蔽性,也容易误解。
因为free函数的原理是退回到该物理内存的前面,找到记录该内存大小的数据结构,所以,即使是char *指针,free也可以正确的释放该内存。
答案:释放100字节,而不是1个字节
free需要释放malloc定义的首地址。
eg:
int *a;
a = (int *)malloc(100);
*a++ = 0;
*a++ = 1;
再用free释放的的时候一定把a的地址指向malloc分配时候的首地址
a--;
a--;
这样采用用free(a);
当然也可以定义一个int *b;在最开始的时候 b = a;
这样就可以free(b)了。