指向NULL的类指针可调用普通成员函数、调用虚函数失败的原因是什么?
#include <iostream> using namespace std; class Test { public: void Func1() { cout << "Func1 called"; } virtual void Func2() { cout << "Func2 called"; } }; int main() { Test* test = NULL; test->Func1(); test->Func2(); }
现象成因分析
- 普通成员函数
Func1的调用逻辑
C++的普通成员函数在编译阶段就会确定调用地址,编译器会隐式将成员函数转换为带this指针参数的全局函数,上述代码中的Func1会被转换成类似void _Test_Func1(Test* const this)的形式。调用test->Func1()时,只是把test指针的值作为this参数传入该函数,由于Func1内部完全没有用到this指针访问成员变量或其他实例相关资源,哪怕传入的this是NULL,也不会触发内存访问错误,函数可以正常执行。 - 虚函数
Func2的调用逻辑
虚函数依赖对象实例的虚函数表实现动态绑定:每个包含虚函数的类实例的内存起始位置都会存储一个虚表指针(vptr),指向该类的虚函数表。调用虚函数时,程序需要先从this指针指向的内存地址取出vptr,再从vptr指向的虚表中查询对应虚函数的地址才能执行。本次调用中test为NULL,也就是this指向0地址,尝试读取0地址的vptr时会直接触发内存访问违例(段错误),程序立刻崩溃退出。 - 无输出的额外原因
cout默认是行缓冲模式,只有遇到换行符、缓冲区满、程序正常退出时才会将缓冲区的内容刷到控制台。当同时调用Func1和Func2时,Func1的输出还存储在缓冲区中,程序就因为Func2的调用崩溃,缓冲区内容没有被刷新,所以看不到任何输出。如果注释掉Func2的调用,程序可以正常执行结束,缓冲区内容被刷新,就能看到Func1的打印内容。
注意:空指针调用类成员函数本身属于C++标准定义的未定义行为,上述现象仅为大部分主流编译器的实现结果,请勿在生产代码中编写此类逻辑。
内容的提问来源于stack exchange,提问作者sayantan dutta
相关产品推荐
相关产品推荐

