请熟悉c++的高手解答一个小问题

yoci 2000-12-17 11:55:00
关于虚继承的对象模型,不同的c++编译器在幕后作的工作也不相同,比如下面这段代码,
在vc下编译时输出8,8,12。请教,除去4字节的虚指针vptr之外,对象模型中还有些什么?我觉得好象是Null指针,不知道对不对,如果对的话,放进1~2个Null指针又有什么作用?
谢谢。


#include "iostream.h"

class base
{
public:
virtual void f() const {};
};

class d1 : virtual public base
{};

class d2 : virtual public base
{};

class derived : public d1, public d2
{};

int main()
{
d1 b;
d2 c;
derived d;
cout<<sizeof(b)<<endl;
cout<<sizeof(c)<<endl;
cout<<sizeof(d)<<endl;
return 0;
}
...全文
2246 84 打赏 收藏 转发到动态 举报
写回复
用AI写文章
84 条回复
切换为时间正序
请发表友善的回复…
发表回复
willyzuu 2000-12-22
  • 打赏
  • 举报
回复
关于这个特殊的0,我想了一晚上,实在是找不到更好的解释,只能如下猜测一下:
首先objV并不含它,那个0是next成员变量值.既然obj3d多个0出来,那一定是CPoint3d与
CVertex不同之处造成,它俩都从CPoint2d虚拟继承而来,但是CPoint3d重载了virtual func Z(),
而CVertex没有重载任何基类虚函数.如果拿走CPoint3d中的那两个重载基类虚函数,0就消失了.
所以我认为重载虚基类虚函数是产生这个0的关键.
在虚继承情况下,如果重载的是虚基类的虚函数(参数和返回值不变),而又没有新的
virtual function的情况下,obj3d只含一个vfptr,而这个vfptr又是其sub virtual base class的.
这个时候添上一个0,是否就保证了obj3d中sub CPoint3d object的完整性呢?(弥补那个省略掉的
vfptr),但是如果CPoint3d包含新的virtual function,那个0一样
会产生,而obj3d的top也会产生新的vfptr,但这个vfptr是针对新虚函数的,一样是个不完整的子类,
仍需补0.但这样的补0又有什么实际用处呢? 计算基类偏移? thunk?
谁能给个解释?
疲惫!
willyzuu 2000-12-22
  • 打赏
  • 举报
回复
zhonghua,我不知道你有没有读过jeffrey 的那本Advance Windows,其间提到了一些关于
loder的动作,系统一般首先把kernal32.dll,user32.dll,gdi32.dll(如果有界面需要的话),
其他程序需要的dll等,映射进exe地址空间,然后系统会根据你的项目类型(GUI or CUI)分别
调用C运行时函数_WinMainCRTStartup or _mainCrtStartup,函数再唤起WinMain或main,
startup函数会得到进程所需的命令行参数,环境变量指针,初始化c运行时全局变量,初始化
供malloc使用的堆等,然后调用WinMain,WinMain返回后,启动代码调用C的exit,执行清理工作,
再调用Win32的ExitProcess函数.具体细节偶也不太清楚
zhangzhonghua 2000-12-21
  • 打赏
  • 举报
回复
还有一个与内存布局有关的问题,想请教各位。我在我的一个贴子(http://www.csdn.net/expert/TopicView.asp?id=49053)中放了30分,解答后我会请回答正确者到哪里签个名就给分。

问题如下:
各个class的vtable是何时建立的?在main()之前,系统到底做了些什么?

按理,每个polymophics class各有一个vftable(如果是虚拟继承,应该还有vbtable),由其所有的instance(即object)共享,故应该是在main()之前建立的,我看了调用main()的函数是__crt0,在文件\MSDEV\VC98\Crt\Src下的Crt0.c中。但我看不出来。我也想象singlerace,willyzuu那样考察汇编,只可惜,我现在的汇编水平...,哀。

归根到底,就是程序Loader的问题,Loader的任务应该包括:把程序的image装入内存,装入相关的DLL,重定位,建立函数调用到DLL输出函数的连接,建立进程空间,建立线程堆栈,...。具体过程是怎样的?vtable是在main()之前动态建立,还是编译时已经建立并放在程序image的.data(or .bss?)节由Loader重定位即可?

MSDN中那里有介绍资料?有没有诸如"Loader: Under the Hood"那样的文章?
至于PE程序image的结构,Matt倒是有详细分析(Windows 95系统编程奥秘 - 电子工业版,只是翻译水平不敢恭维)。
zhangzhonghua 2000-12-21
  • 打赏
  • 举报
回复
看了fengye推荐的那篇vc编译器设计者写的文章,感觉不错。但实际中似乎还是有点东西那篇文章未能解释,就是willyzuu所发现的objV3d offset:28处 ¦ 0 ¦--->I don't know。
而且,在obj3d中它在2(_z)之后,到objV3d时,前面却插入了个5(mumble)。

obj3d : 24
1245012 --> 4354396 --> 0
1245016 --> 2
1245020 --> 0
1245024 --> 4350004 --> 4198615
1245028 --> 0
1245032 --> 1

objV : 24
1244988 --> 4354316 --> 4198595
1244992 --> 4354380 --> -4
1244996 --> 0
1245000 --> 4354412 --> 4198625
1245004 --> 0
1245008 --> 1

objV3d : 40
1244948 --> 4354324 --> 4198595
1244952 --> 4350032 --> -4
1244956 --> 0
1244960 --> 4354296 --> 0
1244964 --> 2
1244968 --> 5
1244972 --> 0
1244976 --> 4354336 --> 4198610
1244980 --> 0
1244984 --> 1

zhangzhonghua 2000-12-21
  • 打赏
  • 举报
回复
谢谢willyzuu!
那么,在main()之前,系统到底做了些什么?
MSDN中那里有介绍资料?有没有诸如"Loader: Under the Hood"那样的文章?
willyzuu 2000-12-21
  • 打赏
  • 举报
回复
To zhonghua, 我觉得vtable是在编译时期建立的,看一下CPoint2d的输出汇编(VC生成):

PUBLIC ?z@CPoint2d@@UAEJXZ ; CPoint2d::z vfunc declare
PUBLIC ?z@CPoint2d@@UAEXJ@Z ; CPoint2d::z vfunc declare
PUBLIC ??_7CPoint2d@@6B@ ; CPoint2d::`vftable' declare
;COMDAT ??_7CPoint2d@@6B@
; File F:\Work\testCsl\testCsl.cpp
CONST SEGMENT
??_7CPoint2d@@6B@ DD FLAT:?z@CPoint2d@@UAEXJ@Z ; CPoint2d::`vftable' define
DD FLAT:?z@CPoint2d@@UAEJXZ ; 将CPoint2d的两个虚函数地址填入vf
CONST ENDS

这样,CPoint2d::vftable作为一个常量放在数据段中,用CPoint2d::z()函数地址来填充,编译结束后vftable中的内容就完全确定了.另外可以通过运行时跟踪CPoint2d::vftable中的内容,纪录后,搜索exe文件,你可以找到相同的内容,这说明编译时期已经确定了vftable的内容,也因为虚函数地址在编译期就决定了.当然这些动作的前提是有instance定义.
lolin 2000-12-20
  • 打赏
  • 举报
回复
谢谢singlerace,你教会了我很多东西,谢谢!
zhangzhonghua 2000-12-20
  • 打赏
  • 举报
回复
修正一句:
谢谢singlerace,willyzuu的精彩分析,所有“抱着琵琶半遮面”的歌女,在汇编下都“一呼两唤即出来”,你们的经验值得我学习。
aska_xxy 2000-12-20
  • 打赏
  • 举报
回复
小弟想借此宝地请教各位大虾一个小问题
vc++linking时见到如下麻烦,该如何是好,
我知道少了库文件,不知如何确定是那个

Linking...
1.obj : error LNK2001: unresolved external symbol __chkesp
LINK : error LNK2001: unresolved external symbol _WinMainCRTStartup
Debug/1.exe : fatal error LNK1120: 2 unresolved externals
Error executing link.exe.
bush 2000-12-19
  • 打赏
  • 举报
回复
guanzhu !!
singlerace 2000-12-19
  • 打赏
  • 举报
回复
我没有参考任何书,一切结论都从汇编代码得出。实际上,我在考察汇编代码前甚至都不知道vbptr的存在。
另外,必须注意,vbptr是VC++的实现,别的编译器可能用不同的方法。
fengye 2000-12-19
  • 打赏
  • 举报
回复
This article in MSDN may help

Technical Articles/Visual Tools/Visual C++ 6.0/C++: Under the Hood
willyzuu 2000-12-19
  • 打赏
  • 举报
回复
objV3d model:

0 __________
4 | vfptr |--->与vertex 公用
8 | vbptr |--->28 (除了-4之外,还要注意相邻后续地址包含基类偏移)
2 | next |
16 | vbptr |--->16 (算法同上)
20 | z |
24 | mumble |
28 | 0 |--->I don't know
32 | vfptr |
34 | x |
36 | Y |
40 ------------

vbptr的作用主要是在派生类与虚基类之间发生多态行为时,即将派生类对象地址付给一个基类指针时,调整this指针而用, 例如一个objV3d对象地址付给一个CPoint2d指针时,你会发现指针偏移
了28字节,这样正好指向objV3d的CPoint2d subobject.这是vbptr在起作用,不过我比较疑惑的是,vc存储虚基类偏移量时,并不是在vbptr指定的地址,而是后移一个双字,再加上本身,多数情况
下看到的是0,或-4,还要看后续地址空间存储的值,才能确定出这个偏移量.
不知我分析的正确否.
to zhonghua,vtbl在虚继承的情况下不可能为空.另外我觉得obj3d的开始字节应是vbptr,因为obj3d没有新的virtual function.
zhangzhonghua 2000-12-19
  • 打赏
  • 举报
回复
fengye推荐的文章的作者是Microsoft C++编译器的开发人员之一,他的文章看来是最权威的了。
谢谢fengye的指点。我以前怎么没发现这篇文章呢,浪费了这么多时间?哎,MSDN真是个宝库,没看到的东西太多了!
谢谢singlerace,willyzuu的精彩分析。
谢谢各位。
willyzuu 2000-12-19
  • 打赏
  • 举报
回复
我觉得大家应该看一下fengye推荐的那篇vc编译器设计者写的文章,比较详细的介绍了VC中C++
的实现.vc是通过virtual base table存储virtual base 偏移来实现派生类对虚基类成员的
存取,以及之间的多态行为,总之vbptr是vc中实现虚拟继承的关键,每增加一重虚拟继承,virtual
base table 就会增加相应的偏移量指示.
zhangzhonghua 2000-12-19
  • 打赏
  • 举报
回复
渴望UltraUnAsm的整理文章贴子。
willyzuu 2000-12-19
  • 打赏
  • 举报
回复
从fengye推荐得文章可以找到答案,
疑惑顿解,*vbptr指示的是此vbptr相对于自身对象地址的偏移,后续的+4才是相对于vbase的偏移
这样就可以解释那个-4了,因为objV3d起始字节是cfptr,紧跟的是vbptr,这样*vbptr就是-4,
如果没有vfptr那*vbptr就是0了.
UltraUnAsm 2000-12-19
  • 打赏
  • 举报
回复
上面的方法叫Pointer Strategy
VC++的做法我将稍候整理并贴上.
UltraUnAsm 2000-12-19
  • 打赏
  • 举报
回复
权威发言,不容质疑(太狂了:-)

base object :base 的vptr (共4bytes)
d1 object :base object(即base的vptr) +[指向base object的指针] (共8bytes)
d2 object :base object(即base的vptr) +[指向base object的指针] (共8bytes)
derived object: base object(即base的vptr) +[指向base object的指针+指向base object的指针] (共12bytes)

[]内的东西可体现派生类与子类的"包含"关系

BTW:vtable的布局的确没有统一标准.gcc和vc++就不一样.
willyzuu 2000-12-19
  • 打赏
  • 举报
回复
改正一点,第一个(*vbptr)+4 的值是24,实际上,发生CPoint2d* p2d = &objV3d 这样的行为时
VC用第二个vbptr即CPoint3d subobject的(*vbptr)+4 = 16来得到相对基类得偏移量,只有发生
ObjV3d->CVertex->CPoint3d这样得多态行为时才用到第一个vbptr.
看一下汇编就知道了
加载更多回复(64)

16,467

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC相关问题讨论
社区管理员
  • 基础类社区
  • Web++
  • encoderlee
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

        VC/MFC社区版块或许是CSDN最“古老”的版块了,记忆之中,与CSDN的年龄几乎差不多。随着时间的推移,MFC技术渐渐的偏离了开发主流,若干年之后的今天,当我们面对着微软的这个经典之笔,内心充满着敬意,那些曾经的记忆,可以说代表着二十年前曾经的辉煌……
        向经典致敬,或许是老一代程序员内心里面难以释怀的感受。互联网大行其道的今天,我们期待着MFC技术能够恢复其曾经的辉煌,或许这个期待会永远成为一种“梦想”,或许一切皆有可能……
        我们希望这个版块可以很好的适配Web时代,期待更好的互联网技术能够使得MFC技术框架得以重现活力,……

试试用AI创作助手写篇文章吧