在谈这个话题之前呢,还是得了解一下内存布局,以x86的32位系统为例:
然后得明确一点,NULL指针是无法访问的,如果强行访问,则会引发异常
然而空对象指针有时候却能够调用成员函数
class C
{
public:int a;static int b;static void f1(){cout << "fl" << endl;}void f2(){cout << "f2" << endl;}void f3(){cout << "f3:" << a << endl;}void f4(){cout << "f4:" << b << endl;}virtual void f5(){cout << "virtual f5" << endl;}
};int C::b = 10;int main()
{C* p = NULL;p->f1(); //正确p->f2(); //正确p->f3(); //错误p->f4(); //成功p->f5(); //错误return 0;
}
假设现在new了一个对象,并把它赋值给指针p,此时指针p则保存在栈上,对象中的a保存在堆上,b保存在.data段中(数据段),f1、f2、f3、f4和f5都保存在代码段,也就是说非静态成员变量在运行时储存在对象内部,成员函数并不储存在对象内部,而是位于程序的二进制代码里,所以与对象的地址无关。
为了方便理解,可以大致的认为编译会把以上代码转换为以下代码
class C
{
public:int a;virtual void f5(){cout << "virtual f5" << endl;}
};static int b = 10;static void f1(C* p)
{cout << "fl" << endl;
}void f2(C* p)
{cout << "f2" << endl;
}void f3(C* p)
{cout << "f3:" << p->a << endl;
}
void f4(C* p)
{cout << "f4:" << b << endl;
}
f1和f2调用成功,就是因为函数与对象的地址无关,即使是空地址,也不会去访问。
f4调用成功,因为除了函数与对象的地址无关之外,静态变量的地址也与对象无关。
f3调用失败,因为非静态成员变量在运行时储存在对象内部,在访问时,需要先对p解引用,然后才能访问找到堆中的数据,但是p是空指针,所以报错。
f5调用失败,因为f5是虚函数,在调用虚函数时,会通过虚函数表去查找函数的地址,而虚函数表(存放在.rodata中)可以看成是对象内的隐藏变量,因此也需要对指针p解引用,但是p是空指针,所以报错。
总的来说,空指针能够调用成员函数,其本质就是没有对这个空指针进行解引用,从而引发访问异常,在Linux下,又称为段错误(segmentation fault)