“野指针”不是NULL指针,是指向“垃圾”内存的指针。人们一般不会错用NULL指针,因为用if语句很容易判断。但是“野指针”是很危险的,if语句对它不起作用。
“野指针”的成因主要有两种:
(1)指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。例如
char *p = NULL;
char *str = (char *) malloc(100);
(2)指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针。参见7.5节。
别看free和delete的名字恶狠狠的(尤其是delete),它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。
用调试器跟踪示例7-5,发现指针p被free以后其地址仍然不变(非NULL),只是该地址对应的内存是垃圾,p成了“野指针”。如果此时不把p设置为NULL,会让人误以为p是个合法的指针。
如果程序比较长,我们有时记不住p所指的内存是否已经被释放,在继续使用p之前,通常会用语句if (p !=
NULL)进行防错处理。很遗憾,此时if语句起不到防错作用,因为即便p不是NULL指针,它也不指向合法的内存块。
char *p = (char *) malloc(100);
strcpy(p, “hello”);
free(p); // p 所指的内存被释放,但是p所指的地址仍然不变
…
if(p !=
NULL) // 没有起到防错作用
{
strcpy(p,
“world”); // 出错
}
示例7-5 p成为野指针
(3)指针操作超越了变量的作用范围。这种情况让人防不胜防,示例程序如下:
class A
{ public:
void Func(void){ cout << “Func of
class A” << endl; }
};
void Test(void)
{
A *p;
{
A a;
p =
&a; // 注意 a 的生命期
}
p->Func(); // p是“野指针”
}
函数Test在执行语句p->Func()时,对象a已经消失,而p是指向a的,所以p就成了“野指针”。但奇怪的是我运行这个程序时居然没有出错,这可能与编译器有关。
Lufer 发表于2006-01-12 7:15 AM IP:
222.183.87.*
函数Test在执行语句p->Func()时,对象a已经消失,而p是指向a的,所以p就成了“野指针”。但奇怪的是我运行这个程序时居然没有出错,这可能与编译器有关。
TO:楼主
我认为不是跟编译器有关,而是p指向的内存区域的内容没有改变,所以仍然可以调用func函数,这样存在潜在的危险,但是你的程序中没有表现出来。即使表现出来时,运行也不会有错,只是逻辑上会出错。楼主觉得呢?
leiXure 发表于2006-01-12 9:20 AM IP: 211.162.235.*
同意Lufer
稍稍改一下你的类就会发现问题
class A
{
public:
void Func(void) { cout << m_msg
<< endl; }
private:
std::string m_msg;
};
一凡 发表于2006-01-12 9:37 AM IP:
#define SafeFree(p) do {if(p) {free(p);p=NULL;}} while(0)
Rayz 发表于2006-01-12 10:16 AM IP:
219.142.170.*
我也举3个例子,分别属于三种情况
1.
class1 * p;
if(p == NULL)
{
P = new class1;
}
else
{
p->Fun1(); //崩溃
}
2.
class1 * p = new class1;
//do sth.... then delete
delete p; //虽然delete,但指针仍不会为空,此时应 P
//P = NULL;
if(p == NULL)
{
P = new class1;
}
p->Fun1(); //崩溃
3. class1
{
public:
void Fun1();
void Fun2();
private:
class1 * p;
}
void class1::Fun1()
{
if(this->p) //其实我想写 if(this == p) ,笔误,呵呵
{
this->Fun2(); //很明显,容易崩溃,呵呵
}
}
fengqingyang2008 发表于2006-01-12 2:48
PM IP: 61.242.107.*
p->Func();
这个是不会出错的,因为你的函数并没有访问A这个类的任何一个数据成员或虚函数指针。
A* ptr=0;
ptr->Func();
这样也是不会出错的
C语言中delete与delete []的不同
下面的语句有什么错?
string *stringarray = new string[100];
...
delete stringarray;
一切好象都井然有序——一个new对应着一个delete——然而却隐藏着很大的错误:程序的运行情况将是不可预测的。至少,stringarray指向的100个string对象中的99个不会被正确地摧毁,因为他们的析构函数永远不会被调用。
用new的时候会发生两件事。首先,内存被分配,然后,为被分配的内存调用一个或多个构造函数。用delete的时候,也有两件事发生:首先,为将被释放
的内存调用一个或多个析构函数,然后,释放内存。对于
delete来说会有这样一个重要的问题:内存中有多少个对象要被删除?答案决定了将有多少个析构函数会被调用。
这个问题简单来说就是:要被删除的指针指向的是单个对象呢,还是对象数组?这只有你来告诉delete。如果你在用delete时没用括号,delete就会认为指向的是单个对象,否则,它就会认为指向的是一个数组:
string *stringptr1 = new string;
string *stringptr2 = new string[100];
...
delete stringptr1;// 删除一个对象
delete [] stringptr2;// 删除对象数组
如果你在stringptr1前加了"[]"会怎样呢?答案是:那将是不可预测的;如果你没在stringptr2前没加上"[]"又会怎样呢?答案也
是:不可预测。而且对于象int这样的固定类型来说,结果也是不可预测的,即使这样的类型没有析构函数。所以,解决这类问题的规则很简单:如果你调用
new时用了[],调用delete时也要用[]。如果调用new时没有用[],那调用delete时也不要用[]。
在写一个包含指针数据成员,并且提供多个构造函数的类时,牢记这一规则尤其重要。因为这样的话,你就必须在所有初始化指针成员的构造函数里采用相同的new的形式。否则,析构函数里将采用什么形式的delete呢?
这个规则对喜欢用typedef的人来说也很重要,因为写typedef的程序员必须告诉别人,用new创建了一个typedef定义的类型的对象后,该用什么形式的delete来删除。举例如下:
typedef string addresslines[4];//一个人的地址,共4行,每行一个string
//因为addresslines是个数组,使用new:
string *pal = new addresslines;// 注意"new addresslines"返回string*,
和
// "new string[4]"返回的一样
delete时必须以数组形式与之对应:
delete pal;// 错误!
delete [] pal;// 正确
为了避免混乱,最好杜绝对数组类型用typedefs。这其实很容易,因为标准c++库包含有stirng和vector模板,使用他们将会使对数组的需
求减少到几乎零。举例来说,addresslines可以定义为一个字符串(string)的向量(vector),即addresslines可定义为
vector类型。
new对应delete而new[]对应的是delete[]
释放空间的作用上基本上一样,但是delete[]和delete调用的析构函数的次数是不一样的,delete只调用一次,而delete[]会根据new[]创建的对象数调用析构函数。
new[] 分配内存的时候在前端多分配了一些字节来保存数组长度,因为 delete[] 需要利用这些信息逐个调用数组元素的
destructor.
vc6 DEBUG 版本下 new[] 分配的内存用 delete 清除就会出 runtime exception。
对于基本数据类型,这也许没有任何问题,但是delete与delete[]的区别在于调用析构函数的次数是不同的(前者只有一次)。但对于类类型,就需要用delete[]
与new[]对应