C迷途指针详解

本文较为详尽的讲述了C语言的迷途指针,分析了其概念、原理与检测方法。分享给大家供大家参考。具体如下:

一般来说,在计算机编程领域中,迷途指针,或称悬空指针、野指针,指的是不指向任何合法的对象的指针。

当所指向的对象被释放或者收回,但是对该指针没有作任何的修改,以至于该指针仍旧指向已经回收的内存地址,此情况下该指针便称迷途指针。若操作系统将这部分已经释放的内存重新分配给另外一个进程,而原来的程序重新引用现在的迷途指针,则将产生无法预料的后果。因为此时迷途指针所指向的内存现在包含的已经完全是不同的数据。通常来说,若原来的程序继续往迷途指针所指向的内存地址写入数据,这些和原来程序不相关的数据将被损坏,进而导致不可预料的程序错误。这种类型的程序错误,不容易找到问题的原因,通常会导致段错误(Linux系统中)和一般保护错误(Windows系统中)。如果操作系统的内存分配器将已经被覆盖的数据区域再分配,就可能会影响系统的稳定性。

某些编程语言允许未初始化的指针的存在,而这类指针即为野指针。野指针所导致的错误和迷途指针非常相似,但野指针的问题更容易被发现。

迷途指针的成因

在很多编程语言中(如C语言)从内存中删除一个对象或者返回时删除栈帧后,并不会改变相关的指针的值。该指针仍然指向原来的内存地址,即使引用已经删除,现在也可能已经被其它进程使用了。

一个直接的例子,如下所示:

{
  char *cp = NULL;
  /* ... */
  {
    char c;
    cp = &c;
  } /* c falls out of scope */
   /* cp is now a dangling pointer */
}

上述问题的解决方法是在该部分程序退出之前立即给CP赋0值(NULL)。另一个办法是保证CP在没有初始化之前,将不再被使用。

迷途指针经常出现在混杂使用malloc() 和 free() 库调用: 当指针指向的内存释放了,这时该指针就是迷途的。和前面的例子一样,一个避免这个错误的方法是在释放它的引用后将该指针的值重置为NULL,如下所示:

#include <stdlib.h>
{
  char *cp = malloc ( A_CONST );
  /* ... */
  free ( cp );   /* cp 现在变成了一个悬空指针 */
  cp = NULL;    /* cp 现在不是悬空了 */
  /* ... */
}

有个常见的错误是当返回一个基于栈分配的局部变量的地址时,一旦调用的函数返回,分配给这些变量的空间将被回收,此时它们拥有的是"垃圾值"。

int * func ( void )
{
  int num = 1234;
  /* ... */
  return &num;
}

在调用func之后一段时间,尝试从该指针中读取num的值,可能仍然能够返回正确的值(1234),但是任何接下来的函数调用会覆盖原来的栈为num分配的空间。这时,再从该指针读取num的值就不正确了。如果要使一个指向num的指针都返回正确的num值,则需要将该变量声明为static。

野指针的产生

野指针指的是还没有初始化的指针。严格地说,编程语言中每个指针在初始化前都是野指针。

一般于未初始化时便使用指针就会产生问题。大多数的编译器都能检测到这一问题并警告用户。

int f(int i)
{
  char* cp;  //cp 是野指针
  static char* scp; //scp 不是野指针,静态变量自动初始化为0并保留它们的值
//使用这种特征可能被认为坏的编程风格
}

迷途指针导致的安全漏洞

如同缓存溢出错误,迷途指针/野指针这类错误经常会导致安全漏洞。 例如,如果一个指针用来调用一个虚函数,由于vtable指针被覆盖了,因此可能会访问一个不同的地址(指向被利用的代码)。或者,如果该指针用来写入内存,其它的数据结构就有可能损坏了。一旦该指针成为迷途指针,即使这段内存是只读的,仍然会导致信息的泄露(如果感兴趣的数据放在下一个数据结构里面,恰好分配在这段内存之中)或者访问权限的增加(如果现在不可使用的内存恰恰被用来安全检测).

避免迷途指针的错误

避免迷途指针,有一种受欢迎的方法——即使用智能指针(Smart pointer)。智能指针使用引用计数来回收对象。一些其它的技术包括tombstone法和locks-and-keys法。

另外,可以使用 DieHard 内存分配器,它虚拟消除了类似其它内存错误(不合法或者两次释放内存)的迷途指针错误。

还有一种办法是贝姆垃圾收集器,一种保守的垃圾回收方法,能够替代C和C++中标准内存分配函数。这种方法完全消除了迷途指针的错误,通过去除内存释放的函数代之以垃圾回收器完成对象的回收。

像Java语言,迷途指针这样的错误是不会发生的,因为Java中没有明确地重新分配内存的机制。而且垃圾回收器只会在对象的引用数为零时重新分配内存。

迷途指针的检测

为了能发现迷途指针,一种普遍的编程技术——一旦指针指向的内存空间被释放,就立即把该指针置为空指针或者为一个非法的地址。当空指针被重新引用时,此时程序将会立即停止,这将避免数据损坏或者某些无法预料的后果。这将使接下来的编程过程产生的错误变得容易发现和解决了。这种技术在该指针有多个复制时就无法起到应有的作用了。

一些调试器会自动地用特定的模式来覆盖已经释放的数据,如0xDEADBEEF (Microsoft's Visual C/C++ 调试器,例如,根据哪种类型被释放采用 0xCC,0xCD 或者 0xDD)。这种方法通过将数据无用化,来防止已经释放的数据重新被使用。这种方法的作用是非常显著的 (该模式可以帮助程序来区分哪些内存是刚刚释放的)。

某些工具,如Valgrind, Mudflap或者 LLVM可以用来检测迷途指针的使用。

总的来说,迷途指针对C程序安全性影响巨大,是C程序员必须谨慎处理的一个问题。相信本文所述对大家C程序设计有一定的借鉴价值。

(0)

相关推荐

  • c++中引用和指针的区别和联系

    C++中的引用和指针 ★ 相同点: 1. 都是地址的概念:指针指向一块内存,它的内容是所指内存的地址:引用是某块内存的别名(java中的引用其实也是别名的意思). ★ 区别: 1. 指针是一个实体,而引用仅是个别名:2. 引用使用时无需解引用(*),指针需要解引用:3. 引用只能在定义时被初始化一次,之后不可变:指针可变: 引用"从一而终" 4. 引用没有 const,指针有 const,const 的指针不可变:5. 引用不能为空,指针可以为空:6. "sizeof 引用&

  • C语言入门之指针用法教程

    本文针对C语言初学者详细讲述了指针的用法,并配以实例进行说明.具体分析如下: 对于C语言初学者来说,需要明白指针是啥?重点就在一个"指"上.指啥?指的地址.啥地址?内存的地址. 上面说明就是指针的本质了. 这里再详细解释下.数据存起来是要存在内存里面的,就是在内存里圈出一块地,在这块地里放想放的东西.变量关心的是这块地里放的东西,并不关心它在内存的哪里圈的地:而指针则关心这块地在内存的哪个地方,并不关心这块地多大,里面存了什么东西. 指针怎么用呢?下面就是基本用法: int a, b,

  • c++将引用或者是指针作为函数参数实现实参的运算

    C++增加引用类型, 主要是把它作为函数参数,以扩充函数传递数据的功能,较指针参数来得更加安全直观.将引用作为参数传递的时候,实参初始化形参的时候不分配内存空间,也不调用拷贝构造函数,因此更加能够提高运算的性能.所以我们应该尽可能地使用引用,而非指针,但是应该要注意,因为局部变量具有自己短暂的生命周期,因此不能够返回对一个局部变量的引用. 引用通常是在被定义的时候被初始化,但是当它作为参数的时候,则是在被调用的时候被初始化.这时候对引用所做的改变就是对被引用的变量所做的改变. 引用对变量的访问是

  • C语言安全之数组长度与指针实例解析

    1.C语言编码需要保证变长数组的长度参数位于合法范围之内 例如以下代码: void func(size_t s) { int vla[s]; /*...*/ } /*...*/ func(size); /*...*/ 解决方案如下: enum {MAX_ARRAY = 1024}; void func(size_t s) { if(s < MAX_ARRAY && s != 0) { int vla[s]; /*...*/ } else { //错误处理 } } /*...*/ fu

  • C++普通函数指针与成员函数指针实例解析

    C++的函数指针(function pointer)是通过指向函数的指针间接调用函数.相信很多人对指向一般函数的函数指针使用的比较多,而对指向类成员函数的函数指针则比较陌生.本文即对C++普通函数指针与成员函数指针进行实例解析. 一.普通函数指针 通常我们所说的函数指针指的是指向一般普通函数的指针.和其他指针一样,函数指针指向某种特定类型,所有被同一指针运用的函数必须具有相同的形参类型和返回类型. int (*pf)(int, int); // 声明函数指针 这里,pf指向的函数类型是int (

  • c++传递函数指针和bind的示例

    复制代码 代码如下: #include <algorithm>class TestClass{public: int Sub(int x, int y) {  return y - x; } void InitAndTest() {  PrintWithClassMemberFunction(&TestClass::Sub);  PrintWithClassPointer(this); } // call: PrintWithClassMemberFunction(&TestC

  • c++函数指针和回调函数示例

    1.函数指针 函数指针是一个指针,只是这个指针它不像普通的指针指向是是一个变量,此时它指向的是一个函数,也就是它存储的是一个函数的地址,如果我们改变它的值,让它所指向的地址由指向funA转变为指向funB,那么这个函数指针的作用就改变了. 2.回调函数 什么是回调函数呢?回调函数其实就是一个通过函数指针调用的函数!假如你把A函数的指针当作参数传给B函数,然后在B函数中通过A函数传进来的这个指针调用A函数,这就是回调机制.B函数就是回调函数. 3.函数指针的使用 3.1函数指针声明 typedef

  • C++指针数组、数组指针、数组名及二维数组技巧汇总

    本文较为详细的分析了关于理解C++指针数组,数组指针,数组名,二维数组的一些技巧.是比较重要的概念,相信对于大家的C++程序设计有一定的帮助作用. 一.关于数组名 假设有数组: int a[3] = {1, 2, 3} 1.数组名代表数组第一个元素的地址,注意,不是数组地址(虽然值相等),是数组第一个元素地址,a 等同于 &a[0]; a+1是第二个元素的地址.比第一个元素地址a(或者&a[0])超出了一个整型指针的大小,在这里是4个字节(byte) cout << a <

  • C++智能指针实例详解

    本文通过实例详细阐述了C++关于智能指针的概念及用法,有助于读者加深对智能指针的理解.详情如下: 一.简介 由于 C++ 语言没有自动内存回收机制,程序员每次 new 出来的内存都要手动 delete.程序员忘记 delete,流程太复杂,最终导致没有 delete,异常导致程序过早退出,没有执行 delete 的情况并不罕见. 用智能指针便可以有效缓解这类问题,本文主要讲解参见的智能指针的用法.包括:std::auto_ptr.boost::scoped_ptr.boost::shared_p

  • C++指向函数的指针实例解析

    通常来说C++函数指针是指指向函数的指针,而非指向对象的指针.就像其他指针一样,函数指针也指向某个特定的类型.函数类型由其返回类型以及形参表确定,而与函数名无关. 定义: char (*fP)(char,int); 赋值: char function(char i,int j) { } fp=function; 调用 (*fp)(10,100); type char (*FUN)(char,int);//类型定义 FUN fp ;//定义fp为指向函数的指针 volatile的用法和作用: co

  • C++编程小心指针被delete两次

    在C++类中,有时候会使用到传值调用(即使用对象实体做参数),当遇到这种情况,可要小心了!尤其是当你所传值的对象生命周期较长,而非临时对象(生命周期段)的时候.来看看下面的情况: #include <iostream> using namespace std; class Text { private: char * str; public: Text(){str = new char[20]; ::memset(str,0,20); } void SetText(char * str) {

  • C++指向函数的指针用法详解

    本文以实例形式展示了C++指向函数的指针用法,是深入学习C++所必须掌握的关键知识点.分享给大家供大家参考之用.具体方法如下: 函数指针 现来看看以下声明语句,看看其含义: float (*h(int, void (*)(int)))(int); 以下是一个变量指针的定义语句: float* pf; 以下是一个普通函数的声明语句: float f(); 请看以下声明语句: float* g(); 因为()的优先级高于*, 所以相当于: float* (g()); g是一个函数, 返回值为floa

随机推荐