C-Style类型转换引发的问题

转载自[url]http://jackaldire.com/201001/c-style-cast-issues/[/url]

有人在byr论坛C++版上问了这样一道C++面试题:

#include <iostream>
using namespace std;

class A
{
public: void fun() { }
};

class B: public A
{
public: virtual void fun() { }
};

class C: public B
{
public: void fun(){}
};

class D: virtual public A
{
public:
void fun(){}
};

int main(void)
{
((A*)NULL)->fun();
((B*)NULL)->fun(); //发生错误
((C*)NULL)->fun(); //发生错误
((D*)NULL)->fun();
return 0;
}


为什么((C*)NULL)->fun();会发生错误?
原因:B的fun是虚函数, 而C继承B, 所以通过C对象指针调用fun的时候触发基类virtual方法的多态特性, 需要查通过vptr查询虚表, 但是对NULL强制类型转换并不会设置vptr(vptr的设置是在构造函数中完成的),编译器就把地址0(NULL)中的内容当vptr,一解引用就 segment fault了。

其实一开始看((A*)NULL)->fun();这句也觉得别扭,没有构造对象就直接调用非static的成员方法,居然还没有运行错误。想了一想,实际上在编译期,编译器都会通过一个name mangling机制将类的成员函数转换成一个具有唯一名字的非成员函数。程序开始运行时,成员函数和非成员函数一样都被载入到内存。所以[b]只要在调用的成员函数不用到需要由类构造函数构造的成分(比如成员变量和vptr),这种通过指针调用成员函数就不会出错。[/b]

有人在回帖中写了一道更恶心的题目:

#include <iostream>
using namespace std;

class A
{
public:
virtual void fun(float) { cout << "A"; }
};

class B
{
public:
virtual void fun(int) { cout << "B"; }
};

class C: public B, public A
{
public:
void fun(float) { cout << "C float"; }
void fun(int) { cout << "C int"; }
};

int main(void)
{
C* p = new C;
((B*)(A*)p)->fun(1);
delete p;
return 0;
}

输出结果:C float

首先输出C是确定的,因为无论怎么转型, p都是指向一个C对象,而A、B中的fun都是虚函数,所以调用的肯定是C类方法。输出float看起来比较诡异,原因肯定在这个(B*)(A*)c-style转型上。


cout << p <<endl;
cout << (A*)p << endl;
cout << (B*)p << endl;
cout << (B*)(A*)p << endl;

上面代码的输出为:
0x3e3f78
0x3e3f7c
0x3e3f78
0x3e3f7c
可见(B*)(A*)p和(A*)p是相同的.

分析如下:
C继承了A和B,所以一个C对象里包含了一个A对象和B对象。C对象默认和它包含的B对象对齐.[b]由上面的输出知道(A*)p使得p偏移到了C对象中的A对象,是个static_cast。[/b]接下来(B*)再对指向A对象的p进行转型,这时候问题就出现了,B对象并不包含A对象,编译器也不知道p指向的A对象实际上是包含在一个C对象里,所以[b] (B*)这次转换,只是改变了p的静态类型,并没有改变p指向的位置,是个reinterpret_cast.也就是说p还是指向一个A对象。[/b]所以通过p调用fun(1)的时候,查的是A的虚表,先查到virtual void A::fun(float),再到void C::fun(float)


这个问题的关键就在(B*)(A*)p这两个C-Style的函数式转型(functional cast)上:
(A*)p是从派生类C到基类A的转换,有可能是个向从派生类到基类的static_cast,也有可能是个简单的reinterpret_cast.
从运行结果上看(A*)是个static_cast,而由(A*)到(B*)的转换不可能是static_cast(static_cast (static_cast(p)))是编译不过的),所以只能是reinterpret_cast.

这里又出现了一个问题,为什么(A*)p是个static_cast而不是reinterpret_cast?
至少这与我的直觉不服,在ISO C++标准5.4节找到如下定义(见图):

[img]http://dl.iteye.com/upload/attachment/196099/01c0e774-0ff2-3c5d-b442-aa3dc6852e56.jpg[/img]

也就是说一个C-style的强制类型转换如果可以解释成多个列表里的C++ style转型,取在列表里位置最前面的一个。static_cast在reinterpret_cast前,所以得到了下面的结果:
(B*)(A*)p 等价于 reinterpret_cast(static_cast(p))

[b]
由这道题目说明(Conclusion):[/b]
1. 千万不要用C-style对对象指针进行转型,请用C++-style的static_cast、reinterpret_cast和dynamic_cast,以免造成意料之外的错误。
2. C++标准是个好东西,它胜过任何技术手册,一旦对语言特性有困惑,查标准是最好的解决方法。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值