- 浏览: 342477 次
- 性别:
- 来自: 福建福州
文章分类
最新评论
-
jw72jw:
最后这个是打表求值
LUA源码分析三:table分析(1) -
dyllove98:
"一些非常重要的问题,涉及面少。那这个时候,我更崇尚 ...
乱写:团队里的独裁和民主一点看法 -
jvmlover:
被踩10次了,什么思想感情啊。
LUA源码分析三:table分析(1) -
chenchenfly99:
chenchenfly99 写道
MMO游戏终极内测开服一周,问题记录 -
chenchenfly99:
...
MMO游戏终极内测开服一周,问题记录
有奇怪需求的代码,都是历史的问题遗留。在这个时候,就需要借助语言的一些特性。就比如这次遇到的:在一个基类接口函数里,调用一个取得GetMaxLife的函数。因为扩充的需求,人物里加入了一些Life的数值,而怪物类没有。不幸的是它们都继承自这个基类。那么问题就转变成,如何在这个基类接口函数里,识别到不同的派生类做不同的事。想到了这么几个方法
1:GetMaxLife是个虚函数。如果是的话一切都解决了。噩梦的是,可惜非也。连修改的权限都没有,因为一个更底的基类(GetMaxLife是属于它的)的初始化是根据sizeof的判定。多次尝试,还是放弃
2:手工的判断是人物类和怪物类,也就是加个if的语句,而且源代码中也提供了这种判定。采用为备选方案
3:无论接口如何,在计算机看来都是一个内存的读取规则。是否能依据这个规则,给怪物类加上一个同样的变量并初始为0,这样的话即使加上也不影响原来的游戏逻辑。
虽然2更简单,但趁着有时间,研究了一下3。demo代码如下(后文涉及到简单的汇编分析,所以一并带上汇编代码)
平台:vs08 + win2003 x86
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; }
先来看void test_fun_base(role_base *pBase)中队role_base三个值的读取方法
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]
其中不管mrole,role_base,monster类,产生的汇编代码都是一样的。那么能推出的端倪是,C++中的动态,其实都只是将指针指向一个共同的基类内存块,然后加以访问偏移规则。而编译器是在控制你的书写规则。就比如对于一个role_base,你无法直接访问派生类的值,你必须进行一个强行的转换,然后编译器才认为你做的是对的。
再来看派生类的访问方法
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]
这里注意的是访问的偏移规则,在我用的平台里,假如一个role_base类,按照变量申明的顺序,最先申明的值反而是在栈的低地址(栈是反向增长)。当申明一个派生类的时候,可以把基类看成一个先申明的变量,当访问派生类变量的时候,先跨过了0ch(role_base大小)
在熟悉了这么访问规则后,是否可以构造出这样的访问规则,控制怪物类读取变量偏移值,当然这个偏移值和人物类访问的一样。而在这里,我们能控制的偏移只是role_base大小,如果新加入的值是紧挨着的role_base,(需要考虑到额外的内存对齐规则,如果末尾是char)那就太完美了。再加入到刚才分析的,先申请的变量在栈的地地址上,完整代码如下:
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 请按任意键继续. . . */
不好的地方就是,非常依赖栈的申明顺序。不过也无所谓,反正现在就一个平台^_^
最后,再说明一个非常优雅的办法。在另一个类中申明一个同名变量就可以了,前提是集成关系必须一样O(∩_∩)O!以上纯属爱好研究。。
评论
4 楼
lin_style
2009-12-28
稻-草 写道
这种代码越多,越难维护
是啊,没办法,先过了再说吧。有时间在重构
3 楼
lin_style
2009-12-28
chester60 写道
这个问题你可以看下面这个连接里的PDF
http://download.csdn.net/source/806435
http://download.csdn.net/source/806435
呵呵,谢谢。硬盘里下着呢,一直没看
2 楼
稻-草
2009-12-28
这种代码越多,越难维护
1 楼
chester60
2009-12-28
这个问题你可以看下面这个连接里的PDF
http://download.csdn.net/source/806435
http://download.csdn.net/source/806435
发表评论
-
strtok三段代码分析
2010-12-13 00:09 1258unsigned char map[32]; ... -
这算fread的BUG吗吗吗
2010-10-26 17:03 1170环境:VC2008 SP1 ReadMe. ... -
linxu(C)的链表风格封装 VS STL中的迭代器
2010-08-18 20:54 1353起因是因为今天组里有个同事在设计链表erase接口的时候出了点 ... -
几个不同逻辑间参数传递弱化的技巧
2010-07-24 23:25 10741.线程间 HANDLE WINAPI CreateT ... -
STL,我对它的思考和学习(纯粹谈思想,不谈语法技术)
2009-09-21 00:07 1495首先,学习一个东西一 ... -
又是一个指针地址操作的错误
2009-04-27 14:19 1063本篇无学习价值。纯粹记录案件的发生。甚入!! 浓缩如下 ... -
python和C/C++的互相调用 VC
2009-03-25 17:00 9494在C++中对Python进行调用, ... -
返回静态地址 g++和VC的一点不同
2008-07-03 10:50 1477在调试一个地址时无意中发现 g++代码,inet_nota情 ... -
内联函数 与 宏
2008-03-02 00:53 1339内联函数 与 宏 目的:完全处于性能的考虑 机制:将被 ... -
局部变量和指针混淆的一段内存泄露
2008-02-24 12:46 1878class CListNode { public: C ... -
条款22: 尽量用“传引用”而不用“传值”
2008-02-17 20:54 1426条款22: 尽量用“传引用”而不用“传值” 这章讲的东西基本都 ... -
条款21: 尽可能使用const
2008-02-10 21:26 1306防止你做错事的冲动"const"!! 先提 ... -
条款20: 避免public接口出现数据成员
2008-02-07 22:27 1590条款20: 避免public接口出现数据成员 其实我以前一直不 ... -
条款19: 分清成员函数,非成员函数和友元函数
2008-02-07 22:26 1381条款19: 分清成员函数,非成员函数和友元函数 很前几章一样, ... -
重新巩固下基础
2008-02-03 19:38 1038#include <iostream> #inc ... -
条款15: 让operator=返回*this的引用
2008-02-02 21:59 1383条款15: 让operator=返回*this的引用 这里的描 ... -
条款14: 确定基类有虚析构函数
2008-02-02 21:58 1887条款14: 确定基类有虚析 ... -
条款16: 在operator=中对所有数据成员赋值
2008-02-01 18:08 1330条款16: 在operator=中对所 ... -
条款11: 为需要动态分配内存的类声明一个拷贝构造函数和一个赋值操作符
2008-02-01 15:46 1599条款11: 为需要动态分配内存的类声明一个拷贝构造函数和一个赋 ... -
对Effective条款学习 12。尽量使用初始化而不要在构造函数里赋值
2008-01-29 16:33 1416对Effective条款学习 12。尽量使用初始化而不要在构造 ...
相关推荐
在多重继承的场景下,基类指针转换为派生类指针可能需要地址偏移,这是因为派生类可能包含多个基类实例,每个基类在内存中的位置可能不同。然而,如果基类是虚基类(virtual base class),如题目中提到的A类型是B...
我们可以创建一个CView的派生类,覆盖OnDraw函数,该函数会在窗口上绘制图形。在OnDraw中,根据读取的DXF数据,调用相应的绘图函数,如CDC::MoveTo和CDC::LineTo来绘制线条,CDC::Ellipse来绘制圆等。 此外,为了...
在多重继承的情况下,基类指针转换为派生类指针需要注意地址偏移的问题,这是因为派生类在内存中不仅包含了基类的成员,还有自己的成员。而虚拟基类(Virtual Base Class)的引入解决了多重继承中的一些问题,特别是...
在Microsoft Foundation Class (MFC)库中,文件操作是一个核心功能,它允许程序员与磁盘上的文件进行交互,包括读取、写入和追加数据。MFC为C++程序员提供了一种面向对象的方式来处理文件操作,使得这些任务更加简便...
Delphi中的VCL(Visual Component Library)提供了处理流操作的基类TStream及其派生类。 3. TStream类及其属性:TStream是所有流类的基类,它定义了两个重要属性,分别是Size和Position。Size表示流对象的大小,即...
C++允许创建派生类来继承一个基类,继承允许派生类获得基类的属性和方法。多态允许使用基类指针或引用指向派生类对象,并通过该指针或引用调用派生类的方法。多态的实现依赖于虚函数机制。 C++中的数组是一种数据...
在使用`Stream`及其派生类时,需要注意的是,每个流对象都有一个当前位置,同步操作时所有操作都基于这个位置,而在异步操作中,可能涉及多个位置。流的超时机制是为了防止长时间无操作导致的资源浪费或阻塞。 创建...
在Delphi中,TFileStream是TStream的一个重要派生类,专门用于文件操作。创建TFileStream对象时,需要指定文件名和打开模式,如只读、只写、读写以及文件共享模式。这使得我们能方便地读取和写入文件。 通过实例,...
#### CWinApp类:派生的程序对象的基类 - **简介**:`CWinApp` 类用于表示应用程序对象。 #### CWnd类:提供所有窗口类的基本函数 - **简介**:`CWnd` 类提供了所有窗口类的基础功能。 ### 总结 以上介绍了 C++ 中...
- 类的继承关系中,构造方法的执行顺序遵循从基类到派生类的原则。 - 构造方法的调用必须出现在第一行,通常是通过`super()`调用基类的构造方法。 12. **内部类的实现方式** - 内部类分为成员内部类、局部内部类...
如果类层次结构中的基类的析构函数不是虚的,那么在通过指向基类的指针删除派生类对象时,只会调用基类的析构函数,从而导致资源泄露。 ##### (7) 为什么不能有虚拟构造函数? 构造函数不能是虚函数,因为构造函数...
//定义两个值类型变量 if (int.TryParse(txt_Num.Text, out P_int_Num) //判断输入是否是数值 && int.TryParse(txt_Key.Text, out P_int_Key)) { txt_Encrypt.Text = (P_int_Num ^ P_int_Key).ToString(); //加密...
- `dynamic_cast`:用于派生类向基类的转换,并进行类型检查。 #### Linux多线程中可重入函数和不可重入函数 - **可重入函数**:可以被多个线程同时安全调用。 - **不可重入函数**:包含静态变量或全局变量,可能...