1. 什么是野指针(wild pointer)?

A pointer in c which has not been initialized is known as wild pointer.

野指针(wild pointer)就是没有被初始化过的指针。例如,

o foo1.c

 int main(int argc, char *argv[])
{
int *p;
return (*p & 0x7f); /* XXX: p is a wild pointer */
}

如果用"gcc -Wall"编译, 会出现如下警告:

 $ gcc -Wall -g -m32 -o foo foo.c
foo.c: In function ‘main’:
foo.c::: warning: ‘p’ is used uninitialized in this function [-Wuninitialized]
return (*p & 0x7f); /* XXX: p is a wild pointer */
^

2. 什么是悬空指针(dangling pointer)?

If a pointer still references the original memory after it has been freed, it is called a dangling pointer.

悬空指针是指针最初指向的内存已经被释放了的一种指针。 典型的悬空指针看起来是这样的,(图片来源是这里

如果两个指针(p1和p2)指向同一块内存区域, 那么free(p1)后,p1和p2都成为悬空指针。如果进一步将p1设置为NULL, 那么p2还是悬空指针。诚然,使用*p1会导致非法内存访问,但是使用*p2却会出现无法预料的结果,可谓防不胜防。例如:

o foo2.c

1 #include <stdlib.h>
2 int main(int argc, char *argv[])
3 {
4 int *p1 = (int *)malloc(sizeof (int));
5 int *p2 = p1; /* p2 and p1 are pointing to the same memory */
6 free(p1); /* p1 is a dangling pointer, so is p2 */
7 p1 = NULL; /* p1 is not a dangling pointer any more */
8 return (*p2 & 0x7f); /* p2 is still a dangling pointer */
9 }

3. 使用野指针和悬空指针的危害

无论是野指针还是悬空指针,都是指向无效内存区域(这里的无效指的是"不安全不可控")的指针。 访问"不安全可控"(invalid)的内存区域将导致"Undefined Behavior"。

关于"Undefined Behavior", 定义(参考来源:A Guide to Undefined Behavior in C and C++, Part 1)如下:

Anything at all can happen; the Standard imposes no requirements. The program
may fail to compile, or it may execute incorrectly (either crashing or silently
generating incorrect results), or it may fortuitously do exactly what the
programmer intended.

也就是说:任何可能都会发生。要么编译失败,要么执行得不正确(崩溃(e.g. segmentation fault)或者悄无声息地产生不正确的执行结果),或者偶尔会正确地产生程序员希望运行的结果。

4. 如何避免使用野指针和悬空指针?

如何避免使用野指针? 好办! 养成在定义指针后且在使用之前完成初始化的习惯就好。

然而,如何避免使用悬空指针,就比较麻烦了。 Solaris引入了ADI(Application Data Integrity)技术避免访问已经释放的内存区域,例如: 最新的SPARC平台已经支持KADI,一旦访问某个已经释放掉的内核内存区域,就会引发操作系统panic。这里简单介绍一下什么是ADI。

ADI (Application Data Integrity) is a software layer built on
MCD (Memory Corruption Detection), a SPARC hardware feature that provides
statistical protection against memory corruption errors such as buffer
overflows, use-after-frees, and use-after-reallocs. KADI allows kernel memory to use ADI.

这有效的避免了foo2.c示例代码中p2变成悬空指针还被使用的情况。 那么问题来了,如果没有ADI/KADI这种高大上的技术,如何避免使用悬空指针?

办法还是有的,直接避免不了就间接避免,那就是所谓的智能指针(smart pointer)。智能指针的本质是使用引用计数(reference counting)来延迟对指针的释放。

o 关于智能指针,请参考维基百科。

Smart pointers eliminate dangling pointers by postponing destruction until
an object is no longer in use.

o 关于引用计数,也请参考维基百科。

Reference counting is a technique of storing the number of references,
pointers, or handles to a resource such as an object, block of memory,
disk space or other resource.

特别说明: 引用计数不但在基于垃圾回收技术的内存管理中被广泛使用,而且在操作系统内核实现中也被广泛使用。 例如: 索引结点(inode)就有使用引用计数,从而保证了硬链接文件的实现。

 $ stat foo | egrep Inode
Device: 23000000002h/2405181685762d Inode: Links: $ ln foo foo9 && stat foo | egrep Inode
Device: 23000000002h/2405181685762d Inode: Links: $ rm -f foo && stat foo9 | egrep Inode
Device: 23000000002h/2405181685762d Inode: Links:

关于智能指针,C++11有很好的支持。 为了方便理解智能指针本质上是延迟释放内存,下面给出一个简单的C代码实现(使用proxy(代理) + refcnt(引用计数))。

o foo3.c

 #include <stdio.h>
#include <stdlib.h> typedef struct proxy_s {
int *object;
int refcnt;
} proxy_t; static int *create(proxy_t **proxy)
{
if (*proxy == NULL) {
proxy_t *p = (proxy_t *)malloc(sizeof (proxy_t));
p->object = (int *)malloc(sizeof (int));
p->refcnt = ;
*proxy = p;
} else {
((*proxy)->refcnt)++;
} return (*proxy)->object;
} static void destroy(proxy_t *proxy)
{
(proxy->refcnt)--; if (proxy->refcnt == ) {
free(proxy->object);
free(proxy);
}
} static void dump(proxy_t *proxy, int i)
{
printf("%02d\tobject: %p (0x%02x) refcnt: %d\n", i,
proxy->object, *(proxy->object), proxy->refcnt);
} int main(int argc, char *argv[])
{
proxy_t *proxy = NULL;
#define NEW() create(&proxy)
#define DELETE(p) do { destroy(proxy); p = NULL; } while (0)
#define DUMP(i) dump(proxy, i)
int *p1 = NEW();
int *p2 = NEW();
*p1 = 0xab; DUMP();
DELETE(p1); DUMP();
*p2 += 0x21; DUMP();
DELETE(p2); DUMP();
return ();
}

o 编译并执行

$ gcc -Wall -m32 -g -o foo3 foo3.c
$ ./foo3
object: 0x8d0d018 (0xab) refcnt:
object: 0x8d0d018 (0xab) refcnt:
object: 0x8d0d018 (0xcc) refcnt:
object: 0x8d0d010 (0x00) refcnt:

一句话总结: 明白了野指针和悬空指针的形成原理及其危害,那么在编程的时候就能有的放矢,写出安全可控的优质代码。

附: 关于wild pointer(野指针)和dangling pointer(悬空指针), 这里援引一段来自yahoo answer的解释以帮助更好的理解。

A dangling pointer is a pointer that used to point to a valid address but now
no longer does.
This is usually due to that memory location being freed up and
no longer available. There is nothing wrong with having a dangling pointer
unless you try to access the memory location pointed at by that pointer.
It is always best practice not to have or leave dangling pointers. A wild pointer is a pointer that has not been correctly initialized and
therefore points to some random piece of memory.
It is a serious error to have wild pointers.

最新文章

  1. VR发展的最大障碍在于内容?
  2. 2013长沙 G Graph Reconstruction (Havel-Hakimi定理)
  3. windows7远程桌面连接unbuntu12.04
  4. [课程设计]Scrum 1.7 多鱼点餐系统开发进度(点餐菜式内容添加及美化)
  5. MFC编程基础
  6. C#判断ip地址是否ping的通
  7. 对象指针与this指针
  8. Ubuntu14.04+CUDA6.5环境下神经网络工具包Deepnet配置
  9. url地址传参中文乱码处理
  10. aspose 生成word 简单的文档操作
  11. 对java中路径的一些理解
  12. from import
  13. Android笔记之 网络http通信
  14. 小米推送 简介 集成 MD
  15. LeetCode--100--相同的树
  16. jQuery 阻止冒泡和默认事件
  17. RabbitMQ上手记录–part 2 - 安装RabbitMQ
  18. java 或者 js 获取项目访问路径(域名)
  19. :c#的remoting里,CallContext.GetData获得的对象老是空的?该怎么处理
  20. DataGridView使用技巧六:冻结列或行

热门文章

  1. 精练代码:一次Java函数式编程的重构之旅
  2. 1001 害死人不偿命的(3n+1)猜想 (15 分)
  3. Docker OpenvSwitch 应用部署
  4. Python import模块
  5. 【CentOS 7】CentOS7与CentOS6 的区别
  6. Spring Boot 2集成Redis
  7. [shell]输出内容到剪切板
  8. Qt基础学习---滑动条之QSlider
  9. Linux下postgres9.4 版本的单机版安装小笔记
  10. 20175317 《Java程序设计》第二周学习总结