野(wild)指针与悬空(dangling)指针
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.
野(wild)指针与悬空(dangling)指针的更多相关文章
- c++野(wild)指针与悬空(dangling)指针
re 1.https://www.cnblogs.com/idorax/p/6475941.html end
- C++中指针常量和常量指针的区别
在C++学习使用过程中,每个人都不可避免地使用指针,而且都或多或少的接触过常量指针或指针常量,但是对这两个的概念还是很容易搞糊涂的. 本文即是简单描述指针常量和常量指针的区别. 常量指针 定义: 又叫 ...
- C与指针(结构体指针,函数指针,数组指针,指针数组)定义与使用
类型 普通指针 指针数组(非指针类型) 数组指针 结构体指针 函数指针 二重指针 定义方式 int *p; int *p[5]; int (*p)[5]; int a[3][5]; struct{.. ...
- 你必须知道的指针基础-7.void指针与函数指针
一.不能动的“地址”—void指针 1.1 void指针初探 void *表示一个“不知道类型”的指针,也就不知道从这个指针地址开始多少字节为一个数据.和用int表示指针异曲同工,只是更明确是“指针” ...
- 不可或缺 Windows Native (18) - C++: this 指针, 对象数组, 对象和指针, const 对象, const 指针和指向 const 对象的指针, const 对象的引用
[源码下载] 不可或缺 Windows Native (18) - C++: this 指针, 对象数组, 对象和指针, const 对象, const 指针和指向 const 对象的指针, con ...
- 指针数组 vs 数组指针
指针数组,故名思义,就是指针的数组,数组的元素是指针: 数组指针,同样,就是直想数组的指针. 简单举例说明: int *p[2]; 首先声明了一个数组,数组的元素是in ...
- C++中,指针数组和数组指针
这俩兄弟长得实在太像,以至于经常让人混淆.然而细心领会和甄别就会发现它们大有不同. 前者是指针数组,后者是指向数组的指针.更详细地说. 前: 指针数组;是一个元素全为指针的数组. 后: 数组指针;可以 ...
- [Reprint]C++普通函数指针与成员函数指针实例解析
这篇文章主要介绍了C++普通函数指针与成员函数指针,很重要的知识点,需要的朋友可以参考下 C++的函数指针(function pointer)是通过指向函数的指针间接调用函数.相信很多人对指向一般 ...
- 深入理解C语言中的指针与数组之指针篇
转载于http://blog.csdn.net/hinyunsin/article/details/6662851 前言 其实很早就想要写一篇关于指针和数组的文章,毕竟可以认为这是C语言的根本 ...
随机推荐
- 即时通讯(IV)
数据流
- Python文件学习
Python文件学习 文章 Python文件学习 open函数 基本的用法模式:file_object=open('',access_mode='r',buffering=-1) 其中access_m ...
- LATCH_VARIANT = Magna compile
after: LATCH_VARIANT = Magna in m_project_cfg.mak of PG_.. 1. first error: SED tmp__.dep/bin/sh: / ...
- Docker 部署 elk + filebeat
Docker 部署 elk + filebeat kibana 开源的分析与可视化平台logstash 日志收集工具 logstash-forwarder(原名lubmberjack)elastics ...
- 浅析vue实例的生命周期(生命周期钩子)
“每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听.编译模板.将实例挂载到 DOM 并在数据变化时更新 DOM 等” ,在不同的生命周期内会经历不同的钩子函数(生命周期 ...
- Python新手入门英文词汇笔记(1-2)
英文词汇总结一.循环1.for…in…循环的使用2.while…循环的使用本节英文单词与中文释义:1.for:因为2.while:当…时…3.range:范围4.sep(separate):分隔5.f ...
- Learning-Python【1】:交互式环境与变量的使用
一.执行Python程序的两种方式 1. 交互式环境,打开cmd,输入python2或python3,显示提示符 “>>>”. 特点:输出代码立即执行 优点:调试程序方便 缺点:无法 ...
- windows(xshell)免密码登录
windows(xshell)免密码登录 第一步生成密钥文件 打开xshell 选个工具栏的工具如下图 选择密钥类型和长度,如下图 密钥会自动生成,如下图,点击下一步即可 设置给密钥加密的密码也可以不 ...
- word->excel数据处理
朋友发来一个word文件,里面的数据没有分割,想分割后放到excel统计 通常遇到这种数据,首先想到每一列数据有没有什么特征 类似这种数据,一种办法是按位数截取,mid函数,或者按第一次出现数字的方式 ...
- bug:进程可调用函数而子线程调用报错
在调试摄像头时遇到问题:在主进程里调用下述函数能够成功,但在子线程里创建时总是失败,错误打印为 sched: RT throttling activated. UniqueObj<OutputS ...