本篇随笔讨论一个比较冷门的知识,继承结构中内存对齐的问题,如今内存越来越大也越来越便宜,大部分人都已经不再关注内存对齐的问题了。但是作为一个有追求的技术人员,实现功能永远都是最基本的要求,把代码优化到自己想要的样子才能从中找到真正的愉悦感。这便是我们追求细节的意义。

声明:以下例子,以x86_64 64bit编译器编译的结果作为参考,32位编译器会有不同结果,这里不讨论。  

目录
引子-内存对齐示例与规则
进阶-继承体系中的内存对齐

引子-内存对齐示例与规则:      

讨论内存对齐,就要牵涉到#pragma pack(n)中定义n的大小。C语言中针对结构体提出了内存对齐的概念。下面请看代码:

#include <iostream>

using namespace std;

#pragma pack(8)

struct Ethanol{
char ch;
short sh;
int it;
}; struct Ether{
char ch;
int it;
short sh;
}; int main()
{ cout<<"Ethanol:"<<sizeof(Ethanol)<<endl;
cout<<"Ether :"<<sizeof(Ether)<<endl;
return 0;
}

运行结果:

如果不清楚上面的数据如何产生,请对照下面内村对齐的规则:

x86(linux 默认#pragma pack(4), window 默认#pragma pack(8))。linux 最大支持 4 字节对齐。

1 )取 pack(n)的值(n= 1 2 4 8--),取结构体中类型最大值 m。两者取小即为外对齐大小 Y= (m<n?m:n)。
2 )将每一个结构体的成员大小与 Y 比较取小者为 X,作为内对齐大小.
3 )所谓按 X 对齐,即为地址(设起始地址为 0)能被 x 整除的地方开始存放数据。
4 )外部对齐原则是依据 Y 的值(Y 的最小整数倍),进行补空操作。

原则:先内后外。

结构体内元素不同的排列组合方式大概可以用化学中的同分异构体来比喻,比如乙醇和甲醚,他们有着完全相同的成分,但是化学性质却不同。所以上面我用了Ethanol和Ether两个名字来命名结构体。

过渡到C++中,类与结构提不仅可以通过组合的方式构成新类型,还可以通过继承的方式来实现代码的重用

由结构体延伸到类看看包含关系中的类大小:

#include <iostream>

using namespace std;

#pragma pack(8)

class P1
{
public:
P1(){}
virtual void printP1(){ }
protected:
int p1;
};class Son
{
public:
Son(){}
private:
int son;
P1 P1;
};
int main()
{
cout<<"P1_Size :"<<sizeof(P1)<<endl;
cout<<"Son_Size:"<<sizeof(Son)<<endl;
}

运行结果:

以上运行结果,如果对C++了解,那么应该知道含有虚函数的类,除了成员变量大小外,虚函数表指针排在最前面。所以虚函数表指针的大小也要算进去。对照上面的内存对齐规则,我们计算一下上面的结果是怎么来的:

#pragma pack(n) ,n = 8。系统和编译器均为为64位,所以指针的大小是8Byte。

1)取P1中的最大类型大小m(虚函数表指针大小)与n作比较,m==n,所以外对齐Y==8。

2)结构体内每一个元素与Y做比较,取小者做内对其。所以,排列为下图:

所谓对齐,就是以0为起始地址,对元素进行排列,使用一个来能够整除内对齐的地址来安放后一个数据元素。

内对齐8+4=12

3)但是12不是外对齐的倍数,要用外对齐的最小整数倍来补齐8X2=16正好是这个类的大小。

进阶-继承体系中的内存对齐

继承现象:

上面用足组合的关系来计算一个类的大小是符合内存对齐规则的,这种包含关系与内存结构体中的对齐没有任何区别,那如果改为继承关系呢?

#include <iostream>

using namespace std;

#pragma pack(8)

class P1
{
public:
P1(){
cout<<"P1() :"<<(long long)this<<endl;
}
virtual void printP1(){ }
protected:
int p1;
}; class Son:public P1
{
public:
Son()
{
} private: int son;
// P1 P1;
};
int main()
{
cout<<"P1_Size :"<<sizeof(P1)<<endl;
cout<<"Son_Size:"<<sizeof(Son)<<endl;
}

运行结果:

仿佛有4个字节丢失了。。。。。。

 查看内存排列:

我们在派生类的构造函数看一下son元素的地址,此地址即使son类型的起始地址,也是基类类型P1的结束地址:

#include <iostream>

using namespace std;

#pragma pack(8)

class P1
{
public:
P1()
{
cout<<"Base_addr:"<<(long long)this<<endl;
}
virtual void printP1(){ }
protected:
int p1;
}; class Son:public P1
{
public:
Son()
{
cout<<"son_addr:"<<(long long)&son<<endl;
     cout<<"Base_inherit_size:"<<(long long)&son - (long long)this<<endl;
  }

private: int son; // P1 P1;

};

int main()
{
cout<<"P1_Size :"<<sizeof(P1)<<endl;
cout<<"Son_Size:"<<sizeof(Son)<<endl;
Son son;
}

运行结果:

由此结果我们得到的结论是派生类继承并非完全继承了基类的大小,却继承了基类未作外对齐的大小,由此运行结果我们可以看出派生类(Son)类只继承了基类(P1)12个字节。对此感到疑惑的朋友可以继续使用其它示例来检验这个结果。

由此我们给出以下总结

继承体系中内存对齐的实质:

所谓继承关系实质上是派生类继承了基类中的元素(虚函数表和成员变量),而非继承已经内存对齐固化的基类结构,基类中的元素被继承到派生类中与派生类中新添加的元素需要重新按着元素的排列组合内存对齐。所以就有了上面包含关系与继承关系完全不一样的大小的现象。

 

C++继承体系中的内存对齐的更多相关文章

  1. C++继承体系中的内存分段

    ---------------综述与目录-------------- 讨论这个问题之前我们先明确类的结构,一个类的大概组成,下面的很多分类名词都是我个人杜撰,为的就是让读者看懂能够区分,下面分别分类: ...

  2. C语言中的内存对齐

    最近看了好多,也编了好多C语言的浩强哥书后的题,总觉的很不爽,真的真的好怀念linux驱动的代码,好怀念那下划线,那结构体,虽然自己还很菜. 同时看了一遍陈正冲老师的C语言深度剖析,收益很多,又把唐老 ...

  3. C++ 继承体系中的名称覆盖

    首先一个简单的样例: int x; int f() { double x; cin >> x; return x; } 在上述代码中.函数f的局部变量x掩盖了全局变量x.这得从 " ...

  4. 关于Java继承体系中this的表示关系

    Java的继承体系中,因为有重写的概念,所以说this在子父类之间的调用到底是谁的方法,或者成员属性,的问题是一个值得思考的问题; 先说结论:如果在测试类中调用的是子父类同名的成员属性,这个this. ...

  5. C/C++中的内存对齐 C/C++中的内存对齐

    一.什么是内存对齐.为什么需要内存对齐? 现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特 定的内存地址 ...

  6. C++中的内存对齐

    在我们的程序中,数据结构还有变量等等都需要占有内存,在很多系统中,它都要求内存分配的时候要对齐,这样做的好处就是可以提高访问内存的速度. 我们还是先来看一段简单的程序: 程序一 1 #include  ...

  7. Delphi中的内存对齐 与 Packed关键字

    以delphi为例:TTest = recordc1: char;i1: Integer;c2: char;c3: Char;end;这个结构如果用sizeof取其占用的内存大小,是多少呢,是1+4+ ...

  8. C/C++中的内存对齐问题和pragma pack命令详解

    这个内存对齐问题,居然影响到了sizeof(struct)的结果值.突然想到了之前写的一个API库里,有个API是向后台服务程序发送socket请求.其中的socket数据包是一个结构体.在发送soc ...

  9. C语言中的内存对齐问题

    问题 突然收到了一个问题: #include<stdio.h> #include <math.h> struct icd { int a; //4 char b; //1 do ...

随机推荐

  1. C语言:输出数字各个位的数字及和

    #include <stdio.h> int main() { char sh[13][5]={"个","十","百",&quo ...

  2. Spring Boot入门学习必知道企业常用的Starter

    SpringBoot企业常用的 starter SpringBoot简介 SpringBoot运行 SpringBoot目录结构 整合JdbcTemplate @RestController 整合JS ...

  3. LeetCode 982. Triples with Bitwise AND Equal To Zero

    题目链接:https://leetcode.com/problems/triples-with-bitwise-and-equal-to-zero/ 题意,已知数组A,长度不超过1000,最大的数不超 ...

  4. 谷粒商城--分布式基础篇P28~P101(完结)

    谷粒商城--分布式基础篇P28~P101(完结) 前面1-27节主要是环境配置特别难,后面的28~101节主要是前端编写的代码较多以及后台的CRUD操作比较多.因为内容很多,所以我是根据自己想学的点进 ...

  5. UnitTest 用法

    功能 1.能组织多个用例去执行 2.提供丰富的断言方法 3.提供丰富的日志与测试结果 核心要素 1.TestCase 2.TestSuite 3.TextTestRunner 4.Fixture 用法 ...

  6. 初识Stream API + Lambda表达式

    使用新特性简化代码,增强可读性 package com.gg.java8; import java.util.*; import org.junit.Test; public class TestLa ...

  7. Vue项目发布的问题--http://localhost:8088/static/fonts/fontawesome-webfont.af7ae50.woff2

    问题:ngnix将8080转成80对外访问,找不对woff2等文件 一\ 搭建环境 ngnix-->conf中 server { listen 80; server_name 10.9.240. ...

  8. SQL注入之二次,加解密,DNS等注入

    #sql注入之二次注入 1.注入原理 二次注入可以理解为,构造恶意数据存储在数据库后,恶意数据被读取并进入到了SQL查询语句所导致的注入.恶意数据插入到数据库时被处理的数据又被还原并存储在数据库中,当 ...

  9. Python实用案例,Python脚本,Python实现自动监测Github项目并打开网页

    往期回顾 Python实现文件自动归类 前言: 今天我们就利用Python脚本实现Github项目的更新,提醒方式是邮箱.直接开整~ 项目地址: https://github.com/kenwoodj ...

  10. RTC为何这么火?

    国内疫情已经接近尾声,有疫情的原因孵化的音视频互动类 App数量出现井喷式增长,通讯场景被资本关注,市场持续走高.在线教育.娱乐社交.直播带货等领域逆势增长,也带动了开发者们对于 IM 和RTC能力的 ...