利用C++特性 从内部成员地址索引外部对象

标题很纠结,来看看实际需求。我们经常说的class A 包含 class B有两种形式,一种是继承,一种是组合。对于这两种情况,假设我们现在得到class B 的地址,怎么通过这个地址反向找到class A 的地址呢?

首先来看看组合的情况:

wp1

对于GetExternalData()函数而言,它只拿到了内部成员MyClassB的地址,但想通过它访问外部的MyClassA的数据要怎么做到呢?首先我们要对于MyClassA的内存布局有一个概念:

wp2

最低位是64字节的Char数组,然后是一个4字节的指针,因为其后跟着的是一个8字节双精度浮点数fData,所以fData内存对齐到8字节的边界上。

我们现在得到pMyClassB指针的地址,只要知道在它上面有多少数据,然后向上偏移N个字节,就能指向外部的MyClassA了。怎么取这个偏移值呢?答案已经在图上给出了,就是声明一个MyClassA的指针p,赋值为0,然后看看p->pMyClassB当前的位置是多少(这里是0x40),就可以知道偏移量是多少,在用pMyClassB地址减去这个偏移值就可以得到MyClassA的地址。

这种利用偏移量换算出外部结构地址的方法,winnt.h 提供了一个宏我们可以直接使用:

wp3

 

接着我们看看继承的情况:

wp7wp5

现在我们有class Dervive,继承了BaseAnother与Base,同样的GetExternalDatat()怎么通过内部的Base指针找到外部的Derive地址,然后访问它的成员呢?

我们知道基类定义的变量是在低位的,然后再到派生类定义的变量,所以如果只是单一继承的话,Base与Derive的地址是相同的,但如果是多重继承,就像上面Base不是排在第一个这样的情况就会复杂一点。Derive的内存布局如下:

wp6

同样的道理,要从Base地址找到Derive地址,其实就是要找到偏移量,如何找到这个偏移量呢?答案是指针转换。我们知道,利用基类指针指向派生类实例是安全的。例如 Base* pBase = pDerive;  这个时候pBase 的值其实不是直接取pDerive的地址赋值,编译器会帮我们完成相应的偏移操作,也就是说pBase 的值会比pDerive的值多8个字节(BaseAnother的大小)。

从派生类转换为基类可以依靠编译器完成偏移,那么从基类转换为派生类是否可以呢?我们都知道将一个基类指针转换成派生类指针是不安全的,但如果我们确保这种转换是合法的,那么编译器也是能很好的完成转换的,一样可以得到偏移后的地址。如同我们的GetExternal()函数,外部确保传进来的对象合法的话,函数内指针的转换是可以正确完成的。

不过有一种情况例外,就是当前编译器无法获知派生类具体定义的时候,例如上面class Base的构造函数中的转换。因为派生类的具体定义在下面,编译器在编译Base的构造中的转换时无从得知派生类的内存布局与具体细节,所以无法完成偏移,更糟糕的是这种情况下不会有编译报错,转换还是能完成,但就是简单的把this的内容赋值给pDerive,这个时候pDerive虽然有值但指向已经错误,用它来访问对象就会悲剧了。所以我们在利用这个特性的时候千万要注意到这一点潜规则。

不过话又说回来,上面介绍的方法虽然能完成从内部成员地址索引外部对象,但如果代码中出现这样的需求,往往是我们的设计不合理,我们应该改的是结构设计,避免这样的情况出现。所以说我们知道了以上的用法但如非迫不得已千万不要这样使用,万一真要用到,也得小心注意上面提到的问题。

关于这个问题我们就讨论到这里,从过完年回来就开始忙4月的资料片,然后就放五一,一直到现在都没写博客,罪过罪过,往后要好好补上,虽说今年真的很忙,但写博客也是万万不能落下的,切记切记。

Tagged , . Bookmark the permalink.

Comments are closed.