# 指针和内存 如果一个指针指向的内存没有访问权限,或者指向一块已经释放掉的内存,那么就无法对该指针进行操作,这样的指针称为野指针(Wild Pointer)。 ## 指向没有访问权限的内存 请看下面的代码: ``` #include int main(){ char *str; gets(str); puts(str); return 0; } ``` 在GCC下运行,输入一个字符串后会提示`段错误(Segment Fault)`。在VS下运行,输入一个字符串后会提示类似下面的错误: ![img](http://c.biancheng.net/uploads/allimg/190122/1059411114-0.png) 这是因为,str 是局部变量,它的值是不确定的,是随机的,不知道指向哪块内存。一般情况下,这块内存要么没有访问权限,要么还没有分配,当 gets() 函数试图将读取到的字符串写入这块内存时,必然会发生错误。 当然,如果足够幸运的话,str 也可能恰好指向一段分配好的、并且有读写权限的内存,程序就运行成功了,但这是小概率事件,一般不会发生。 ## 指向释放掉的内存 请继续看下面的代码: ``` #include #include #include int main(){ char *str = (char*)malloc(20*sizeof(char)); strcpy(str, "wangyuedong"); puts(str); free(str); if(str){ puts(str); } return 0; } ``` 运行程序,第一次输出`wangyuedong`,第二次输出的是乱码或者什么也不输出。这是因为,freed() 只是释放掉了动态分配的内存,但并未改变 str 的值,str 的值不是 NULL,它仍然指向被释放掉的内存,所以会执行 if 语句里面的 puts() 函数。但由于此时的内存已经被释放掉了,原来的字符串已经不在了,所以输出的数据是未知的。 这就提醒我们,使用 free() 释放内存的同时要将指针置为`NULL`,否则下次就无法判断指向的内存是否有效。 还有一种情况是函数外部指针指向函数内部的变量、数组等,请看下面的代码: ``` #include void func(char **pp); int main(){ char *pstr; func(&pstr); puts(pstr); return 0; } void func(char **pp){ char arr[] = "C语言中文网"; *pp = arr; } ``` arr 数组在栈上分配内存,字符串`"C语言中文网"`就存储在这里,func() 函数运行结束后,这块内存被释放掉,但是函数外部的 pstr 仍然指向这里,所以执行`puts(pstr);`时,输出结果是未知的。 ## 规避野指针 要想规避野指针,就要养成良好的编程习惯: 1) 指针变量如果暂时不需要赋值,一定要初始化为NULL,因为任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的。 2) 当指针指向的内存被释放掉时,要将指针的值设置为 NULL,因为 free() 只是释放掉了内存,并未改变指针的值。 ## 内存泄漏 使用 malloc()、calloc()、realloc() 动态分配的内存,如果没有指针指向它,就无法进行任何操作,这段内存会一直被程序占用,直到程序运行结束由操作系统回收。 ```c #include #include int main(){ char *p = (char*)malloc(100 * sizeof(char)); p = (char*)malloc(50 * sizeof(char)); free(p); p = NULL; return 0; } ``` 该程序中,第一次分配 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() 函数是一个良好的编程习惯。这不但体现在处理大型程序时的必要性,并能在一定程度上体现程序优美的风格和健壮性。