当所指向的对象被释放或者收回,但是对该指针没有作任何的修改,以至于该指针仍旧指向已经回收的内存地址,此情况下该指针便称悬垂指针(也叫迷途指针)。
某些编程语言允许未初始化的指针的存在,而这类指针即为野指针。
悬垂指针的成因:
在许多编程语言中(比如C),显示地从内存中删除一个对象或者返回时通过销毁栈帧,并不会改变相关的指针的值。该指针仍旧指向内存中相同的位置,即使引用已经被删除,现在可能已经挪作他用。
一个简单的例子:
{ char *dp = NULL; /* ... */ { char c; dp = &c; } /* c falls out of scope */ /* dp is now a dangling pointer */ }
如果操作系统能够侦测运行时的指向空指针的引用,一个方案是在内部快消失之前给dp赋为0(NULL)。另一个方案是保证dp在没有被初始化之前不再被使用。
另一个常见原因是混用 malloc() 和 free():当一个指针指向的内存被释放后就会变成悬垂指针。正如上个例子,可以避免这个问题的一种方法是在释放它的引用后把指针重置为NULL。
#include <stdlib.h> void func() { char *dp = malloc(A_CONST); /* ... */ free(dp); /* dp now becomes a dangling pointer */ dp = NULL; /* dp is no longer dangling */ /* ... */ }
一个很常见的失误是返回一个栈分配的局部变量:一旦调用的函数返回了,分配给这些变量的空间被回收,此时它们拥有的是“垃圾值”。
int *func(void) { int num = 1234; /* ... */ return # }
调用 func 后,尝试从该指针暂时能读取到正确的值(1234),但是再次调用函数后将会重写栈为 num 分配的的值,再从该指针读取的值就不正确了。如果必须要返回一个指向 num 的指针,num 的作用域必须大于这个函数——它也许被声明为 static。
野指针的成因:
野指针的产生是由于在首次使用之前没有进行必要的初始化。因此,严格地说,在编程语言中的所有为初始化的指针都是野指针。
int f(int i) { char *dp; /* dp is a wild pointer */ static char *scp; /* scp is not a wild pointer: * static variables are initialized to 0 * at start and retain their values from * the last call afterwards. * Using this feature may be considered bad * style if not commented */ }
dp 是一个野指针。scp 不是一个野指针:静态变量一开始被初始化为0,从最后一次调用后保持着它们的值。如果没有注释,使用这个特性也许被视为不良风格。
避免悬垂指针错误:
在 C/C++ 中,一种最简单的技术是实现一个 free()(或类似的)替代版本或者 delete 析构器来保证指针的重置。然后,这个技术不会清除其他指针变量,它们含有该指针的副本。
/* Alternative version for 'free()' */ void safefree(void **pp) { if (pp != NULL) { /* safety check */ free(*pp); /* deallocate chunk, note that free(NULL) is valid */ *pp = NULL; /* reset original pointer */ } } int f(int i) { char *p = NULL, *p2; p = (char *)malloc(1000); /* get a chunk */ p2 = p; /* copy the pointer */ /* use the chunk here */ safefree(&p); /* safety freeing; does not affect p2 variable */ safefree(&p); /* this second call won't fail */ char c = *p2; /* p2 is still a dangling pointer, so this is undefined behavior. */ }
替换版本可以用来保证在调用 malloc() 之前一个空指针的正确性:
safefree(&p); /* i'm not sure if chunk has been released */ p = malloc(1000); /* allocate now */
这些用法可以通过 #define 指令来构造有用的宏指令,创建像元语言的东西来掩饰或者被嵌入到一个工具库中。但凡使用这个技术的程序员在会用到 free()的地方应该使用安全版本;不这么做会再次导致这些问题。另外,这个解决方案局限于单个程序或工程的作用域中,并且应该正确地写入文档。
在更多结构化的解决方案中,一种流行的避免悬垂指针的技术是使用智能指针。一个智能指针通常使用引用技术来收回对象。还有些技术包括 tombstones 方法和 locks-and-keys 方法。
另一个方法是使用 Boehm 垃圾收集器,一种保守的垃圾收集器,取代C和C++中的标准内存分配函数。此法通过禁止内存释放函数来完全消除悬垂指针引发的错误,通过收集垃圾来回收对象。
像Java语言,悬垂指针这样的错误是不会发生的,因为Java中没有明确地重新分配内存的机制。而且垃圾回收器只会在对象的引用数为0时重新分配内存。