野指针的介绍

 什么是野指针?      优快云 Blog推出文章指数概念,文章指数是对Blog文章综合评分后推算出的,综合评分项分别是该文章的点击量,回复次数,被网摘收录数量,文章长度和文章类型;满分100,每月更新一次。

野指针,也就是指向不可用内存区域的指针。通常对这种指针进行操作的话,将会使程序发生不可预知的错误。
“野指针”不是NULL指针,是指向“垃圾”内存的指针。人们一般不会错用NULL指针,因为用if语句很容易判断。但是“野指针”是很危险的,if语句对它不起作用。野指针的成因主要有两种:

一、指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。

二、指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针。别看free和delete的名字恶狠狠的(尤其是delete),它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。通常会用语句if (p != NULL)进行防错处理。很遗憾,此时if语句起不到防错作用,因为即便p不是NULL指针,它也不指向合法的内存块。例:

char *p = (char *) malloc(100);

strcpy(p, “hello”);

free(p); // p 所指的内存被释放,但是p所指的地址仍然不变

if(p != NULL) // 没有起到防错作用

strcpy(p, “world”); // 出错

另外一个要注意的问题:不要返回指向栈内存的指针或引用,因为栈内存在函数结束时会被释放。
 

 


 

 

 


 

 


 

 


 

野指针,也就是指向不可用内存区域的指针。通常对这种指针进行操作的话,将会使程序发生不可预知的错误。首先请诸位看以下一段“危险”的C++代码:

void function( void )
{
char* str = new char[100];
delete[] str;
// Do something
strcpy( str, "Dangerous!!" );
}


之所以说其危险,是因为这是一段完全合乎语法的代码,编译的时候完美得一点错误也不会有,然而当运行到strcpy一句的时候,问题就会出现,因为在这之前,str的空间已经被delete掉了,所以strcpy当然不会成功。对于这种类似的情况,在林锐博士的书中有过介绍,称其为“野指针”。

那么,诸位有没有见过安全的“野指针”呢?下面请看我的一段C++程序,灵感来自优快云上的一次讨论。在此,我只需要C++的“类”,C++的其余一概不需要,因此我没有使用任何的C++标准库,连输出都是用printf完成的。

#include <stdio.h>

class CTestClass
{
public:
CTestClass( void );
int m_nInteger;
void Function( void );
};

CTestClass::CTestClass( void )
{
m_nInteger = 0;
}

void CTestClass::Function( void )
{
printf( "This is a test function./n" );
}

void main( void )
{
CTestClass* p = new CTestClass;
delete p;
p->Function();
}


OK,程序到此为止,诸位可以编译运行一下看看结果如何。你也许会惊异地发现:没有任何的出错信息,屏幕上竟然乖乖地出现了这么一行字符串:

This is a test function.

奇怪吗?不要急,还有更奇怪的呢,你可以把主函数中加上一句更不可理喻的:

((CTestClass*)NULL)->Function();


这仍然没有问题!!

我这还有呢,哈哈。现在你在主函数中这么写,倘说上一句不可理喻,那么以下可以叫做无法无天了:

int i = 888;
CTestClass* p2 = (CTestClass*)&i;
p2->Function();


你看到了什么?是的,“This is a test function.”如约而至,没有任何的错误。

你也许要问为什么,但是在我解答你之前,请你在主函数中加入如下代码:

printf( "%d, %d", sizeof( CTestClass ), sizeof( int ) );


这时你就会看到真相了:输出结果是——得到的两个十进制数相等。对,由sizeof得到的CTestClass的大小其实就是它的成员m_nInteger的大小。亦即是说,对于CTestClass的一个实例化的对象(设为a)而言,只有a.m_nInteger是属于a这个对象的,而a.Function()却是属于CTestClass这个类的。所以以上看似危险的操作其实都是可行且无误的。

现在你明白为什么我的“野指针”是安全的了,那么以下我所列出的,就是在什么情况下,我的“野指针”不安全:

在成员函数Function中对成员变量m_nInteger进行操作;
将成员函数Function声明为虚函数(virtual)。
以上的两种情况,目的就是强迫野指针使用属于自己的东西导致不安全,比如第一种情况中操作本身的m_nInteger,第二种情况中变为虚函数的Function成为了属于对象的函数(这一点可以从sizeof看出来)。

其实,安全的野指针在实际的程序设计中是几乎毫无用处的。我写这一篇文章,意图并不是像孔乙己一样去琢磨回字有几种写法,而是想通过这个小例子向诸位写明白C++的对象实例化本质,希望大家不但要明白what和how,更要明白why。李马二零零三年二月二十日作于自宅。


关于成员函数CTestClass::Function的补充说明



这个函数是一个普通的成员函数,它在编译器的处理下,会成为类似如下的代码:

void Function( const CTestClass * this ) // ①
{
printf("This is a test function./n");
}


那么p->Function();一句将被编译器解释为:

Function( p );


这就是说,普通的成员函数必须经由一个对象来调用(经由this指针激活②)。那么由上例的delete之后,p指针将会指向一个无效的地址,然而p本身是一个有效的变量,因此编译能够通过。并且在编译通过之后,由于CTestClass::Function的函数体内并未对这个传入的this指针进行任何的操作,所以在这里,“野指针”便成了一个看似安全的东西。

然而若这样改写CTestClass::Function:

void CTestClass::Function( void )
{
m_nInteger = 0;
}


那么它将会被编译器解释为:

void Function( const CTestClass * this )
{
this->m_nInteger = 0;
}


你看到了,在p->Function();的时候,系统将会尝试在传入的这个无效地址中寻找m_nInteger成员并将其赋值为0,剩下的我不用说了——非法操作出现了。

至于virtual虚函数,如果在类定义之中将CTestClass声明为虚函数:

class CTestClass
{
public:
// ...
virtual void Function( void );
};


那么C++在构建CTestClass类的对象模型时,将会为之分配一个虚函数表vptr(可以从sizeof看出来)。vptr是一个指针,它指向一个函数指针的数组,数组中的成员即是在CTestClass中声明的所有虚函数。在调用虚函数的时候,必须经由这个vptr,这也就是为什么虚函数较之普通成员函数要消耗一些成本的缘故。以本例而言,p->Function();一句将被编译器解释为:

(*p->vptr[1])( p ); // 调用vptr表中索引号为1的函数(即Function)③


上面的代码已经说明了,如果p指向一个无效的地址,那么必然会有非法操作。

备注:

①关于函数的命名,我采用了原名而没有变化。事实上编译器为了避免函数重载造成的重名情况,会对函数的名字进行处理,使之成为独一无二的名称。
②将成员函数声明为static,可以使成员函数不经由this指针便可调用。
③vptr表中,索引号0为类的type_info。


 

Trackback: http://tb.blog.youkuaiyun.com/TrackBack.aspx?PostId=765554 

<think>好的,我现在需要解释空指针指针和悬垂指针这三个概念。首先,我得确认自己对这三个术语的理解是否正确,然后按照用户的要求用中文清晰、结构分明地介绍它们。 用户还特别提到系统级指令,比如行内数学表达式要用$...$,独立公式用$$...$$,并且确保LaTeX语法正确。不过这三个概念可能不需要太多数学公式,但如果有相关的表达式,比如指针的声明或赋值,可能需要用行内格式。 首先,空指针。空指针通常是指向内存地址0的指针,在C/C++中一般用NULLnullptr表示。它的作用是明确表示指针不指向任何有效的对象。需要说明它的定义、用途以及注意事项,比如解引用空指针会导致程序崩溃。 然后是指针指针是指未初始化或指向已释放内存的指针。它的问题在于其指向的位置不确定,使用它会导致不可预测的行为,比如数据损坏或安全漏洞。需要解释其成因,比如未初始化、释放后未置空等。 接下来是悬垂指针。悬垂指针指的是指向曾经有效但已被释放的内存的指针。例如,在释放内存后,指针仍然保留原来的地址,这时候使用它会导致访问已释放的内存,可能引发错误。需要和指针区分,虽然有时这两个概念会被混用,但悬垂指针更特指指向被释放的内存的情况。 要确保结构清晰,可能每个指针类型都分点说明,包括定义、示例代码、危害和避免方法。用户要求回答真实可靠,所以需要确认每个定义的正确性,必要时参考权威资料或书籍。 现在考虑是否需要代码示例。比如空指针可以写int *ptr = nullptr;,指针的例子是int *ptr;(未初始化),悬垂指针的例子是在free或delete后未置空。这些代码可能需要用行内代码格式,但用户没有特别要求代码格式,可能用中文描述即可,或者用引号标出。 需要注意避免使用Markdown格式,所以分点说明时可能用数字和项目符号,但用户的结构要求中允许结构清晰,所以可能需要用自然段分述。 另外,用户提到尽量保证回答真实可靠,所以需要确保每个概念的解释准确无误。例如,指针是否包括悬垂指针?可能在某些资料中指针的定义更广,但通常悬垂指针指针的一种。不过为了明确区分,最好分开解释。 现在组织内容: 1. 空指针 - 定义:指向地址0的指针,明确表示不指向任何对象。 - 示例代码:int *p = nullptr; - 注意事项:解引用导致崩溃,用于条件判断等。 2. 指针 - 定义:未初始化或指向无效内存。 - 示例:未初始化的指针,或释放后未置空。 - 危害:不可预测行为,如数据损坏。 - 避免方法:初始化指针,释放后置空。 3. 悬垂指针 - 定义:指向已释放内存的指针。 - 示例:释放后继续使用指针。 - 危害:访问无效内存,可能导致错误。 - 避免方法:释放后置空,使用智能指针。 最后比较三者区别,帮助用户理解不同点。例如,空指针是故意设置为空,而指针和悬垂指针属于错误情况。悬垂指针指针的一种特殊情况。 需要确保语言简洁,避免技术术语过于复杂,适合不同层次的读者理解。同时检查是否遵循了用户的格式要求,比如行内数学表达式是否正确,是否有独立公式等。不过这三个概念可能较少涉及数学公式,但如果有指针地址的表示,可能需要用$0x00000000$之类的格式,用行内LaTeX。 例如,空指针指向$0x0$地址,可以写成$0x00000000$(假设是32位系统)。但可能不需要,因为用户可能更关心概念而非具体地址值。 总结结构: 1. 空指针 - 定义 - 示例 - 危害 - 解决方法 2. 指针 - 同上结构 3. 悬垂指针 - 同上结构 然后可能加一个对比表格或总结段落,但用户要求结构清晰,逐步解决问题,所以可能分点更合适。 现在开始组织语言,确保每个部分明确,使用中文,避免术语混淆。</think>以下是对空指针指针和悬垂指针的详细解释: --- ### **1. 空指针Null Pointer)** - **定义**:空指针是明确指向“无”的指针,通常被赋值为内存地址 $0$(表示为 `NULL` 或 `nullptr`)。它表明指针当前未指向任何有效对象。 - **示例**: ```cpp int *ptr = nullptr; // C++11 后推荐使用 nullptr ``` - **特点与用途**: - 用于条件判断(如 `if (ptr == nullptr)`)。 - 避免未初始化的指针随机指向内存。 - **风险**:解引用空指针(如 `*ptr = 5`)会导致程序崩溃(如段错误)。 --- ### **2. 指针(Wild Pointer)** - **定义**:指针是未初始化或指向未知内存区域的指针。它的值不确定,可能指向任意地址(如垃圾值或已释放的内存)。 - **成因**: - **未初始化**:`int *ptr;`(未赋初值)。 - **未置空后释放**:释放内存后未将指针置空。 - **示例**: ```cpp int *ptr; // 未初始化,成为指针 *ptr = 10; // 危险操作! ``` - **危害**:操作指针可能导致数据损坏、程序崩溃或安全漏洞(如内存泄漏)。 - **避免方法**: - 初始化指针(如 `int *ptr = nullptr;`)。 - 释放内存后立即置空指针。 --- ### **3. 悬垂指针(Dangling Pointer)** - **定义**:悬垂指针指向的内存已被释放,但指针仍保留原地址。它是指针的一种特殊情况。 - **成因**: - **释放后未置空**: ```cpp int *ptr = new int(5); delete ptr; // 内存释放,但 ptr 仍指向原地址 *ptr = 10; // 危险!操作已释放的内存 ``` - **局部变量作用域结束**: ```cpp int* func() { int x = 5; return &x; // x 的生命周期在函数结束后结束,返回的指针成为悬垂指针 } ``` - **危害**:访问已释放内存可能导致程序行为不可预测(如数据覆盖或崩溃)。 - **避免方法**: - 释放内存后立即置空指针(如 `delete ptr; ptr = nullptr;`)。 - 避免返回局部变量的地址。 - 使用智能指针(如 `std::shared_ptr`、`std::unique_ptr`)自动管理内存。 --- ### **三者对比** | **类型** | **特点** | **风险** | **解决方法** | |------------|----------------------------------------|---------------------------|---------------------------| | **空指针** | 明确指向 $0$,表示“无” | 解引用导致崩溃 | 初始化时赋值为 `nullptr` | | **指针** | 未初始化或指向无效地址 | 不可预测行为 | 初始化指针,释放后置空 | | **悬垂指针**| 指向已释放的内存 | 访问无效内存 | 释放后置空,使用智能指针 | --- ### **总结** - **空指针**是“主动安全”的设计,用于明确表示无指向。 - **指针**和**悬垂指针**是编程错误,需通过初始化、及时置空和内存管理工具规避。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值