野指针(悬挂指针)

97 篇文章 7 订阅

野指针(悬挂指针)


野指针:一个指针变量指向一个错误的地址,即,这个指针变量中存着的值是一个污数据,是无效的,错误的,无用的。
野指针即悬挂指针。

野指针的核心概念:一个指针变量指向的空间是不可访问的,也不应该被指针操作者访问,即,指针变量中的数据是 污数据

出现野指针的情况:

1.指针变量在定义时未初始化;

2.指针指向的对象或空间已经被delete或free掉了,并且未及时置指针变量值为NULL;

3.指针指向的对象在此作用域中已无效(通常由于此对象已被释放)。

针对第一种情况:

指针变量如果是在栈上(局部)创建,而不是在堆上(全局)创建,那么其值通常是不可靠的,极有可能其值不为NULL,而是指向一个位置的内存空间,对这样的指针进行操作是完全无意义的,且可能破坏某些重要的数据信息,毕竟,你不知道它到底指向哪里;

#include <iostream>

using namespace std;

int main(int argc, char* argv[])
{
    void *p;
    cout<<p<<endl;
    return 0;
}

g++ -o main main.C

./main

0

怎么会是0?这种情况可能是编译器表现出来的,常使人迷惑。

请谨记,栈上声明的指针,若不初始化,其值都是不可靠的,极有可能不是NULL,任何时候也不要依赖于编译器去做这件事情。

针对第二种情况:

#include <iostream>

using namespace std;

int main(int argc, char* argv[])
{
    int *p = (int *)malloc(20);
    *(p) = 0;
    *(p+1) = 1;
    *(p+2) = 2;
    *(p+3) = 3;
    *(p+4) = 4;
    for (int i = 0; i<5; i++)
        cout<<*(p+i)<<endl;
    free(p);
    cout<<"after free"<<endl;
    for (int i = 0; i<5; i++)
        cout<<*(p+i)<<endl;
    return 0;
}

g++ -o main main.C

./main

0
1
2
3
4
after free
0
0
2
3
4

p指向的空间是malloc出来的,在free后,其指向的空间,已被操作系统认为是可被重用的空间,可能下一次malloc,还是这个地址开始的空间。

在free这个空间之后,这个空间的值就得不到了保证,随时可能被别的进程所操作(读/写),故此处所存的数据也不再有效,不可靠。

p这个时候,也是一种野指针。

delete同理,不再赘述。

注:

对于c++,一个类中可以有成员变量是一个指针,但是需要注意,在对象之间复制和赋值时可能出现隐患—请看下面的几个小例子:

【例1】

#include <iostream>

using namespace std;

class A
{
public:
	A():p(NULL){}
	~A(){free(p);}
	void mymalloc();
	void myfree();
	void setVal(int v);
	int getVal();
private:
	int *p;
};

void A::mymalloc()
{
	p = (int *)malloc(sizeof(int)*1);
}

void A::myfree()
{
	free(p);
}

void A::setVal(int v)
{
	*p = v;
}

int A::getVal()
{
	return *p;
}

int main(int argc, char* argv[])
{
	A obj1;
	obj1.mymalloc();
	obj1.setVal(6);
	cout<<"obj1 val is :"<<obj1.getVal()<<endl;
	A obj2 = obj1;
	cout<<"obj2 val is :"<<obj2.getVal()<<endl;
	return 0;
}

编译后运行。

结果报错:

obj1 val is :6
obj2 val is :6
*** glibc detected *** ./main: double free or corruption (fasttop): 0x0000000017361010 ***
======= Backtrace: =========
/lib64/libc.so.6[0x31c987174f]
/lib64/libc.so.6(cfree+0x4b)[0x31c987597b]
./main(__gxx_personality_v0+0x2f6)[0x400b26]
./main(__gxx_personality_v0+0x283)[0x400ab3]
/lib64/libc.so.6(__libc_start_main+0xf4)[0x31c981d9c4]
./main(__gxx_personality_v0+0x49)[0x400879]
======= Memory map: ========
00400000-00401000 r-xp 00000000 fd:00 4243371                            /home/mytmp/20170330/main
00601000-00602000 rw-p 00001000 fd:00 4243371                            /home/mytmp/20170330/main
17361000-17382000 rw-p 17361000 00:00 0                                  [heap]
31c9000000-31c901c000 r-xp 00000000 fd:00 1340605                        /lib64/ld-2.5.so

……

./main: double free or corruption

说的很明白了,两次free。

由于obj2是完全由obj1那里复制来的,当然obj2中私有成员变量同obj1中是一样的,指向同一片malloc出来的内存空间。

在main函数结束时,两个A对象将会被析构,在析构中free这个指针时,第一个析构没啥问题,第二个析构可就出问题了:

free一个NULL,几次都OK,但是你不能可一个具体的值free呀。

很坑吧。。。

同样需要注意的是,比如:

【例2】

// A类定义同上

void myfunction(A a){}

int main(int argc, char* argv[])
{
    A obj1;
    obj1.mymalloc();
    obj1.setVal(6);
    cout<<"obj1 val is :"<<obj1.getVal()<<endl;
    myfunction(obj1);
    return 0;
}

额,运行时还报错,同上:

*** glibc detected *** ./main: double free or corruption (fasttop): 0x0000000005c98010 ***

原因在于,传入myfunction时,会复制一个一模一样的对象到myfunction函数栈上,当函数结束时,函数栈上的对象都会自动析构,也就会释放此对象所的空间;
在main函数结束时,欲再一次free,结果同上。

修改方法:

void myfunction(A &a){}

再看看下面的例子:

【例3】

#include <iostream>

using namespace std;

class A
{
public:
	A():p(NULL){}
	~A(){free(p);cout<<"deconstructor called"<<endl;}
	void mymalloc();
	void myfree();
	void setVal(int v);
	int getVal();
private:
	int *p;
};

void A::mymalloc()
{
	p = (int *)malloc(sizeof(int)*1);
}

void A::myfree()
{
	free(p);
}

void A::setVal(int v)
{
	*p = v;
}

int A::getVal()
{
	return *p;
}

A getA()
{
	A a;
	a.mymalloc();
	a.setVal(17);
	cout<<"a.getValue is "<<a.getVal()<<endl;
	return a;
}

int main(int argc, char* argv[])
{
	A a = getA();
	return 0;
}

会是什么结果呢?为什么会出现这样的结果呢?

总之:在一个类中声明其持有指针,需要谨慎对待,谨慎使用呀!尤其在函数实参形参结合之时。

针对第三种情况:

#include <iostream>

using namespace std;

int *p = NULL;

void setP()
{
    int num = 10;
    p = &num;
}

int main(int argc, char* argv[])
{
    setP();
    cout<<p<<endl;
    cout<<*p<<endl;
    return 0;
}

g++ -o main main.C

./main

0x7fff55a791dc
49

在main中,先调用了setP这个函数,在此函数中定义了局部变量num,注意,此局部变量是在setP这个函数栈上的,当setP调用返回后,num这个变量就会被销毁。

返回到main中,num已不在其作用域中,此时,一个指针变量指向其在栈上的位置,还有意义吗?无意义。

可能有一种情况:输出时还是10,这是由于,系统暂时没有对这块内存操作,但是谁也不能保证系统啥时候操作它,你说呢?


上述三种,基本上是野指针出现的各种成因。

野指针一般不易被发现。

通常大家会使用:

    if (p != NULL)
        //do something

这样的条件语句来判断其是否有值来避免操作空指针,但是很明显,其不能防备野指针,野指针有值,但是值确是错误的,无效的。

野指针的解决办法:

通常最终要的一点,也是最核心的一点,就是养成良好的变成习惯,譬如:

    free(p);
    p = NULL;

这是最常见的形式,应该尽可能地,当free时,就想起来要置p为NULL,如同你malloc时就轻微紧张地想起要free,形成一条线。

尽可能地将其作为一种原子操作,即使在java这样的不须太过关心指针的语言中,在对象消亡后,也应该及时地置NULL,这是一种习惯。

如上,我们可以把它封装成一个宏:

#include <iostream>

using namespace std;

#define xfree(ptr) {free(ptr);ptr=NULL;}

int main(int argc, char* argv[])
{
    int *p = (int *)malloc(20);
    *(p) = 0;
    *(p+1) = 1;
    *(p+2) = 2;
    *(p+3) = 3;
    *(p+4) = 4;
    for (int i = 0; i<5; i++)
        cout<<*(p+i)<<endl;
    xfree(p);
    cout<<"after free"<<endl;
    cout<<"p is :"<<p<<endl;
    if (p != NULL)
        for (int i = 0; i<5; i++)
            cout<<*(p+i)<<endl;
    return 0;
}

关键在于:
#define xfree(ptr) {free(ptr);ptr=NULL;}
这是一个宏定义。

注:free一个空指针不会出现问题。(free(NULL)总是成功的,不出错的。)

再者可以使用智能指针,说白了,智能指针就是一个类,使用引用计数来去“智能地”判断,下次有时间详细总结~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值