书里给了一段代码,假如有个结构体如下:
struct test {
    char a;
    int b;
    long c;
    void* d;
    int e;
    char* f
}
这个结构体的大小是多少呢?
 
先来看一下 C 语言中不同数据类型的长度,因操作系统而异:
数据类型
bool
char
short
int
float
double
long
long long
指针
长度(字节/64位)
1
1
2
4
4
8
8
8
8
长度(字节/32位)
1
1
2
4
4
8
4
8
4
假设是64位机器,则上面代码段中的结构体总大小是 1+4+8+8+4+8=33,然而正确的答案是 40!
这是因为结构体按照8字节对齐,如图所示:
虽然char a 只占了1字节,int b 只占了4字节,但根据 8 字节对齐后,a 和 b 之间空了 3 字节。同样,char* f 和 int e 之间空了 4 字节,因此总大小为 40 字节。
 
以上是书中对结构体对齐的介绍,这么简单!我稍微改了一下结构体,发现自己还是不能快速算出结构体的大小,而书中关于结构体对齐的介绍有限,因此补了一下结构体对齐这方面的知识。
首先是 3 个基本概念:
1) 自身对齐值:
    数据类型的自身对齐值参见上面的表格,如:64位机中,char型数据自身对齐值为 1 字节,int型为 4 字节等。
    结构体或类的自身对齐值就是其成员中自身对齐值最大的那个值。如:上面的 test 结构体,其自身对齐值是 8,因为有 long 型和指针型,它们的长度最大,都是 8。
2) 指定对齐值:#pragma pack (n),n 就是对齐系数。如:#pragma pack (2) 指定按 2 字节对齐。
3) 数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中较小者,即有效对齐值=min{自身对齐值,当前指定的pack值}。
 
然后是对齐原则:
1) 结构体的起始存储位置能够被该结构体中最大的数据类型的大小所整除;
2) 每个数据成员存储的起始位置是自身大小的整数倍(比如int在 64 位机为 4 字节,则int型成员要从 4 的整数倍地址开始存储),若不满足,会根据需要自动填充空缺的字节;
3) 结构体的总大小为该结构体最大基本类型成员大小的整数倍,若不满足,会根据需要自动填充空缺的字节。
简而言之就是:
1) 第一个成员在与结构体变量偏移量(offset)为0的地址处。
2) 其他成员变量要对齐到有效对齐值的整数倍的地址处。
3) 结构体总大小为最大自身对齐值的整数倍。
 
补充说明:
1) 如果结构体包含另一个结构体成员,则被包含的结构体成员要从其原始结构体内部最大对齐模数的整数倍地址开始存储。比如 struct a 里存有struct b,b 里有 char,int,double 等元素,而 double 的长度是8,那 b 应该从 8 的整数倍开始存储。
2) 如果结构体包含数组成员,比如 char a[3],它的对齐方式和分别写 3 个 char 是一样的,即它还是按一个字节对齐。如果写:typedef char Array[3],Array这种类型的对齐方式还是按 1 个字节对齐,而不是按它的长度 3 对齐。
3) 如果结构体包含共用体成员,则该共用体成员要从其原始共用体内部最大对齐模数的整数倍地址开始存储。
4) 一般情况下32位默认 4 字节对齐,64位默认 8 字节对齐。
 
练习:按 4 字节对齐,struct A 的大小是多少?
struct A {
    long a1;
    short a2;
    int a3;
    int *a4;
};
long a1;    //8
short a2;  //2 8+2=10(不是4的倍数)对齐到4的倍数12
int a3;     //4 4+12=16(4的倍数)
int *a4;   //8 8+16=24(4的倍数)
所以 struct A 的大小是 24。
 
有网友说:快速计算总大小可以归纳成 2 步:
1) 前面单元的大小必须是后面单元大小的整数倍,如果不是就补齐;
2) 整个结构体的大小必须是最大字节的整数倍。
举个例子,结构体如下:
struct B {
    int a;
    char b;
    char c;
};
int a 是 4 字节,char b、c 都是 1 字节,最大的长度是 4 字节;
step1:int a 在前,char b 在后,int a 的长度是 char b 的 4 倍,累计 4+1=5 字节,
          char b 在前,char c 在后,char b 的长度是 char c 的 1 倍,累计 5+1=6 字节;
step2:6(累计大小)不是4(最大长度)的整数倍,所以最后结果为 8。
下图是内存分配图,左图为按 8 对齐,右图为按 4 对齐。
再比如:
struct C {
     char b;
     int a;
     char c;
};
int a 是 4 字节,char b、c 都是 1 字节,最大的长度是 4 字节;
step1:char b 在前,int a 在后,1 != 4*N,所以 b 要补到 4 字节,累计 4+4=8 字节,
       int a 在前,char c 在后,int a 的长度是 char c 的 4 倍,累计 8+1=9 字节;
step2:9(累计大小)不是4(最大长度)的整数倍,所以最后结果为 12。
这个方法很好用,但还是要注意具体情况具体分析。
 
为什么要进行内存对齐?
若无内存对齐情况下,按照连续存储时,1234 5678作为8字节,在结构体中,char c会存储在1号位上,而int i会存储在2345位上,而CPU在读取在访问c的时候,每次访问4个字节,没有什么问题,会先拿出1---4,再拿出5---8,但是int i被切割开了,仍需要做字节切割及字节拼接,效率很低。
而进行内存对齐时,将char c存放在1号位,再偏移3个字节,将int i存储在5--8号位,这样CPU进行访问的时候,不必做字节上的拼接和切割,效率会大大提高。是一种典型的空间换时间以提高效率的方式。

《PHP7底层设计与源码实现》学习笔记2——结构体对齐的更多相关文章

  1. 《PHP7底层设计与源码实现》学习笔记1——PHP7的新特性和源码结构

    <PHP7底层设计与源码实现>一书的作者陈雷亲自给我们授课,大佬现身!但也因此深感自己基础薄弱,遂买了此书.希望看完这本书后,能让我对PHP7底层的认识更上一层楼.好了,言归正传,本书共1 ...

  2. zepto 源码 $.contains 学习笔记

    $.contains(parent,node)  返回值为一个布尔值 ==> boolean parent,node我们需要检查的节点检查父节点是否包含给定的dom节点,如果两者是相同的节点,返 ...

  3. c++ stl源码剖析学习笔记(一)uninitialized_copy()函数

    template <class InputIterator, class ForwardIterator>inline ForwardIterator uninitialized_copy ...

  4. C#学习笔记之结构体

    1.概述 结构是一种与类相似的数据类型,不过它较类更为轻量,一般适用于表示类似Point.Rectangle.Color的对象.基本上结构能办到的类全都能办到,但在某些情况下使用结构更为合适,后面会有 ...

  5. STL源码剖析 学习笔记 MiniSTL

    https://github.com/joeyleeeeeee97 目录: 第二章 空间适配器 第三章 迭代器 第四章 序列式容器(vector,list,deque,stack,heap,prior ...

  6. requests源码阅读学习笔记

    0:此文并不想拆requests的功能,目的仅仅只是让自己以后写的代码更pythonic.可能会涉及到一部分requests的功能模块,但全看心情. 1.另一种类的初始化方式 class Reques ...

  7. c++ stl源码剖析学习笔记(二)iterator

    ITERATOR 迭代器 template<class InputIterator,class T> InputIterator find(InputIterator first,Inpu ...

  8. c++ stl源码剖析学习笔记(三)容器 vector

    stl中容器有很多种 最简单的应该算是vector 一个空间连续的数组 他的构造函数有多个 以其中 template<typename T> vector(size_type n,cons ...

  9. STL源码剖析-学习笔记

    1.模板是一个公式或是蓝图,本身不是类或是函数,需进行实例化的过程.这个过程是在编译期完成的,编译器根据传递的实参,推断出形参的类型,从而实例化相应的函数 2. 后续补充-.

随机推荐

  1. PHP使用PhpAnalysis进行分词

    1.介绍 PHPAnalysis分词程序使用居于unicode的词库,使用反向匹配模式分词,理论上兼容编码更广泛,并且对utf-8编码尤为方便. 下载地址:http://www.phpbone.com ...

  2. Django 学习笔记之模型高级用法

    目录 1 复杂的字段类型 1.1 整数类型的区别 1.2 自增类型的区别 1.3 时间类型 1.4 FilePathField 1.5 FileField 1.6 ImageField 2 关系字段 ...

  3. SpringBoot 定时任务篇

    一. 基于注解@Scheduled默认为单线程,开启多个任务时,任务的执行时机会受上一个任务执行时间的影响. 1.创建定时器 使用SpringBoot基于注解来创建定时任务非常简单,只需几行代码便可完 ...

  4. 综合架构之Rsync备份服务,服务端和客户端配置

    服务端配置(即备份服务器) ps:客户端配置见下方 配置一个新服务的步骤: 第一步:先将该服务下载 yum install -y rsync 第二步:编写服务配置文件 配置文件:/etc/rsyncd ...

  5. C++ 梳理:跑通简单程序

    C++ 结合了三个编程流派: 过程式编程:C 特性,结构化语言,强调过程 面向对象编程:C++ 对于 C 语言的补充,一切皆对象,强调数据 泛型编程(generic programming):由 C+ ...

  6. 高斯混合模型GMM与EM算法的Python实现

    GMM与EM算法的Python实现 高斯混合模型(GMM)是一种常用的聚类模型,通常我们利用最大期望算法(EM)对高斯混合模型中的参数进行估计. 1. 高斯混合模型(Gaussian Mixture ...

  7. 数据库: 安装配置数据库,使用Navicat for MySQL和手机APP 连接测试(如果上一节碰到问题可参考这一节)

       咱就安装上       还有这个                    最终测试请参考上一节 启动MySQL服务主要有以下两种方法: 第一种: 在搜索框中输入“services,msc” ,en ...

  8. idea 2018.1 创建springboot开启找回Run Dashboard

    Run Dashboard 他是一个代替Run窗口的一个更好清晰简介的一个启动器.一般我们需要启动多个窗口时,Run窗口不能让我们直观的看到我们看到每一个端口的变化. 我们可以对比一下这个是Run D ...

  9. 【BigData】Java基础_Eclipse配置自动补全new

    需求描述 在使用Eclipse的时候,每次new一个对象,写起来比较麻烦,以下是配置Eclipse,然后使用快捷键补全new方法的步骤,此配置使用官方属于叫做:配置自动分配局部变量 配置步骤 打开Wi ...

  10. Java集合详解3:一文读懂Iterator,fail-fast机制与比较器

    <Java集合详解系列>是我在完成夯实Java基础篇的系列博客后准备开始写的新系列. 这些文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查 ...