派生类的指针指向基类对象的问题 (100分)

  • 主题发起人 主题发起人 huaervvhuaer
  • 开始时间 开始时间
错了,ai与bi也不是占用相同空间。因为TLine中也有ai的。
 
不好意思,没有MSN。
 
那怎么解释问题2呢??
 

来自:Carlsberg, 时间:2003-8-25 14:56:00, ID:2132721
错了,ai与bi也不是占用相同空间。因为TLine中也有ai的。
"因为TLine中也有ai的。"这是什么意思?难道你认为line是Tline的实例吗?
 
当然不是。你改了程序?以前你在TLine中有一个bi。
当你对形式上的TLine中的bi赋值时实际上是给TShape的ai赋值。
象你现在的程序则不一样,在给形式上的TLine.bp.x赋值时实际上是给TShape的ai赋值。
你改过的程序肯定不是原先的结果。
 
是的,刚修改的,其实改不改,结果也是一样的呀,你可以试试
 
我刚才说的有点问题:
对Tline.bp的操作的确不影响到ai。因为TLine一样要分配一个ai的地址。也就是说,对bp的访问在ai的下一个地址中。但bp的确是一个不存在的地址。虽然没有导致AV错误,但很偶然。也许结构再大一点就会出现AV错。
 
to Carlsberg,
请问什么是AV错误?会不会把我也加入菜鸟花名册啊,
ps:看来你一定是delphibbs的大人物
 
“line:=Tline(Tshape.create);”这种写法我总觉得怪怪的。
看了半天,同意Jhdandcl的看法。
 
这个错误太常识性的了!!
 
第一个问题,Tline只是强制转化了Tshape类的实例而已,所以,转化后的对象拥有的属性,除了Tshape类的属性之外,应该包括Tline属性的默认值,所以第二个问题正确,第四个问题,应该属于过载问题,应该将Tshape类的方法用作虚拟或者动态,在Tline类中进行
override;
第三个问题我不知道
 
根据我理解的VCL 源代码,为实例分配的instance字节是这样使用的:
回溯类的基类到最顶层,逐一把类的VMT放到堆栈,然后开始出栈。
每次出堆栈,都根据具体情况,读取当前基类的vmt表,相应的填写好该实例的函数,方法等等信息。
这样子,父类的方法入口就可能被子类的方法入口覆盖掉。

根据这个回答你的问题:
我的回答用'-----------------------------'隔开

line:=Tline(Tshape.create);//1.只是转换了对象引用的类型??
——————————————————————————
创建的是TShape的内存

line.bp:=point(2,3)
//2.ok,为什么?但下一条语句出错
——————————————————————————
line.bp使用的内存超出了为它分配的TShape内存,属于非法写内存,但是因为这时候是作为Line来使用,而且运气好,所以运行通过了。至于它偷偷使用的内存本来应该放什么东西,谁也不知道。


line.bs:='aaaaaaaaaa'
//3.运行时出错,为什么?
——————————————————————————
这个同上面一样,侵占了4个指针字节,运气不好的侵略到非法内存了。

line.draw();//4.调用的是派生类的对象的draw(),为什么?
——————————————————————————
作为TShape的内存,调用的当然是tShape的Draw了。


 
实例初始化时,基类的内存都保证分配了,各种方法表都加载了。而派生类则根据情况覆盖掉祖先类的方法指针,或者产生新的方法和属性内存,没覆盖掉的方法指针和属性在运行时候则表现出基类的行为。
 
我的回答用'========================'开头~~~[:(!]
line:=TLine(Tshape.Create);
//转换的只是对象的引用的类型,从下面的测试结果可以得出实际的对象还是Tshape对象
line2:=TLine.Create;
showmessage('inst size:'+inttostr(line.InstanceSize)+':'+inttostr(line2.InstanceSize));

//但是--> 问题2. 下面又如何解释?
Line.bp:=point(2,3);
line.ai:=999;
showmessage(format('line.bp:%d,%d: line.ai is %d',[Line.bp.X,Line.bp.y,line.ai]));
// 输出的是line.bp:2,3 line ai is 999
// 说明ai,bp并不是占用相同的内存空间,这样的话与上面的自相矛盾呀!!!
=============ai 和bp属于不同的内存空间,不会互相影响。
//问题3, 下面的在delphi7中运行的时候会出错,但在delphi 6中正常!为什么?
//有兴趣的朋友可以分别在delphi 7和delphi 6中试试
line.str:='drived class string';
showmessage(line.str);
============line.str使用的内存不是正常分配的,所以这里无论运行再怎样正常,肯定也是不稳定的!
//新发现,用派生类的指针/引用指向基类的对象,同样可以实现多态!!!
line.draw;
//输出结果是invoked Tshape.draw(),即调用的基类Tshape的方法。
============对,因为line的实例初始化,给draw这个位置放的是TShape的Draw方法指针。
 
你在delphi7中运行出错,我这里却是delphi7运行正常。
string在实例中存储的只是4个字节的指针信息,多出4个字节,要侵占到不属于它的领空比较困难。
你把string 该为array [0..255] of integer, 然后访问第200个数组成员,这样就肯定出错了!!!




//问题3, 下面的在delphi7中运行的时候会出错,但在delphi 6中正常!为什么?
//有兴趣的朋友可以分别在delphi 7和delphi 6中试试
line.str:='drived class string';
showmessage(line.str);

TLine=class(Tshape)
public
bp:Tpoint;
a:array [1..200]of integer;
procedure draw()
override;
end;
的确在delphi7中出乎意料的极端正常,。
 
procedure TForm1.Button1Click(Sender: TObject);
var
line,line2:TLine;
begin
line:=TLine(Tshape.Create);
//转换的只是对象的引用的类型,从下面的测试结果可以得出实际的对象还是Tshape对象
line2:=TLine.Create;
//但是--> 问题2. 下面又如何解释?
Line.bp:=point(2,3);
line.ai:=999;
showmessage(format('line.bp:%d,%d: line.ai is %d',[Line.bp.X,Line.bp.y,line.ai]));
// 输出的是line.bp:2,3 line ai is 999
// 说明ai,bp并不是占用相同的内存空间,这样的话与上面的自相矛盾呀!!!




////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
这个问题并不难理解,事实上程序没有出问题是因为后面创建了一个对象,注意不一定是TLine对象,任何对都可以.甚至可以不用一个变量,比如你只是写一句TObject.Create在Line2:=TLine.Create的位置.

事实上如果把TLine.Create一句去掉,程序也是运行正确的,显示也是正确的,但是我们定义一个字符串变量s,并在 line.ai:=999;一句的下面加入SetLength(s,10);
然后再看看showmessage(format('line.bp:%d,%d: line.ai is %d',[Line.bp.X,Line.bp.y,line.ai]));喝喝你看到什么了,显示的值是就不对了,为什么呢?是因为bp所占的内存空间本身就不是Line对象的,但是是本应用程序可用(至于是哪个变量所用或者其他东东所用就不得而知了),所以给它赋值不会造成错误,但是假如已经被其他部份占用,它就会改写其他部份的值(就比如你的程序中的Line2对象),而如果没有其他地方占用,并且后面存在着分配堆内存的操作,那它也会被赋盖掉.就比如上面的TObject.Create或者SetLength(s,10);
你也许会试一下,下面的代码也会得到正确结果:
line:=TLine(Tshape.Create);
line2:=TLine.Create;
Line.bp:=point(2,3);
line.ai:=999;
line2.ai:=99999;
showmessage(format('line.bp:%d,%d: line.ai is %d,Line2.ai is %d',[Line.bp.X,Line.bp.y,line.ai,line2.ai]));
// 输出的是line.bp:2,3 line ai is 999,Line2.ai is 99999
看起来好象说明Line2也并没有被占用.
事实上是被占用了的,不信?你调用Line2.Draw试试?会出错?为什么,因为Line2的RTTI已经被破坏了.

我们在后面加入下面一段代码:

p:=Pointer(Line2);
For i:=0 to 100 do
begin
s:=s+' '+IntToStr(p^);
if (i+1) MOD 10 = 0 then
s:=s+#13;
inc(p);
end;
Application.MessageBox(PChar(s),'',0);
显示的结果应该是Line2在内存中的数据的整数表示,按照VTM的结构,第一个整数应该是一个比较大的整数,是一个指针,指向RTTI,可是这里是3,说明Line.bp中的y占用了Line2的RTTI指针,按照这个说法,我们是肯定不能再调用Line2.Draw了,试验结果肯定了这一说法.

全部代码如下:

procedure TForm1.Button1Click(Sender: TObject);
var
line,line2:TLine;
p:PInteger;
i:Integer;
s:String;
begin
line:=TLine(Tshape.Create);
line2:=TLine.Create;
Line.bp:=point(2,3);
line.ai:=999;
line2.ai:=99999;
showmessage(format('line.bp:%d,%d: line.ai is %d,line2.ai is %d',[Line.bp.X,Line.bp.y,line.ai,line2.ai]));
// 输出的是line.bp:2,3 line ai is 999,line2.ai is 99999

p:=Pointer(Line2);
For i:=0 to 100 do
begin
s:=s+' '+IntToStr(p^);
if (i+1) MOD 10 = 0 then
s:=s+#13;
inc(p);
end;
Application.MessageBox(PChar(s),'',0);
//输入出的前两个整数是3,99999

line2.draw;
end;
////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////
以上代在Delphi5/6/7中均测试过.
 
我觉得这个问题基本上可以肯定了,如果用一个子类的指针指向一个父类的对象,其结果将肯定造成使用了不是这个对象的内存空间,而这个空间一般来说是本应用程序的,所以一般不会造成执行异常,但是或者把别的地方使用的内存改写了,又或者被别的地方改写.总之有可能会造成异常(而且无论程序员如何认真考虑,都有可能出现异常,因为操作系统分配内存并不是程序员可以预先知道).

从原理上来说,这个问题就是骗过了编译器,但没有骗过操作系统,使用了一段内存,但是操作系统并不知道你已经使用了这段内存(因为对编译进行了欺骗,导至编译器没有向操作系统申请使用),所以可能会把它分配给另外的代码.
 
个人认为这个问题争论下去的意义已经不大了.
 
aizb分析得极是,
这个四个问题终于明白了。
 
关于“你的Draw方法是虚方法,类指针指向的是TShape,也就是说调用draw的时候是从TShape的VMT中找draw的入口地址当然是执行TShape的Draw了”(引用carlsberg的话)
还问个问题,是不是在基类中的方法声明了virtual之后,基类的对象是不也有VMT,还是 只是派生类中才有?
我估计是基类中也有,只是想确认一下
 
后退
顶部