书里给了一段代码,假如有个结构体如下:
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. Python之路(第四十七篇) 协程:greenlet模块\gevent模块\asyncio模块

    一.协程介绍 协程:是单线程下的并发,又称微线程,纤程.英文名Coroutine.一句话说明什么是线程:协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的. 协程相比于线程,最大的区别在于 ...

  2. thinkphp5 + barcode 生成条形码

    1.去官网下载类库 “https://www.barcodebakery.com/en/download”,选择自己的版本下载 2.解压放到“E:\phpstudy\PHPTutorial\WWW\g ...

  3. 7.redis 集群模式的工作原理能说一下么?在集群模式下,redis 的 key 是如何寻址的?分布式寻址都有哪些算法?了解一致性 hash 算法吗?

    作者:中华石杉 面试题 redis 集群模式的工作原理能说一下么?在集群模式下,redis 的 key 是如何寻址的?分布式寻址都有哪些算法?了解一致性 hash 算法吗? 面试官心理分析 在前几年, ...

  4. django迁移脚本

    执行migrate报错的解决办法: 想知道migrate为什么报错,需要先了解migrate到底做了什么事情 migrate做了什么事情? 1.将相关的迁移脚本翻译成sql语句,然后在数据库中执行 2 ...

  5. Elasticsearch 动态修改replica配置、增删replica

    1. 获取当前所有index配置 curl -XGET http://localhost:9200/_settings 2. 获取某些index的配置 curl -XGET http://localh ...

  6. 让Discuz! X3.2 SEO标题里的“-”支持空格

    打开Discuz!根目录下source\class\helper\helper_seo.php文件找到如下代码: public static function strreplace_strip_spl ...

  7. python操作mysql(增、删、改、查)

    用python操作数据库,特别是做性能测试造存量数据时特别简单方便,比存储过程方便多了. 连接数据库 前提:安装mysql.python,参考:https://www.cnblogs.com/Uncl ...

  8. IConvertible接口

    IConvertible接口:定义特定的方法,这些方法将实现引用或值类型的值转换为具有等效值的公共语言运行库类型. 公共语言运行库类型包括: Boolean.SByte.Byte.Int16.UInt ...

  9. 最全BT磁力搜索引擎索引(整理分享,不断更新...)

    最全BT磁力搜索引擎索引(整理分享,不断更新...) btkitty:http://cnbtkitty.com/(知名的BT磁力搜索,资源很多) idope.se:https://idope.se/( ...

  10. PATB1009说反话

    这里有一点需要注意的就是,PAT里面是禁用gets函数的,所以要换成一个替代函数 那就是下面这个: //这个是定义一个字符串数组 char str[90]; //需要被替代的函数 gets(str) ...