接上篇,时间不够,写得较乱
我上一篇给C++添加属性机制的文章中,让属性类得到目标类的类指针,我使用了一个INIT_PROPERTY宏,在前几天和龙子龙孙聊天时,他告诉了我一个技巧,得到类成员在类中的偏移地址,如下
(int)(&((type_name*)NULL)->member_name)
例如我有个类 A
class A
{
public:
virtual int show() {}
int i1, i2;
};
则 (int)(&((A*)NULL)->i2) == 8
类地址+8的偏移处为i2的地址(4字节vmt表地址,4字节i1,所以i2的寻址偏移为8)
恍然大悟,这样我就能在属性类中自己计算出拥有属性类的类的地址了,这是因为类的寻址方式决定的,如下所示:
class Child
{
public:
int i1, i2; i1偏移为0, i2为4,无vmt表
};
class Parent
{
public:
int i; i偏移为0
Child *c; c偏移为4
Parnet() {...} c的new 和 delete就不写了
};
Parent *p;
这时要new一个Parent类时,会在堆上分配二块内存,一块为c对象占用内存,一块为p对象占用内存.
要寻址类P对象中c对象的i2成员(p->c->i2),大概的汇编代码如下:
mov ecx, p //得到p的指针
mov ecx, [ecx + 4] //得到p对象成员c的指针
mov eax, [ecx + 4] //得到c对象成员i2的地址
这样就寻址到了i2
但如果Parent类不是包含的Child类的指针,而是直接包含的对象,情况就不一样了,如下:
class Parent
{
public:
int i; i偏移为0
Child c; c偏移为4(注意不同,这里c不是Child*了)
};
Parent *p;
这时要new一个Parent类时,只会在堆上分配一块内存,为p对象占用内存,c对象包含在p对象内,直接使用p对象的内存,要寻址类P对象中c对象的i2成员(p->c.i2),大概的汇编代码如下:
mov ecx, p //得到p的指针
mov ecx, ecx + 8 //得到i2的地址 8 = c偏移+i2在c类中的偏移,编译优化
要寻址类P对象中c对象的地址,如下:
mov ecx, p //得到p的指针
mov ecx, ecx + 4 //得到c的地址 p对象指针+偏移
从上面可以看出,只要知道c类地址,要得到p的指针,就 c地址 - c对象在p对象中的偏移就行了.
事情就简单了,示例代码如下:
#include <iostream>
using std::cout;
using std::cin;
using std::endl;
template<typename T, typename int(*GetOffset)()>
class Property
{
public:
Property()
{
cout << "this:" << (int)this << std::endl;
cout << "owner:" << (int)this - GetOffset() << std::endl;
}
};
#define PROPERTY_DECLARE(pro_name, type_name)/
private: int inline static pro_name##_offset() {return (int)(&((type_name*)NULL)->pro_name);};/
public: Property<type_name, type_name::pro_name##_offset> pro_name;
class PropertyTest
{
public:
int i;
public:
PROPERTY_DECLARE(Name, PropertyTest)
};
int main()
{
PropertyTest *test = new PropertyTest();
cout << std::endl;
cout << "property address:" << (int)(&test->Name) << std::endl;
cout << "test class address:" << (int)test << std::endl;
std::cin.get();
return 0;
}
我上一篇给C++添加属性机制的文章中,让属性类得到目标类的类指针,我使用了一个INIT_PROPERTY宏,在前几天和龙子龙孙聊天时,他告诉了我一个技巧,得到类成员在类中的偏移地址,如下
(int)(&((type_name*)NULL)->member_name)
例如我有个类 A
class A
{
public:
virtual int show() {}
int i1, i2;
};
则 (int)(&((A*)NULL)->i2) == 8
类地址+8的偏移处为i2的地址(4字节vmt表地址,4字节i1,所以i2的寻址偏移为8)
恍然大悟,这样我就能在属性类中自己计算出拥有属性类的类的地址了,这是因为类的寻址方式决定的,如下所示:
class Child
{
public:
int i1, i2; i1偏移为0, i2为4,无vmt表
};
class Parent
{
public:
int i; i偏移为0
Child *c; c偏移为4
Parnet() {...} c的new 和 delete就不写了
};
Parent *p;
这时要new一个Parent类时,会在堆上分配二块内存,一块为c对象占用内存,一块为p对象占用内存.
要寻址类P对象中c对象的i2成员(p->c->i2),大概的汇编代码如下:
mov ecx, p //得到p的指针
mov ecx, [ecx + 4] //得到p对象成员c的指针
mov eax, [ecx + 4] //得到c对象成员i2的地址
这样就寻址到了i2
但如果Parent类不是包含的Child类的指针,而是直接包含的对象,情况就不一样了,如下:
class Parent
{
public:
int i; i偏移为0
Child c; c偏移为4(注意不同,这里c不是Child*了)
};
Parent *p;
这时要new一个Parent类时,只会在堆上分配一块内存,为p对象占用内存,c对象包含在p对象内,直接使用p对象的内存,要寻址类P对象中c对象的i2成员(p->c.i2),大概的汇编代码如下:
mov ecx, p //得到p的指针
mov ecx, ecx + 8 //得到i2的地址 8 = c偏移+i2在c类中的偏移,编译优化
要寻址类P对象中c对象的地址,如下:
mov ecx, p //得到p的指针
mov ecx, ecx + 4 //得到c的地址 p对象指针+偏移
从上面可以看出,只要知道c类地址,要得到p的指针,就 c地址 - c对象在p对象中的偏移就行了.
事情就简单了,示例代码如下:
#include <iostream>
using std::cout;
using std::cin;
using std::endl;
template<typename T, typename int(*GetOffset)()>
class Property
{
public:
Property()
{
cout << "this:" << (int)this << std::endl;
cout << "owner:" << (int)this - GetOffset() << std::endl;
}
};
#define PROPERTY_DECLARE(pro_name, type_name)/
private: int inline static pro_name##_offset() {return (int)(&((type_name*)NULL)->pro_name);};/
public: Property<type_name, type_name::pro_name##_offset> pro_name;
class PropertyTest
{
public:
int i;
public:
PROPERTY_DECLARE(Name, PropertyTest)
};
int main()
{
PropertyTest *test = new PropertyTest();
cout << std::endl;
cout << "property address:" << (int)(&test->Name) << std::endl;
cout << "test class address:" << (int)test << std::endl;
std::cin.get();
return 0;
}