[size=medium]
有奇怪需求的代码,都是历史的问题遗留。在这个时候,就需要借助语言的一些特性。就比如这次遇到的:在一个基类接口函数里,调用一个取得GetMaxLife的函数。因为扩充的需求,人物里加入了一些Life的数值,而怪物类没有。不幸的是它们都继承自这个基类。那么问题就转变成,如何在这个基类接口函数里,识别到不同的派生类做不同的事。想到了这么几个方法
1:GetMaxLife是个虚函数。如果是的话一切都解决了。噩梦的是,可惜非也。连修改的权限都没有,因为一个更底的基类(GetMaxLife是属于它的)的初始化是根据sizeof的判定。多次尝试,还是放弃
2:手工的判断是人物类和怪物类,也就是加个if的语句,而且源代码中也提供了这种判定。采用为备选方案
3:无论接口如何,在计算机看来都是一个内存的读取规则。是否能依据这个规则,给怪物类加上一个同样的变量并初始为0,这样的话即使加上也不影响原来的游戏逻辑。
虽然2更简单,但趁着有时间,研究了一下3。demo代码如下(后文涉及到简单的汇编分析,所以一并带上汇编代码)
平台:vs08 + win2003 x86
[/size]
[size=medium]先来看void test_fun_base(role_base *pBase)中队role_base三个值的读取方法[/size]
[size=medium]
其中不管mrole,role_base,monster类,产生的汇编代码都是一样的。那么能推出的端倪是,C++中的动态,其实都只是将指针指向一个共同的基类内存块,然后加以访问偏移规则。而编译器是在控制你的书写规则。就比如对于一个role_base,你无法直接访问派生类的值,你必须进行一个强行的转换,然后编译器才认为你做的是对的。[/size]
再来看派生类的访问方法
[size=medium]
这里注意的是访问的偏移规则,在我用的平台里,假如一个role_base类,按照变量申明的顺序,最先申明的值反而是在栈的低地址(栈是反向增长)。当申明一个派生类的时候,可以把基类看成一个先申明的变量,当访问派生类变量的时候,先跨过了0ch(role_base大小)
在熟悉了这么访问规则后,是否可以构造出这样的访问规则,控制怪物类读取变量偏移值,当然这个偏移值和人物类访问的一样。而在这里,我们能控制的偏移只是role_base大小,如果新加入的值是紧挨着的role_base,(需要考虑到额外的内存对齐规则,如果末尾是char)那就太完美了。再加入到刚才分析的,先申请的变量在栈的地地址上,完整代码如下:
[/size]
[size=medium] 不好的地方就是,非常依赖栈的申明顺序。不过也无所谓,反正现在就一个平台^_^
最后,再说明一个非常优雅的办法。在另一个类中申明一个同名变量就可以了,前提是集成关系必须一样O(∩_∩)O!以上纯属爱好研究。。
[/size]
有奇怪需求的代码,都是历史的问题遗留。在这个时候,就需要借助语言的一些特性。就比如这次遇到的:在一个基类接口函数里,调用一个取得GetMaxLife的函数。因为扩充的需求,人物里加入了一些Life的数值,而怪物类没有。不幸的是它们都继承自这个基类。那么问题就转变成,如何在这个基类接口函数里,识别到不同的派生类做不同的事。想到了这么几个方法
1:GetMaxLife是个虚函数。如果是的话一切都解决了。噩梦的是,可惜非也。连修改的权限都没有,因为一个更底的基类(GetMaxLife是属于它的)的初始化是根据sizeof的判定。多次尝试,还是放弃
2:手工的判断是人物类和怪物类,也就是加个if的语句,而且源代码中也提供了这种判定。采用为备选方案
3:无论接口如何,在计算机看来都是一个内存的读取规则。是否能依据这个规则,给怪物类加上一个同样的变量并初始为0,这样的话即使加上也不影响原来的游戏逻辑。
虽然2更简单,但趁着有时间,研究了一下3。demo代码如下(后文涉及到简单的汇编分析,所以一并带上汇编代码)
平台:vs08 + win2003 x86
[/size]
class role_base
{
//variable
public:
int base_1;
int base_2;
int base_3;
private:
//function
public:
role_base()
{
base_1=10;
base_2=20;
base_3=30;
}
private:
};
class monster:public role_base
{
public:
int monster_1;
int monster_2;
int monster_3;
private:
public:
monster()
{
monster_1=100;
monster_2=200;
monster_3=300;
}
private:
};
class mrole:public role_base
{
public:
int mrole_1;
int mrole_2;
int mrole_3;
int mrole_add;
private:
public:
mrole()
{
mrole_1=1000;
mrole_2=2000;
mrole_3=3000;
mrole_add=4000;
}
private:
};
void test_fun_base(role_base *pBase)
{
/*
这个就是被mrole,monster调用的公共函数接口。mrole中的mrole_add是额外加的,希望这样访问。并且能够控制monster的读取值。
cout<<( (mrole*)pBase)->mrole_add<<endl;
*/
cout<<pBase->base_1<<endl;
00401919 mov ecx,dword ptr [pBase]
0040191C mov edx,dword ptr [ecx]
0040191E push edx
cout<<pBase->base_2<<endl;
00401939 mov ecx,dword ptr [pBase]
0040193C mov edx,dword ptr [ecx+4]
0040193F push edx
cout<<pBase->base_3<<endl;
0040195A mov ecx,dword ptr [pBase]
0040195D mov edx,dword ptr [ecx+8]
00401960 push edx
mrole_add
}
void test_fun_monster(monster *pMonster)
{
cout<<pMonster->monster_1<<endl;
004018A9 mov ecx,dword ptr [pMonster]
004018AC mov edx,dword ptr [ecx+0Ch]
004018AF push edx
cout<<pMonster->monster_2<<endl;
004018CA mov ecx,dword ptr [pMonster]
004018CD mov edx,dword ptr [ecx+10h]
004018D0 push edx
cout<<pMonster->monster_3<<endl;
004018EB mov ecx,dword ptr [pMonster]
004018EE mov edx,dword ptr [ecx+14h]
004018F1 push edx
}
int _tmain(int argc, _TCHAR* argv[])
{
role_base Role;
monster Monster;
mrole MRole;
/*
现实中的执行情况,2个派生类同时执行
*/
test_fun_base(&Monster);
test_fun_base(&MRole);
/*
2个测试访问规则的方法
*/
test_fun_base(&Role);
test_fun_monster(&Monster);
return 0;
}
[size=medium]先来看void test_fun_base(role_base *pBase)中队role_base三个值的读取方法[/size]
base_1
0040191C mov edx,dword ptr [ecx]
base_2
0040191C mov edx,dword ptr [ecx+4]
base_3
0040191C mov edx,dword ptr [ecx+8]
[size=medium]
其中不管mrole,role_base,monster类,产生的汇编代码都是一样的。那么能推出的端倪是,C++中的动态,其实都只是将指针指向一个共同的基类内存块,然后加以访问偏移规则。而编译器是在控制你的书写规则。就比如对于一个role_base,你无法直接访问派生类的值,你必须进行一个强行的转换,然后编译器才认为你做的是对的。[/size]
再来看派生类的访问方法
monster_1
004018A9 mov ecx,dword ptr [pMonster]
004018AC mov edx,dword ptr [ecx+0Ch]
monster_2
004018CA mov ecx,dword ptr [pMonster]
004018CD mov edx,dword ptr [ecx+10h]
monster_3
004018EB mov ecx,dword ptr [pMonster]
004018EE mov edx,dword ptr [ecx+14h]
[size=medium]
这里注意的是访问的偏移规则,在我用的平台里,假如一个role_base类,按照变量申明的顺序,最先申明的值反而是在栈的低地址(栈是反向增长)。当申明一个派生类的时候,可以把基类看成一个先申明的变量,当访问派生类变量的时候,先跨过了0ch(role_base大小)
在熟悉了这么访问规则后,是否可以构造出这样的访问规则,控制怪物类读取变量偏移值,当然这个偏移值和人物类访问的一样。而在这里,我们能控制的偏移只是role_base大小,如果新加入的值是紧挨着的role_base,(需要考虑到额外的内存对齐规则,如果末尾是char)那就太完美了。再加入到刚才分析的,先申请的变量在栈的地地址上,完整代码如下:
[/size]
class role_base
{
//variable
public:
int base_1;
int base_2;
int base_3;
private:
//function
public:
role_base()
{
base_1=10;
base_2=20;
base_3=30;
}
private:
};
class monster:public role_base
{
public:
int monster_add; //这个值必须最前
int monster_1;
int monster_2;
int monster_3;
private:
public:
monster()
{
monster_1=100;
monster_2=200;
monster_3=300;
monster_add=400;
}
private:
};
class mrole:public role_base
{
public:
int mrole_add; //这个值必须最前
int mrole_1;
int mrole_2;
int mrole_3;
private:
public:
mrole()
{
mrole_1=1000;
mrole_2=2000;
mrole_3=3000;
mrole_add = 4000;
}
private:
};
void test_fun_base(role_base *pBase)
{
cout<<pBase->base_1<<endl;
cout<<pBase->base_2<<endl;
cout<<pBase->base_3<<endl;
cout<< *(int*)( (int)pBase+sizeof(role_base) )<<endl;
}
void test_fun_monster(monster *pMonster)
{
cout<<pMonster->monster_1<<endl;
cout<<pMonster->monster_2<<endl;
cout<<pMonster->monster_3<<endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
role_base Role;
monster Monster;
mrole MRole;
/*
现实中的执行情况,2个派生类同时执行
*/
test_fun_base(&Monster);
test_fun_base(&MRole);
return 0;
}
/*
输出:
10
20
30
400
10
20
30
4000
请按任意键继续. . .
*/
[size=medium] 不好的地方就是,非常依赖栈的申明顺序。不过也无所谓,反正现在就一个平台^_^
最后,再说明一个非常优雅的办法。在另一个类中申明一个同名变量就可以了,前提是集成关系必须一样O(∩_∩)O!以上纯属爱好研究。。
[/size]