《C++应用程序性能优化》《深度探索C++对象模型》笔记

#include<iostream>
using namespace std;
class student
{
public:
static int count;
int value;
char c;
student();
virtual ~student();
int getvalue();
virtual void foo(void);
static void addcount();
};
int main()
{
cout << sizeof(student) << endl;
return 0;
}

  运行结果:

解析:静态数据成员static int count 存储在全局/静态存储区中,并不作为对象占据的内存的一部分,sizeof返回的大小不包括count所占据的内存的大小,而非静态数据成员int value和char c存储在对象占据的的内存中,不论是在全局/静态存储区,还是在堆上或栈上。value内存大小是4个字节,c是1一个字节,但是32位计算机为了提高效率按4个字节对齐,因此也占据了4个字节。此外该类还有两个成员函数(其中一个构造函数)和两个虚函数(一个虚析构函数)和一个静态成员函数,在这些函数中,只有虚函数会分配一个虚函数指针,指向虚函数地址表,叫做“虚函数表”,这个虚函数指针会占据4个字节。

那么虚函数指针存放在哪里呢?现在写一个程序验证一下虚函数指针存放的位置:

#include<iostream>
using namespace std;
class student
{
public:
static int count;
int value;
char c;
student() {};
virtual ~student() {};
int getvalue();
virtual void foo() {};
static void addcount();
};
int main()
{
student stu;
cout << &stu << endl;
cout << &stu.value << endl;
cout << &stu.c << endl;
return 0;
}

  运行结果如下:

从上图可知,对象的起始地址是0x003afd10,接着是value的地址0x003afd14两个地址之间相差4个字节,从这里我们也可以知道,虚函数指针存放在对象开始的4个字节。

(1)非静态数据成员是影响占据内存大小的主要因素,随着对象数目的增加,非静态数据成员占据的内存会相应的增加。

(2)所有的对象共享一份静态数据成员,所以静态数据成员占据的内存数量不会随着对象数目的增加而增加。

(3)静态数据成员函数和非静态数据成员函数不会影响对象内存的大小,虽然其实现会占据相应的内存空间,同样也不会随着数目的增加而增加。

(4)如果对象中有虚函数会增加一个虚函数指针,不管有多少个虚函数都只有一个虚函数指针。

单继承

#include<iostream>
using namespace std;
class student
{
public:
static int count;
int value;
char c;
student() {};
virtual ~student() {};
int getvalue();
virtual void foo() {};
static void addcount();
};
class student1 :public student
{
int name;
public:
virtual void func() {};
};
int main()
{
cout << sizeof(student) << endl;
cout << sizeof(student1) << endl;
return 0;
}

  运行结果为:

解析:我们可以看到派生类的内存大小是16个字节,基类的内存大小是12个字节,相差4个字节,派生类中增加了一个一个int类型的成员变量4个字节,同时也增加了一个虚函数,此时派生类也需要虚函数表,因此我们可知道派生类和基类使用的是同一个虚函数表。或者说,在派生类构造时不再需要创建一张新的虚函数表,而应该在基类的虚函数表中增加或者修改。

此程序的内存布局图如下:

问:派生类和基类公用一张虚函数表,且派生类会对虚函数表进行增加和修改,那么如果基类有多个派生类,且多个派生类中都对基类的虚函数进行了重载,那么是如何实现的,会不会有冲突?

答:这里首先要理解多态的含义,多态是发生在运行时的,只有当一个基类对象指针/引用指向一个派生类对象时才会发生,此时派生类才会对虚函数表进行修改,而不是在编译时就修改的。

多继承

#include<iostream>
using namespace std;
class student
{
public:
static int count;
int value;
char c;
student() {};
virtual ~student() {};
int getvalue();
virtual void foo() {};
static void addcount();
};
class people
{
public:
virtual void eat() {}
int age;
};
class student1 :public student,public people
{
int name;
public:
virtual void func() {};
};
int main()
{
cout << sizeof(student) << endl;
cout << sizeof(people) << endl;
cout << sizeof(student1) << endl; return 0;
}

  运行结果:

此程序的内存布局如下:

虚继承:为了支持虚继承不同 编译器的做法会有所不同,在vs中通过添加一个虚基类表来实现

#include<iostream>
using namespace std;
class student
{
public:
static int count;
int value;
char c;
student() {};
virtual ~student() {};
int getvalue();
virtual void foo() {};
static void addcount();
};
class people
{
public:
virtual void eat() {}
int age;
};
class student1 :virtual public student, public people
{
int name;
public:
virtual void func() {};
};
int main()
{
cout << sizeof(student) << endl;
cout << sizeof(people) << endl;
cout << sizeof(student1) << endl; return 0;
}

  运行结果:

#include<iostream>
using namespace std;
class student
{
public:
static int count;
int value;
char c;
student() {};
virtual ~student() {};
int getvalue();
virtual void foo() {};
static void addcount();
};
class people
{
public:
virtual void eat() {}
int age;
};
class student1 :virtual public student,virtual public people
{
int name;
public:
virtual void func() {};
};
int main()
{
cout << sizeof(student) << endl;
cout << sizeof(people) << endl;
cout << sizeof(student1) << endl; return 0;
}

  运行结果:

#include<iostream>
using namespace std;
class student
{
public:
static int count;
int value;
char c;
student() {};
virtual ~student() {};
int getvalue();
virtual void foo() {};
static void addcount();
};
class people
{
public:
virtual void eat() {}
int age;
};
class student1 :virtual public student,virtual public people
{
int name;
public:
//virtual void func() {};
};
int main()
{
cout << sizeof(student) << endl;
cout << sizeof(people) << endl;
cout << sizeof(student1) << endl; return 0;
}

  运行结果:

从上面我们可以知道,在虚继承中,每一个基类都会生成一个虚基类表,因此每多一个虚继承内存会增加4,同时如果派生增加一个基类中没有的虚函数也会增加一个指针,内存加4

问题:类中成员变量的初始化顺序?

答:因为我们知道初始化的顺序与析构的顺序相反,也就是说,如果类中有两个成员变量A和B,初始化顺序为A->B,那么在这个类被析构时应该先析构B再析构A,而对于一个类来说构造函数可能有多个,初始化列表也有多个,这个在析构时就没法统一,所以就按照成员变量定义时的顺序来。

C++对象布局的更多相关文章

  1. C++ 类继承的对象布局

    C++多重继承下,对象布局与编译器,是否为虚拟继承都有很大关系,下面将逐一分析其中的差别,相同点为都按照类继承的先后顺序布局(类内按照虚表.成员声明先后顺序排列).该类情况为子类按照继承顺序排列,如c ...

  2. 对象布局已知时 C++ 对象指针的转换时地址调整

    在我调试和研究 netscape 系浏览器插件开发时,注意到了这个问题.即,在对象布局已知(即对象之间具有继承关系)时,不同类型对象的指针进行转换(不管是隐式的从下向上转换,还是强制的从上到下转换)时 ...

  3. C++ 中的对象布局

    C++中的涉及到虚表时,类对象的布局分为:虚表与数据成员,子类包含派生类布局,假设下面一个程序: #include <iostream> using namespace std; clas ...

  4. 15条规则解析JavaScript对象布局(__proto__、prototype、constructor)

    大家都说JavaScript的属性多,记不过来,各种结构复杂不易了解.确实JS是一门入门快提高难的语言,但是也有其他办法可以辅助记忆.下面就来讨论一下JS的一大难点-对象布局,究竟设计JS这门语言的人 ...

  5. javascript--15条规则解析JavaScript对象布局(__proto__、prototype、constructor)

    大家都说JavaScript的属性多,记不过来,各种结构复杂不易了解.确实JS是一门入门快提高难的语言,但是也有其他办法可以辅助记忆.下面就来讨论一下JS的一大难点-对象布局,究竟设计JS这门语言的人 ...

  6. Visual C++ 8.0对象布局的奥秘:虚函数、多继承、虚拟继承(VC直接输出内存布局)

    原文:VC8_Object_Layout_Secret.html 哈哈,从M$ Visual C++ Team的Andy Rich那里又偷学到一招:VC8的隐含编译项/d1reportSingleCl ...

  7. VC++对象布局的奥秘:虚函数、多继承、虚拟继承

    哈哈,从M$ Visual C++ Team的Andy Rich那里又偷学到一招:VC8的隐含编译项/d1reportSingleClassLayout和/d1reportAllClassLayout ...

  8. Revit API创建一个拷贝房间内对象布局命令

    本课程演示创建一个拷贝房间内对象布局命令,完整演示步骤和代码.这个命令把选中房间内的对象复制到其它选中的一个或多个房间中,而且保持与源房间一致的相对位置.通过本讲座使听众知道创建一个二次开发程序很简单 ...

  9. Qt之美(一):d指针/p指针详解(二进制兼容,不能改变它们的对象布局)

    Translated  by  mznewfacer   2011.11.16 首先,看了Xizhi Zhu 的这篇Qt之美(一):D指针/私有实现,对于很多批评不美的同路人,暂且不去评论,只是想支持 ...

  10. Visual C++ 8.0对象布局

    哈哈,从M$ Visual C++ Team的Andy Rich那里又偷学到一招:VC8的隐含编译项/d1reportSingleClassLayout和/d1reportAllClassLayout ...

随机推荐

  1. python asyncio 关闭task

    import asyncio import time async def get_html(sleep_times): print("waiting") await asyncio ...

  2. 从零到一手写基于Redis的分布式锁框架

    1.分布式锁缘由 学习编程初期,我们做的诸如教务系统.成绩管理系统大多是单机架构,单机架构在处理并发的问题上一般是依赖于JDK内置的并发编程类库,如synchronize关键字.Lock类等.随着业务 ...

  3. 成都,我们来啦 | Dubbo 社区开发者日

    [关注 阿里巴巴云原生 公众号,回复关键词"报名",即可参与抽奖!] 活动时间:10 月 26 日 13:00 - 18:00 活动地点:成都市高新区交子大道中海国际中心 233 ...

  4. 2019-6-15-WPF-触摸到事件

    原文:2019-6-15-WPF-触摸到事件 title author date CreateTime categories WPF 触摸到事件 lindexi 2019-06-15 08:58:54 ...

  5. ASP.NET Core: BackgroundService停止(StopAsync)后无法重新启动(StartAsync)的问题

    这里的 BackgroundService 是指: Microsoft.Extensions.Hosting.BackgroundService 1. 问题复现 继承该BackgroundServic ...

  6. NetCoreApi框架搭建(一、swagger插件使用)

    1.首先用vs2017创建新的项目 2.开始引入swagger插件 右击项目=>管理NuGet程序包=>搜索Swashbuckle.AspNetCore点击安装 3.打开Startup.c ...

  7. 利用Java EE里jsp制作登录界面

    jsp连接数据库.百度经验. 1.在新建的Project中右键新建Floder 2.创建名为lib的包 3.创建完毕之后的工程目录 4.接下来解压你下载的mysql的jar包,拷贝其中的.jar文件 ...

  8. mssql SQL Server 2008 阻止保存要求重新创建表的更改问题的设置方法

    解决方法: 工具-〉选项-〉左侧有个 设计器-〉表设计器和数据库设计器 -> 阻止保存要求重新创建表的更改(右侧) 把钩去掉即可.

  9. 统计代码测试覆盖率-Python

    衡量Unit Test(单元测试)是否充分, 覆盖率是一个必要指标, 是检验单元测试的重要依据, 这里针对python unittest 的单元测试覆盖率coverage进行分享. 来自官方的解释: ...

  10. 如何在一个ubuntu系统上搭建SVN版本控制工具

    有话说,由于公司项目部署需要,将Windows工程迁移到Linux,通过调查确定使用Ubuntu的Linux操作系统.那么如何快速搭建和Windows一样快捷方便的开发环境就很重要了.本文讲述如何在一 ...