最近代码中看到调用空指针对象的成员函数的写法,联想起上次碰到的问题:
两者的本质是一样的,上次只是简单地讨论了下,这次从编译器的角度,来谈一谈这个知识点。
一个简单的例子:
class MyClass
{
public:
int i;
void hello()
{
printf("hello\n");
}
void print()
{
printf("%d\n", i);
}
};
void main()
{
MyClass* pmy = NULL;
pmy->hello();
}
“静态绑定”和“动态绑定”的区别
以下面的语句为例:
somenull->foo();
该语句的意图是:调用对象somenull的foo成员函数。
这句话在Java或Python等动态绑定的语言之中,编译器生成的代码大概是:
找到对象somenull的foo成员函数,调用它。(注意,这里的找到是程序运行的时候才找的,这也是所谓动态绑定的含义:运行时才绑定这个函数名与其对应的实际代码。有时也叫迟绑定。)
而对于C++,为了保证程序的运行时效率,C++的设计者认为凡是编译时能确定的事情,就不要拖到运行时再查找了。所以C++的编译器看到这句话会这么干:
1、查找somenull的类型B,发现该类型有一个非虚的成员函数叫foo。
2、找到了,在这里生成一个函数调用,直接调B::foo(somenull)。
所以到了运行时,由于foo()函数里面并没有任何需要解引用somenull指针的代码,所以真实情况下也不会引发segment fault。这里对成员函数的解析,和查找其对应的代码的工作都是在编译阶段完成而非运行时完成的,这就是所谓的静态绑定,也叫早绑定。
因此上述代码能够正确运行的真正原因是:对于非虚成员函数,C++这门语言是静态绑定的。
在C++中,每个对象都有一个指向自己的this指针,它的作用主要就是用来区分不同的对象,这样你就可以根据它来访问不同的对象的成员变量。
编译器编译后的成员函数的第一个参数是this指针,通过this指针引用数据成员及调用其它成员函数。
MyClass* pmy = NULL;
pmy->hello();
实际上相当于:
MyClass::hello(NULL);
hello函数并没有使用类中的任何成员变量,所以,它也就不会用到this指针,即使此时的this指针是NULL,也不妨碍我们使用hello函数,然而,如果你调用pmy->print(),那么将会报空指针错误,因为这个函数试图用this指针访问成员变量i,而此时this为NULL。
需要注意的是,虚函数一般是动态绑定的~