免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12下一页
最近访问板块 发新帖
查看: 4912 | 回复: 17
打印 上一主题 下一主题

C++对象模型一个问题: sizeof得到的大小和我想象的不一样! [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2012-03-08 18:41 |只看该作者 |倒序浏览
20可用积分
一个对象,自己有virtual函数,继承了父类的virtual函数,那么就相当于有了两个虚函数指针。
下面的程序中,s5继承了两个父类,自己也有virtual函数,那么我认为,在32位系统上,它应该有3个虚表指针,大小应该是12。
但是运行结果却是8。我尝试了vc2005/2010,一样的效果:

  1. #include "stdafx.h"
  2. #include <string>
  3. using namespace std;
  4. struct s1{
  5.     virtual void f();
  6. };
  7. struct s2{
  8.     virtual void g();
  9. };
  10. struct s3: public s1{
  11.     virtual void f();
  12. };
  13. struct s4: public s1,s2{//s4的大小是8,我理解。
  14. };
  15. struct s5: public s1,s2{//s5的大小是8,我就不理解了。难道不是12么?
  16.     virtual void h();
  17. };
  18. int  main( void){
  19.     printf("%d,%d,%d,%d,%d\n",sizeof(s1),sizeof(s2),sizeof(s3),sizeof(s4),sizeof(s5));
  20.     return 0;
  21. }
复制代码
我认为s5的对象应该是这样子长的:
----------------
|  s1 vtbl_ptr  |
----------------
|  s2 vtbl_ptr  |
----------------
|  s5 vtbl_ptr  |
----------------

为什么和我想象的不一样呢?

最佳答案

查看完整内容

回复 10# donet8 仔细看我在9楼的帖子,编译器必须在对象构造的时候就给 vptr 赋值。等到虚函数调用的时候,只要把 vptr 的值加上具体虚函数的偏移量,就找到了正确的入口。这就要求,同一个虚函数在父类和子类的虚表中,必须保持相同的偏移量。假设 f(), g() 来自父类 B1, x(), y() 来自父类 B2,D 同时继承B1 和 B2假设f(),g() 在 B1 的虚表中位于第1位和第2位;那么它们在D的虚表中就也必须在第1位和第2位;同样x(),y() 在 B2 ...

论坛徽章:
0
2 [报告]
发表于 2012-03-08 18:41 |只看该作者
回复 10# donet8

仔细看我在9楼的帖子,编译器必须在对象构造的时候就给 vptr 赋值。等到虚函数调用的时候,只要把 vptr 的值加上具体虚函数的偏移量,就找到了正确的入口。

这就要求,同一个虚函数在父类和子类的虚表中,必须保持相同的偏移量。

假设 f(), g() 来自父类 B1, x(), y() 来自父类 B2,D 同时继承B1 和 B2

假设f(),g() 在 B1 的虚表中位于第1位和第2位;那么它们在D的虚表中就也必须在第1位和第2位;

同样

x(),y() 在 B2的虚表中位于第1位和第2位;那么它们在D的虚表中就也必须在第1位和第2位;

如果用同一张虚表,你怎么让 f(),g() 和 x(),y() 同时在第1位和第2位呢?

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
3 [报告]
发表于 2012-03-08 18:53 |只看该作者
struct s3的大小可能是4,你理解么?
struct s31 : s1 { virtual void f(); virtual void g(); /*再多加一个虚函数*/ }; 它的大小也可能只有4,你理解么?

s5大小是12就没什么不好理解的了……

论坛徽章:
3
CU大牛徽章
日期:2013-03-13 15:32:35CU大牛徽章
日期:2013-03-13 15:38:15CU大牛徽章
日期:2013-03-13 15:38:52
4 [报告]
发表于 2012-03-08 22:35 |只看该作者
本帖最后由 wjianc 于 2012-03-08 22:43 编辑

我是这么理解的:
1. 有虚函数的类都有一个一维的虚函数表叫虚表,虚表里存放着虚函数的地址
2. 有虚函数的类对象保存着一个指向虚表的指针
3. 父类和子类共享虚表指针
4. 多重继承增加额外的指针

对照以上几条:
* 就算s4你加上虚函数成员,sizeof(s4)大小也不会变
* s5你再多加一个父类,会多增加一个虚表指针

论坛徽章:
0
5 [报告]
发表于 2012-03-09 00:31 |只看该作者
本帖最后由 bufferfly 于 2012-03-09 13:16 编辑

解释有误,请看9楼

论坛徽章:
0
6 [报告]
发表于 2012-03-09 10:52 |只看该作者
OwnWaterloo 发表于 2012-03-08 18:53
struct s3的大小可能是4,你理解么?
struct s31 : s1 { virtual void f(); virtual void g(); /*再多加一 ...


我知道的基础知识: 一个类有虚函数,就有了虚指针,size+4。无论有多少个虚函数,都只有一个虚指针,size还是+4。虚指针指向虚函数表。

  1. struct s{
  2.    virtual void f();
  3.    virtual void g();
  4.    virtual void h();
  5. };
复制代码
sizeof(s)一直都是4.

但是问题是,为什么在vc的编译器实现中,多继承时,通常子类的虚函数表跟第一个父类的虚函数表在一起,所以只用一个虚指针? 会不会有什么逻辑上的冲突啊。
如果有了N(N>2)个父类,就再增加(N-1)个虚指针?

我感觉这个解释这个不太清楚,因为如果我有class2和class3都是从class1继承的

  1. struct class1{
  2.     virtual void f(){printf("class1.f()\n");}
  3. };
  4. struct class2: class1{
  5.     virtual void f(){printf("class2.f()\n");}
  6. };
  7. struct class3: public class1{
  8.     virtual void f(){printf("class3.f()\n");}
  9. };
复制代码
那么,难道说class1的虚函数表不单独存在,而是class2的虚函数表=class1+class2,class3的虚函数表=class1+class3.
我不知道上面两位大侠说的,是不是这个意思。

请指正!

论坛徽章:
0
7 [报告]
发表于 2012-03-09 11:09 |只看该作者
本帖最后由 bufferfly 于 2012-03-09 14:33 编辑

回复 5# donet8

解释有误,请看9楼

论坛徽章:
3
CU大牛徽章
日期:2013-03-13 15:32:35CU大牛徽章
日期:2013-03-13 15:38:15CU大牛徽章
日期:2013-03-13 15:38:52
8 [报告]
发表于 2012-03-09 11:10 |只看该作者
donet8 发表于 2012-03-09 10:52
我知道的基础知识: 一个类有虚函数,就有了虚指针,size+4。无论有多少个虚函数,都只有一个虚指针,si ...


是的,  该类实例的头4个字节就是虚表指针.
VC++. GCC实现时 子类和父类共享虚指针, 只有一个虚指针, 子类将自己的虚函数插在父虚函数之前.
但是多重继承另外的父类虚指针则不共享, 有一个就增加一个虚指针.

论坛徽章:
0
9 [报告]
发表于 2012-03-09 12:00 |只看该作者
bufferfly 发表于 2012-03-09 11:09
回复 5# donet8


我没有混淆,我在5L里面已经说了:
"我知道的基础知识: 一个类有虚函数,就有了虚指针,size+4。无论有多少个虚函数,都只有一个虚指针,size还是+4。虚指针指向虚函数表。"

我的问题是,既然子类和父类公用一个虚指针不冲突,那么多余一个父类的话,为什么要添加虚指针呢? 多于一个父类就冲突了?

论坛徽章:
0
10 [报告]
发表于 2012-03-09 13:07 |只看该作者
本帖最后由 bufferfly 于 2012-03-09 14:37 编辑

回复 8# donet8


要想完美的解释你的问题,恐怕要涉及到C++编译器的很多设计细节,我并不是写编译器的,但可以告诉你一个大概的思路。

一般的书上都说,虚函数是在运行时根据对象的实际类型“动态决定”函数入口。但什么是“动态决定”呢?实际上C++编译器在实现这个功能的时候,并非真的等到虚函数被调用时才去判断这个对象是什么类型的。下面我用一个简单的图表来说明C++编译器到底干了些什么。假设有两个类

struct Base {
  virtual void f();
  virtual void g();
};


struct Derived : public Base {
  virtual void f();
  virtual void g();
};

Base 和 Derived 各有一个虚表,分别是 VTable_B 和 VTable_D ,那么编译器是怎么通过只用一个虚表指针来实现下面的“动态”调用呢?

Base *pB = new Derived();
pB->f();

先让我们看Base和Derived对象是怎么存储的,以及两个类的虚表结构

Base:                    VTable_B:
------------         -------------
|  vptr       |        |  f() 入口    |
+---------+        +----------+
|  Base的   |        |  g() 入口   |
|    数据     |        -------------
------------

Derived:                VTable_D:
------------        --------------
|  vptr       |        | f() 入口     |
+---------+        +----------+
|  Base的   |        | g() 入口    |
|    数据     |        -------------
+---------+
| Derived的|
|    数据     |
------------

pB 指针既可以指向 Base 对象,也可以指向 Derived 对象,所以 pB 本身是不确定的。但是,任何对象本身却从被 new 出来开始就是确定的,所以 Base 对象在构造时,编译器会往 vptr 中填上 VTable_B 的地址,而 Derived 对象在构造时,编译器会往 vptr 中填上 VTable_D 的地址

等到虚函数被调用的时候,也就是 pB->f() 这行语句被执行的时候,编译器并不需要知道 pB 到底是指向 Base 还是 Derived ,它只要直接用 vptr 就能找到正确的虚表和虚函数入口了,父类和子类的虚表结构是相似的,同一个虚函数入口在父表和子表的偏移量都是一样的

通过上面这些介绍,我想你应该能理解,为什么在单一继承的条件下,不管有多少层继承,每个对象只需一个 vptr 就行了。

多重继承的条件下,一个 vptr 行不行呢?

不行。多重继承的时候,虚函数既有来自父类1的,也有来自父类2的,所以这些虚函数入口是不能放在同一个虚表当中的。假设 Derived 除了 Base外,还继承 Base2,并且 Base2 中有两个虚函数 x() 和 y (),那么 Derived 对象的存储结构也许是这样的(只是大概,和具体编译器相关)。

Derived:                VTable_D:
------------        --------------
|  vptr       |        | f() 入口     |
+---------+        +----------+
|  Base的   |        | g() 入口    |
|    数据     |        -------------
+---------+
| vptr2      |          VTable_D2:
+---------+        -------------
| Base2的  |        |  x() 入口    |
|    数据     |        +-----------+
+---------+        | y() 入口     |
| Derived的|        -------------
|    数据     |
------------
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP