对象在内存中的存储形式

辉歌 2001-05-01 10:34:00
加精
我知道在c语言中结构体的存储形式。但类实例化的对象在内存中如何表示的呢?请多指教。
...全文
1770 65 打赏 收藏 转发到动态 举报
写回复
用AI写文章
65 条回复
切换为时间正序
请发表友善的回复…
发表回复
yjfu_ 2001-05-08
  • 打赏
  • 举报
回复
好.希望常看到这样的帖子.
..................................................
打好C++的功底还是很重要的哟.
..................................................
支持..温故而知新..................................
agz123 2001-05-08
  • 打赏
  • 举报
回复
对象的存储空间除了通常的数据成员外还可能有两个隐含的成员vfptr和vbptr, vfptr指到类的vtable, vbptr指到虚基类子对象.
Edelwiss 2001-05-07
  • 打赏
  • 举报
回复
Cker说得非常透彻,但有一点不太对,虚函数表的内容在编译的时候就确定了。
其实虚函数跟普通函数的区别只是调用过程不同,举个例子就能看出他们之间的区别了:

class CBase{
virtual draw(); //虚函数
int getlen(); //普通函数
}

class CDer:public CBase{
virtual draw(); //虚函数
int funcofCDer();//普通函数
}


void display(CBase* p);//只有这么调用的时候,才体现出虚函数的特点来了



main()
{
CBase base;
CDer der;

base.getlen();
//对这个方法的调用实际上是call CBase::getlen (0x------ 指向代码区中相应的
//函数部分,也就是在编译的过程中就确定了调用的函数的地址,这就是所谓的静态连编。
base.draw(); //调用CBase的虚函数,
//同上,call CBase::draw 此时,调用虚函数与调用普通函数一样。

display(&base); //调用过程见下面的解释
}

void display(CBase* p)
{

p->draw();
//调用过程如下:
//mov eax,[ebp+8] ;获得参数,即p,由于类中有虚函数,所以p指向虚函数表
//mov edx,[eax] ;得到第一个虚函数的指针,
//mov ecx,[ebp+8] ;ecx 存放this指针,这是非常重要的,因为函数是所有对
// ;象公用的,..这个与此无关以后再说。
//call [edx] ;调用第一个虚函数,即draw(),如果要调用第二个虚函数的话
// ;用call [edx+04];依此类推。
// 这样,在这个函数里面到底调用哪一个draw就完全由主程序传过来的p决定
// 如果p是CBase的对象的指针,则调用CBase的draw,如果是CDer对象的指针
// 则调用CDer的draw

p->getlen();
//这是一个普通函数,直接call CBse::getlen(因为,CDer没有这个函数)

}

写得有点乱,不知大家能不能看明白,这些都是我自己试的,因为书上讲的根本就不明白,
遇到这种问题大家可以写一些简单的例子试一试。

辉歌 2001-05-04
  • 打赏
  • 举报
回复
没办法,最多143分。全给了cker(我不是高手) 
  • 打赏
  • 举报
回复
西安,我的第二故乡......
brucegong 2001-05-04
  • 打赏
  • 举报
回复



w8u(晌马) :
很多种,其中一种就是函数指针——多研究一下函数指针,就会发现函数指针和函数重载的关系——最主要的是,我见过用C语言实现的类。如果大家有兴趣,可以到西安的无敌科技干一两年。



brucegong 2001-05-03
  • 打赏
  • 举报
回复




类的实现是以结构体为基础的。不管面向对象将类如何神化,落实到最後,它就是一个充满了指针的结构体。只不过,面向对象後的C++比原来的C语言编译时的算法要复杂很多(这是题外话)。





  • 打赏
  • 举报
回复
COOL!有进步!
下午再见!
要做饭^-^
  • 打赏
  • 举报
回复
w8u(晌马),我算服了你了。有出息,不人云亦云而会用脑子思考。
CKER(我不是高手)喜欢和这样的兄弟讨论问题。
这个问题涉及到oop的本质,继承,派生,多态性,虚函数,静态联编和动态联编。
真要明白了,也就可以了。
CKER也不敢说完全清楚,大家一起切磋。

分三个方面讨论好吗?
1)数据成员
2)没有声明为虚函数的成员函数--静态联编
3)声明为虚函数的成员函数--动态联编

首先,类的本质应该是封装了操作和数据,用成员函数来实现操作,用数据类型表示类的数据成员。

我前面的说法有错误。 ^-^ 将一个类对象赋值给另一个类对象时,类的数据成员间的赋值方法与结构间的赋值方法相同。
是逐位拷贝的(也就是传值吧)。先考察相同类型的对象间的赋值,数据成员会逐位复制,直至赋值结束。
当把派生类(子类)对象赋给基类(父类)对象时,因为派生类具有基类的全部数据成员,所以赋给基类也没有问题。相反情况的不可行性是显而易见的。

现在回答你的第一个问题,考察我对你程序的修改:
...

int main(int argc, char *argv[])
{
//ptrobjBase是指向基类的指针,而objBase 和 objDer已经分别是基类和子类的实例对象
//就是说,他们已经初始化过了,此时objBase.x=1;objDer.x=1
CBase objBase,*ptrobjBase;
CDer objDer;


objBase=objDer; //这里将派生类的数据成员拷贝给基类,显然objBase.x=1
objDer.x=10; //改变派生类的数据值,与基类无关
printf("%d \n", objBase.x);//所以结果还是1

//再次将派生类的数据成员拷贝给基类,此时的结果objBase.x=10
objBase=objDer;

//另外,C++允许将指向基类的指针指向派生类
ptrobjBase=&objDer;

printf("%d %d ", objBase.x,ptrobjBase->x);//结果10 10

后两个问题明天继续讨论。OK?
但有一点是明确的,不论类在内存中的具体实现如何,类的大小不包括成员函数的大小,只是数据成员大小的和。
这是和C中的结构相一致的。
正如我所说,类和结构的区别仅仅在于类的成员缺省声明为私有的,而结构成员缺省声明为公有的。
要睡觉啦....
辉歌 2001-05-03
  • 打赏
  • 举报
回复
to cker(我不是高手):
很感谢你.我懂了.以下是我的理解:

//再次将派生类的数据成员拷贝给基类,此时的结果objBase.x=10
objBase=objDer;



//另外,C++允许将指向基类的指针指向派生类
ptrobjBase=&objDer;

printf("%d %d ", objBase.x,ptrobjBase->x);//结果10 10
^ ^
| |
数据存在实例 数据存在实例
objBase的空间里 objDer的空间里.
是吗?
对象的成员变量的传递我清楚了.
再次感谢各位老大,特别是cker(我不是高手),耽误了你好多时间.
请cker(我不是高手)继续.


辉歌 2001-05-03
  • 打赏
  • 举报
回复
to magicblue(小飞侠):
objBase=objDer;
作用是用来证明对象的传值不是指针.
  • 打赏
  • 举报
回复
点管理啊。
辉歌 2001-05-03
  • 打赏
  • 举报
回复
300分我给,但怎么给分哪?以前我好象都给了,现在不知道给分的地方哪里去了。
magicblue 2001-05-03
  • 打赏
  • 举报
回复
to:w8u
说句过分点的话,其实这些东东书里都有呀,《thinking in c++》里有讲,再不行看看《The C++Programming Language》
  • 打赏
  • 举报
回复
再补充一点,成员函数间相互拷贝的时候,为何没有拷贝成员函数的问题。
因为成员函数包括虚函数本身都是在编译期间就编译好了的。
程序加载到内存时,他们都位于代码段。
而类的数据成员和虚指针是在程序运行时分配内存空间的。所以成员函数的拷贝是不可能的。
所谓虚函数是指虚函数表的内容是推迟到运行时才确定的,这个过程是由构造函数完成的。
说起来又是一箩筐哪......
  • 打赏
  • 举报
回复
我拷,这个问题应该加到300分,大家说是不是!
我晕...
  • 打赏
  • 举报
回复
讨论上面的例子前,先说说静态联编。

对类的普通成员函数的调用在编译期间就已经由编译器确定好了。因此,就算将基类指针指向派生类,程序仍不知道自己所指向的是派生类

的对象,它只能调用基类的方法。静态联编无法提供这种能力,因为相对应的派生类成员函数显然有着不同的入口地址。这样多态性也就无

法实现。

为了解决这个问题,提出了新的概念-动态联编,又叫滞后联编。
就是在程序运行期间在确定虚成员函数调用的入口地址。
C++要求程序员显式的声明这样的成员函数,就是加上virtual关键字,这样的函数叫做虚函数。
虚函数超凡能力的具体实现是通过虚函数表VTABLE来实现的。
虚函数表实际上是一个指针数组,其中的指针指向特定的虚函数。
但虚函数表在内存中并不包括在类的内存空间中,类的内存空间只包括了普通的数据变量和一个系统隐含的指向虚函数表的指针,这个指针

叫做虚指针。
在linux平台上由GCC编译的可执行文件在内存中展开时,虚指针一般紧跟于数据成员之后。
而windows平台上由VC6编译的可执行文件展开时,虚指针位于类的头部,其后才是数据成员。

说到这里,昨天我说的话又有点毛病了。但知错能改,是我CKER的好习惯(说完,CKER的晚饭全都出来了......)

编译器在编译时,遇到类之后,先为数据成员保留适当的空间,
然后为成员函数生成入口地址,并为对此方法所有的调用生成类似call0x804c123这样的汇编代码,后面的数字就是入口地址。
最后如果发现虚函数,就在类中分配四个字节存放虚指针。如果虚函数的个数不只一个,虚函数表的个数也会相应增加。

这就是说,当成员函数没有虚函数时,类的大小就是考虑数据成员的大小的和。
有虚函数时,再加4,道理就是刚才讲的多多废话。

顺便说一句,对于基类中的纯虚函数,一定要在派生类中实现的原因就是要使得派生类的虚函数表中指向纯虚函数的指针有意义。
辉歌 2001-05-03
  • 打赏
  • 举报
回复
//=============================================================
objBase.print();
//基类虽然得到了派生类的数据成员拷贝,仍调用了基类的成员函数。再次说明不同对象的数据成员有各自的内存空间,而成员函数不被拷贝。
成员函数不被拷贝 ,why???????


//===============================
ptrobjBase->print();//基类指针虽然指向了派生类,竟然仍调用了基类的成员函数???(原因:基类的成员函数不是虚函数。静态联编!!)
//===============================
ptrobjVBase->print();//基类指针指向派生类,根据多态性,基类的虚函数将调用派生类的虚成员函数?(原因:基类的成员函数是虚函数。动态联编!!)

????????????
magicblue 2001-05-03
  • 打赏
  • 举报
回复
to:cker
讲的好~
to:w8u
我想问问你objBase=objDer;这句有什么作用?
#include<iostream.h>
class CBase
{
public:
int x;
int y;
CBase(){x=1,y=2;}
};
class CDer:public CBase
{
public:
int z;
CDer() {z=3;}
};

main()
{
CBase objBase;
CDer objDer;
cout<<objDer.x<<endl;
return 0;
}
//结果:1
在这段code中,可以看到objDer完全继承了objBase中的实列化。

  • 打赏
  • 举报
回复
晚上10:30见
加载更多回复(45)

69,371

社区成员

发帖
与我相关
我的任务
社区描述
C语言相关问题讨论
社区管理员
  • C语言
  • 花神庙码农
  • 架构师李肯
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

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