作为C++的核心单元,对象模型在编译器眼中是如何实现的?本文从几个基本理论模型出发,剖析实际。 深度探索C++对象模型 ---- 简单对象模型 对象存放若干slots,由slot指向实际成员。 表驱动对象模型 这个模型的function部分可以看做在上面的简单对象模型基础上再增加了一层间接性,因此被称作双表格模型。IBM的系统对象模型SOM也依赖于这种模型。 一个古老的实现方法是,在每一个派生类对象中存放一个虚基类指针而非传统对象模型中的基类对象本身,对虚基类的访问通过指针间接实现,以此实现共享。 ---- C++对象模型 上述模型的Extension部分其实已经涵盖了部分对象模型的静态结构,而对象模型的生成与维护则更多见原书中的一系列章节。 Bjarne Stroustrup设计的C++对象模型从简单对象模型派生而来,对内存空间和存取时间做了优化。
Markdown 画图工具 Processon 1,关于对象 从这篇博客开始真正介绍C++对象模型,前边BB了那么多没用的,终于开始了C++对模型的分析。 关于C++对象模型的介绍,我将根据《深度探索C++对象模型》这本书,其书中的每一章,对应一篇博客,博客内容为自己对这本书的理解和补充吧。 pd.init(&pd); } 1.2 class 需要指出的是,C++类的非static的成员函数都有一个隐式的参数,即this(class object *const this)指针(对象的首地址) C++在内存布局以及存取时间上主要的额外负担是虚函数(即链接时的多态)和虚继承(即多次出现在继承体系中的父类,在子类对象中有一个单一共享的实例,其最典型的是菱形继承) 另外,需要指出的是,C++中class class point2d的对象对应的内存布局 通过对比point和point2d的对象内存布局,可知,如果父类中定义了虚函数,并且在子类中进行了重写,则在子类的对象模型中,用子类重写的函数的地址将父类的虚函数地址替换掉
领8888元新春采购礼包,抢爆款2核2G云服务器95元/年起,个人开发者加享折上折
gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.10)
只说C++对象模型在内存中如何分配这是不现实的,所以这里选择VS 2013作为调试环境具体探讨object在内存中分配情况.目录给出了具体要探讨的所有模型,正文分标题依次讨论.水平有限,如有错误之处请多包涵如若能及时反馈于我请接受我的谢意 简单对象模型 首先给出具体的模型和类的代码,然后我们会验证模型是否正确:) class base { public: base() :baseData(5) {} virtual 单继承对象模型 只要明白了简单对象模型接来的单/多继承也就变得很简单了. derivedDataPtr = (int *)(&d) + 2; std::cout << *baseDataPtr<<*derivedDataPtr; system("pause"); } 多继承对象模型 std::cout << *base1_dataPtr << *base2_dataPtr << *derived_dataPtr; system("pause"); } 菱形多继承对象模型
subl $4, %esp pushl $2 pushl $1 leal -24(%ebp), %eax //eax=ebp-24, b对象首地址 leal -4(%ecx), %esp .cfi_def_cfa 4, 4 ret .cfi_endproc 2.图示 3.参考 《深度探索C+ +对象模型》
成员函数和成员变量分开存储 只有非静态成员变量才属于对象上。 每个空对象占用的内存空间为:1。c++编译器会给每个空对象也分配一个内存空间,是为了区分空对象占内存的位置。 每个空对象应该有一个独一无二的空间。 函数也不会占用对象空间,所有对象共享一个函数实例。 sizeof(p1) << endl; } int main() { test(); system("pause"); return 0; } 此时输出: 1 4 4 说明了:空对象也是有 静态成员变量并不属于特定的某一个对象,同理,静态成员函数也不属于某一个对象。进一步来说:所有对象共享一个成员函数实例。
跟着箭头走是第一个虚表,存放这个类中有几个虚函数,就像Shape类有两个虚函数,所以就存放这两个虚函数的调用地址;后面接着箭头走的就是经过Shape对象调用的虚函数 再看第二个类,它继承了Shape的全部再加上它自身的数据类型 直接调用此类中的即可 //如果不是指针调用 quadShape b; Shape a = (Shape)b; a.draw(); //输出的也必然是shape里的draw,因为是静态绑定,什么对象调用 mm.push_back(ss1); ss->draw(); ss1->draw(); cout << mm.size() << endl; //对于ss指针对象 所以其实qs.vfunc2()等价于Shape::vfunc2(&quadShape); 在vfunc2()函数里,draw()其实也就是this->draw();(this就是quadShape的指针对象
subl $12, %esp leal -20(%ebp), %eax //eax=bar的首地址 pushl %eax //bar对象首地址压栈 addl $4, %eax //eax=eax+4,即foo的首地址 subl $12, %esp pushl %eax //foo对象的
1.何为C++对象模型? 引用《深度探索C++对象模型》这本书中的话: 有两个概念可以解释C++对象模型: 语言中直接支持面向对象程序设计的部分。 对于各种支持的底层实现机制。 对象模型概述:介绍简单对象模型、表格驱动对象模型,以及非继承情况下的C++对象模型。 继承下的C++对象模型。 4.3.非继承下的C++对象模型 概述:在此模型下,nonstatic 数据成员被置于每一个类对象中,而static数据成员被置于类对象之外。 这与上述的C++对象模型相符合。 这个结果与我们的C++对象模型图完全符合。
这篇文章主要介绍了详解C++对象模型和this指针,是C++入门学习中的基础知识,需要的朋友可以参考下,希望能够给你带来帮助对象模型成员变量和成员函数分开存储一、只有非静态成员变量才属于类的对象上空对象占用字节为 所以每个空对象都会有一个自己的内存地址。 m_B = 0;非静态成员变量 属于类的对象上静态成员变量,不属于类对象上所以不考虑在内四、class Person{int m_A;// 非静态成员变量 属于类的对象上static int m_B;/ voidfunc(){}静态成员函数也不会增加 不属于类对象上this指针用于区分类中多个对象调用函数时,分别都是哪个函数在调用自己。 this 指向被调用的对象,此时为 p1。
本文参考深度探索C++对象模型 编译器为未定义构造函数的类合成默认构造和拷贝构造函数 如果你已经开始点头了,那么你和我一样,陷入了深深的误解。 当我看到书中作者的这句话时,几乎是一身冷汗。 在C++中,class和struct在某些实现中是转换等同的。那么,为什么还需要合成构造函数呢?按C的做法来不就好了么。 同理,在C++中,完全也可以进行这样的处理,而根本不需要合成一个构造函数! 这样的情形,我们称之为trivial. ---- 那么,什么时候才叫nontrivial呢。
本文参考深度探索C++对象模型/ISO文档 析构函数必须为虚,构造函数不能为虚,因为在对象完全构造之前是没有类型的,也不存在虚表,所以虚构造函数也就不可能发生。 但是,我从短暂的人生当中学到一件事......越是玩弄C++,就越会发现人类的能力是有极限的......除非超越人类。CC,我不做人了! 在C++中,这种idiom又被称为“虚构造函数“,是基于语法的拓展。 这里的虚构造函数,能够按照指针指向的实际对象给出多态式的拷贝与默认构造。
开发环境 VC6.0 编辑器 Cmd Markdown C++中delete表达式执行的操作是:1,调用析构函数;2,释放对象内存(operator delete(…))。 如果父类的析构函数没有声明为virtual函数,且子类中至少存在一个virtual函数,此时将子类的对象地址赋值给父类指针。 当对父类的指针执行delete操作时,会调用父类析构函数,然后在释放内存时(即delete表达式执行的操作的2,释放对象内存)出现崩溃。
原因分析 深度探索C++对象模型 1.3 章节 https://github.com/wangcy6/weekly/blob/master/reading-notes/object-model/ 译成中文就是,编译器必须要确保如果一个对象有一个或多个vptr,这些vptr不是由原对象来初始化或改变的。 也就是说:当使用赋值的方式或拷贝构造的方式创建一个对象时,这个对象的vptr与源对象无关。 +对象模型》的4.2节能够找到完美答案,具体摘抄如下: “表格中的virtual functions地址是如何被建构起来的? +对象模型-构造函数语义学 补充内容 错误理解1 :如果类没有定义任何构造函数,编译器一定会自动生成默认的构造函数 注意:这种说法是错误的(编译器太懒了) 正确的说法: 惟有默认构造函数”被需要“的时候编译器才会合成默认构造函数 编程规范 不要在构造函数中调用虚函数 3 C++编译期多态与运行期多态 补充内容 ?
本文参考深度探索C++对象模型 ---- 我们常常使用基类指针指向派生类对象,那么,为什么基类指针能够如此轻松的调用派生类的方法呢?在多继承的情况下,this指针必须经过调整,才能正确地找到虚表。 下文为你介绍多继承模型下的指针偏移机制 ---- 指针偏移存在机制: 设一个多继承的类内存布局如下,单词代表对象首地址。 函数较大时,产生多重进入点,函数体分为(1)调整this (2)执行自定义函数码,根据是否需要调整,通过thunks跳转至对应的进入点 address points: 虚函数期待获得的是引入虚函数的类对象的地址 换而言之,它首先确保了转入的地址能够正确的指向对应的调用对象,此后再进行传递。
, %eax //eax=0 subl $12, %esp //esp=esp-12 pushl -28(%ebp) //push xx.this, 即xx对象首地址压栈 subl $12, %esp pushl %ebx //push px.this call _ZN1XC1Ev //X::X(),构造px指针指向的对象 movl %ebx, -16(%ebp) //[ebp-16]=px.this subl $12, %esp pushl -28(%ebp) //xx对象 .cfi_endproc 2,通过分析cpp代码对应的汇编,发现foobar函数在内部被转化为: void foobar(X *result) { //构造result指向的对象
先说结论:C++的类成员函数和C函数实质是一样的,只是C++类成员函数多了隐藏参数this。 通过本文的演示,可以看见这背后的一切,完全可C函数方式调用C++类普通成员函数和C++类虚拟成员函数。 为了实现C函数方式调用C++类成员函数,准备两个文件:。 1) 被调用的C++类成员函数源代码文件aaa.cpp #include // fprintf class X { public: void xxx(); private: int m; int n printf("m=%d, n=%d\n", m, n); } 把aaa.cpp编译成共享库: $ g++ -g -o libaaa.so aaa.cpp -fPIC -shared 2) 调用的C+ (*XXX)(struct X*); // 参数实为aaa.cpp中类X的this指针 // 需要指定一个命令行参数argv[1], // 值为aaa.cpp中类X的成员函数xxx的名字, // 因为C+
C++对象模型和this指针 成员变量和成员函数分开存储 在C++中,类内的成员变量和成员函数分开存储 只有非静态成员变量才属于类的对象上 class Person { public: Person this指针概念 通过4.3.1我们知道在C++中成员变量和成员函数是分开存储的 每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码 那么问题是:这一块代码是如何区分那个对象调用自己的呢 c++通过提供特殊的对象指针,this指针,解决上述问题。 p2.age = " << p2.age << endl; } int main() { test01(); system("pause"); return 0; } 空指针访问成员函数 C+ : 声明对象前加const称该对象为常对象 常对象只能调用常函数 示例: //常函数 class Person { public: Person() { m_A = 0; m_B = 0;
开发环境 VC6.0 编辑器 Cmd Markdown 关于C/C++中基本类型(如:int,int*等)作为函数参数时,是通过将该变量的值压栈来进行参数传递;本文通过C++反汇编代码分析了当对象作为函数参数时 obj对象分配内存空间,然后将对象arg的首地址压栈;2,调用拷贝构造函数(此为C++中三种调用拷贝构造函数情况之一),将arg的数据成员拷贝至obj;3,执行show()函数体(此时,ebp+8即为obj //C++源码。 0040D4D9 lea eax,[ebp-0Ch] 0040D4DC push eax //对Basexx的this(即Basexx对象的首地址)指针压栈。 0040D4E5 lea edx,[ebp-8] 0040D4E8 push edx //对Basex的this(即Basex对象的首地址)指针压栈。
腾讯云对象存储数据处理方案主要针对于存储于腾讯云对象存储COS中的数据内容进行处理加工,满足压缩、转码、编辑、分析等多种诉求,激活数据价值。
扫码关注腾讯云开发者
领取腾讯云代金券