如果一个指针指向的内存没有访问权限,或者指向一块已经释放掉的内存,那么就无法对该指针进行操作,这样的指针称为野指针(Wild Pointer)。

指向没有访问权限的内存

请看下面的代码:

  1. #include <stdio.h>
  2. int main(){
  3. char *str;
  4. gets(str);
  5. puts(str);
  6. return 0;
  7. }

在GCC下运行,输入一个字符串后会提示段错误(Segment Fault)。在VS下运行,输入一个字符串后会提示类似下面的错误:
image.png

这是因为,str 是局部变量,它的值是不确定的,是随机的,不知道指向哪块内存。一般情况下,这块内存要么没有访问权限,要么还没有分配,当 gets() 函数试图将读取到的字符串写入这块内存时,必然会发生错误。

当然,如果足够幸运的话,str 也可能恰好指向一段分配好的、并且有读写权限的内存,程序就运行成功了,但这是小概率事件,一般不会发生。

指向释放掉的内存

请继续看下面的代码:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. int main(){
  5. char *str = (char*)malloc(20*sizeof(char));
  6. strcpy(str, "C语言中文网");
  7. puts(str);
  8. free(str);
  9. if(str){
  10. puts(str);
  11. }
  12. return 0;
  13. }

运行程序**,第一次输出C语言中文网,第二次输出的是乱码或者什么也不输出。这是因为,freed() 只是释放掉了动态分配的内存,但并未改变 str 的值,str 的值不是 NULL,它仍然指向被释放掉的内存,所以会执行 if 语句里面的 puts() 函数。但由于此时的内存已经被释放掉了,原来的字符串已经不在了,所以输出的数据是未知的。
这就提醒我们,使用 free() 释放内存的同时要将指针置为NULL,否则下次就无法判断指向的内存是否有效。

还有一种情况是函数外部指针指向函数内部的变量、数组等,请看下面的代码:

  1. #include <stdio.h>
  2. void func(char **pp);
  3. int main(){
  4. char *pstr;
  5. func(&pstr);
  6. puts(pstr);
  7. return 0;
  8. }
  9. void func(char **pp){
  10. char arr[] = "C语言中文网";
  11. *pp = arr;
  12. }

arr 数组在栈上分配内存,字符串”C语言中文网”就存储在这里,func() 函数运行结束后,这块内存被释放掉,但是函数外部的 pstr 仍然指向这里,所以执行puts(pstr);时,输出结果是未知的。

规避野指针

要想规避野指针,就要养成良好的编程习惯:
1) 指针变量如果暂时不需要赋值,一定要初始化为NULL,因为任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的。2) 当指针指向的内存被释放掉时,要将指针的值设置为 NULL,因为 free() 只是释放掉了内存,并为改变指针的值。**

内存泄露

使用 malloc()、calloc()、realloc() 动态分配的内存,如果没有指针指向它,就无法进行任何操作,这段内存会一直被程序占用,直到程序运行结束由操作系统回收。

请看下面的代码:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. int main(){
  4. char *p = (char*)malloc(100 * sizeof(char));
  5. p = (char*)malloc(50 * sizeof(char));
  6. free(p);
  7. p = NULL;
  8. return 0;
  9. }

该程序中,第一次分配 100 字节的内存,并将 p 指向它;第二次分配 50 字节的内存,依然使用 p 指向它。
这就导致了一个问题,第一次分配的 100 字节的内存没有指针指向它了,而且我们也不知道这块内存的地址,所以就再也无法找回了,也没法释放了,这块内存就成了垃圾内存,虽然毫无用处,但依然占用资源,唯一的办法就是等程序运行结束后由操作系统回收。
这就是内存泄露(Memory Leak),可以理解为程序和内存失去了联系,再也无法对它进行任何操作。

内存泄漏形象的比喻是“操作系统可提供给所有程序使用的内存空间正在被某个程序榨干”,最终结果是程序运行时间越长,占用内存空间越来越多,最终用尽全部内存空间,整个系统崩溃。

再来看一种内存泄露的情况:
int *pOld = (int*) malloc( sizeof(int) );
int *pNew = (int*) malloc( sizeof(int) );
这两段代码分别创建了一块内存,并且将内存的地址传给了指针 pOld 和 pNew。此时指针 pOld 和 pNew 分别指向两块内存。

如果接下来进行这样的操作:
pOld=pNew;
pOld 指针就指向了 pNew 指向的内存地址,这时候再进行释放内存操作:
free(pOld);
此时释放的 pOld 所指向的内存空间就是原来 pNew 指向的,于是这块空间被释放掉了。但是 pOld 原来指向的那块内存空间还没有被释放,不过因为没有指针指向这块内存,所以这块内存就造成了丢失。

另外,你不应该进行类似这面这样的操作:
malloc( 100 * sizeof(int) );
这样的操作没有意义,因为没有指针指向分配的内存,无法使用,而且无法通过 free() 释放掉,造成了内存泄露。

最后的总结

free() 函数的用处在于实时地回收内存,如果程序很简单,程序结束之前也不会使用过多的内存,不会降低系统的性能,那么也可以不用写 free() 函数。当程序结束后,操作系统会释放内存。

但是如果在开发大型程序时不写 free() 函数,后果是很严重的。这是因为很可能在程序中要重复一万次分配10MB的内存,如果每次进行分配内存后都使用 free() 函数去释放用完的内存空间, 那么这个程序只需要使用10MB内存就可以运行。但是如果不使用 free() 函数,那么程序就要使用100GB 的内存!这其中包括绝大部分的虚拟内存,而由于虚拟内存的操作需要读写磁盘,因此,这样会极大地影响到系统的性能,系统因此可能崩溃。

因此,在程序中使用 malloc() 分配内存时都对应地写出一个 free() 函数是一个良好的编程习惯。这不但体现在处理大型程序时的必要性,并能在一定程度上体现程序优美的风格和健壮性。