野指针(悬挂指针)
野指针:一个指针变量指向一个错误的地址,即,这个指针变量中存着的值是一个污数据,是无效的,错误的,无用的。
野指针即悬挂指针。
野指针的核心概念:一个指针变量指向的空间是不可访问的,也不应该被指针操作者访问,即,指针变量中的数据是
污数据。
出现野指针的情况:
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 = #
}
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)总是成功的,不出错的。)
再者可以使用智能指针,说白了,智能指针就是一个类,使用引用计数来去“智能地”判断,下次有时间详细总结~