这道题是c++编程中因为失误出现的错误,结果选错了,这里做个笔记。
原文链接:http://longzxr.blog.sohu.com/213486896.html
还是得从最近一个比较“诡异”的问题说起:C++ 类指针定义的时候没有初始化的时候,居然可以安全的调用类内部的成员函数而不出错。
这段代码是来源于之前项目,然后我把问题抽离出来,另开一个工程测试的时候,还是这个结果,最开始以为是VC编译器的问题,最后跑到mac下用gcc编译的结果依旧,说明这不是偶然现象,此时我才开始真正思考背后的缘由。在不断的测试得出的结论是:初始化为NULL的类指针可以安全的调用不涉及类成员变量的类成员函数而不出错,但是如果类成员函数中调用了类成员变量则会出错,既然赋值为NULL的情况都可以使用,那么自然不初始化的类指针同样满足这类情况。
查了一些资料,未免自己理解有误,特意将问题在群里共享了一下,果然大家集思广益的效率更高,最终问题得以解释,同时也加深了自己对C++某些机制的理解,不说废话,进入正题:
假设现在有一个简单的类定义如下:
class Test
{
public:
void func(){cout << "hahaha" << endl;}
int get(){return a+b;}
Test():a(1),b(2){}
public:
int a,b;
};
而之后编译器会自动将这个类转换成:
class Test
{
int a,b;
};
void _test_func(Test * this);
int _test_get(Test* this);
........
类中的函数被编译器静态编译了,所有非虚函数(虚函数呢?别急,待会会解释到)都可以调用,因为函数地址编译期间已经确定。我们知道,类中的成员函数都是通过this指针调用成员变量的,编译器会将this指针作为默认参数传给类成员函数的,如myclass.function(int a,int b) --> function(&myclass,int a,int b)
好,现在我们添加main函数如下:
int main()
{
Test *p=NULL;
p->func();//正确,没有调用成员变量,没有使用空的this指针
p->get();//错误,this指针为空,通过this指针调用变量所以出错
return 0;
}
运行结果见上面注释,没有调用成员变量的func()函数正确执行,调用了成员变量的get()函数错误。两者其实都传入了空的this指针,前者没出错仅仅是因为没有调用this指针,而后者调用了。(此时p-func()和p->get()等同于func(NULL),get(NULL)......)是对象指针为NULL,而调用成员函数的时候,函数地址是编译期间确定的,成员函数不通过对象指针(也即当前的p指针)去调用,对象指针仅仅作为参数传入函数然后去调用成员变量。
好现在问题来了,如果是虚函数呢,因为虚函数要通过this指针计算vptr,然后找到vtable,然后dispatch。因为this指针为空,所以在找vtable时候就会coredump了。总之这类情况下,一切调用了this指针的函数都会出错,而完全不调用this指针的成员函数则没问题。
gdb跟踪截图如下:
可以看到,最开始的时候p是有值的,不过是随便指向的,*p指向{a= 3204,b=1},可以看出这个时候p指向的内容是随机的不可控制的,然后往下走,p=NULL,*p对应0x0,但是调用p->func()的时候是通过$Test()::func()调用的,这也进一步验证了,之前说的,成员函数在静态编译的时候地址已经确定,调用的时候直接通过函数地址调用,this指针只是参数传入,p->get()也一样,只不过其内部调用了空的this指针来调用成员变量,所以出错。
总结:任何时候定义指针的时候一定要初始化,这是良好的习惯,这个问题最初是由于当时写程序疏忽造成的,然而错有错着居然编译通过,所以当时一直没发现这个手误,现在追本溯源也算是对c++的内部机制有了更深的了解,不过也提示自己不能有侥幸心理,一定要养成良好的编程习惯,不管对于自己还是对于以后合作的伙伴都是一件好事。