万恶的void*指针类型转换

大家都知道:用一个基类的指针指向一个派生类的对象是合理的,然而很多人却忽略了这样做的大前提:必须使用规范的指针转换过程。
最近要添加一个功能,上层代码为此新增了一个虚函数接口。我一看,此虚函数所在的类也是新增的,底层驱动代码以前没有使用过这个类。
class IDmIAL_VER5
{
public:
    virtual WORD IAL_SetRldRamCondition(BYTE byCondition) {return PTC_IAL_SUCCESS;}
};
但是驱动现有一个CDrvAdaptDm类。问了问上层,他们说直接用现有的类继承IDmIAL_VER5这个父类,然后实现新增的IAL_SetRldRamCondition虚函数就可以了。以后上层调用这个虚函数的时候,就会自动调用到驱动的实现代码。
于是我就给CDrvAdaptDm新增了一个父类,一切看起来都很正常:
class CDrvAdaptDm : public IDmIAL, public IDrvAdpModule, public IDmIAL_VER5
{
    ....
    virtual WORD IAL_SetRldRamCondition(BYTE byCondition);
}
搞定,编译软件,上板测试,结果——单板起不来!
我连上串口看,每次启动中都会出现这么个错误,然后单板就再次复位了:
program
Exception current instruction address: 0x00000000
Machine Status Register: 0x0008b032
Condition Register: 0x40004085
Task: 0x4334460 "tBdPtcCmd"
经过一番定位,发现是在上层软件调用这个新的接口时复位的。奇怪了,继承一个父类,实现其虚函数难道有问题吗?打断点看看上层在调用时,传入的this指针,也确实就是CDrvAdaptDm对象的指针没错。突然,我想到:难道是多重继承捣的鬼?
于是,我写了一段代码,测试一下用父类的指针调用子类的虚函数:(g_pDrvAdaptDm是CDrvAdaptDm对象的全局指针)
printf("g_pDrvAdaptDm = %#X", (unsigned int)g_pDrvAdaptDm);
IDmIAL_VER5* pVER5 = dynamic_cast(g_pDrvAdaptDm);
printf("VER5 = %#X", (unsigned int)pVER5);
执行这个函数,输出如下:
g_pDrvAdaptDm = 0X65DAEA0
VER5 = 0X65DAEB0
经过一次自动类型转换后的指针值,居然和原来的指针值不一样!
这下明白问题出在哪儿了,恰恰就是因为上层在调用时用的是CDrvAdaptDm对象的地址,而不是经过转换后的指针,导致了调用出错。
其实,对于多重继承的子类,由于它必须要实现多个虚函数表,所以它的数据结构和多个父类的关系如下:
万恶的void*指针类型转换_第1张图片
在编译时,编译器会根据父类指针的不同类型,对子类指针的值进行转换。 这个时候的类型转换就不仅仅是语义转换这么简单的事了,还会实实在在的改变指针的值。
那么上层调用时使用的指针为什么没有经过恰当的转换呢?我找到上层获取指针的代码:
void* CDrvAdpComm::QueryDrvAdp(const DWORD& dwClassId)
{
    return (void*)m_apDrvAdpIAL[dwClassId];
}
原来上层获取到的是个void*指针!难怪没办法进行恰当的类型转换。即使把CDrvAdaptDm指针转换成void*以后,再强制转换成IDmIAL_VER5指针,结果也不正确。因为一旦转成void*,指针的类型信息就全部丢掉了,只剩下一个干巴巴的值。
由于现有的代码架构把所有的对象指针都保存在void指针数组里面,我只好在一开始将转换好的指针也加入到void指针数组里去,然后在上层查询IDmIAL_VER5的指针时,返回这个新的指针。
问题是解决了,但让我感到不解的是:为啥非要用void指针数组来保存这些对象?它们又不是没有共同基类。只要用一个公有基类指针来访问这些对象,再在需要派生类类型时使用dynamic_cast转换,就可以让编译器的RTTI特性来为我们构造防护网,比起void*转换要安全得多。
比如,像下面这样的代码就是安全的代码:
IDmIAL_VER5* pVER5 = QueryDrvAdp(CLASSID_DRVADP_DM_VER5);
CDrvAdaptDm* pDrvAdaptDm = dynamic_cast(pVER5);
if (pDrvAdaptDm)
{
    ....
}
dynamic_cast允许把一个基类的指针转换成其派生类的指针(反过来当然更没问题),前提是对象的实际类型符合派生类类型。假如实际类型不符合,转换后的指针将会是NULL。dynamic_cast类型转换是优雅的C++代码唯一推荐使用的类型转换方式。

你可能感兴趣的:(万恶的void*指针类型转换)