书里给了一段代码,假如有个结构体如下:
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. CORE DUMP生成调试

    之前我调试嵌入式linux程序,一般是借助ucontext库,在发生段错误时,直接将错误函数打印出来.有同事建议我使用core dump,于是我今天在嵌入式板卡尝试了core文件的生成,但是也是几经波 ...

  2. Anaconda 安装 tensorflow 和 keras

    说明:此操作是在 Anaconda Prompt 窗口完成的 CPU版 tensorflow 的安装. 1.用 conda 创建虚拟环境 tensorflow python=3.6 conda cre ...

  3. 遍历php的_SERVER数组键值信息

    $_SERVER 是一个包含了诸如头信息(header).路径(path).以及脚本位置(script locations)等等信息的数组.这个数组中的项目由 Web 服务器创建.不能保证每个服务器都 ...

  4. curl-7.21.2

    curl 源码编译 自己定义的库编译 https://blog.csdn.net/initiallht/article/details/92655025 静态库,debug,x86nmake /f M ...

  5. Consul 知识点

    平时开发时,一般使用consul dev模式,开发模式下kv存储不会持久化存储,全在内存中(重启consul就丢了!),所以一般建议yml配置文件内容,在项目中单独存一个文件,启动调试时,直接把配置文 ...

  6. 题解:洛谷P1891 疯狂LCM

    原题链接 题目描述 描述: 众所周知,czmppppp是数学大神犇.一天,他给众蒟蒻们出了一道数论题,蒟蒻们都惊呆了... 给定正整数N,求LCM(1,N)+LCM(2,N)+...+LCM(N,N) ...

  7. html--前端css常用属性

    1.颜色属性 <div style="color:blueviolet">ppppp</div> 输入颜色英文单词 <div style=" ...

  8. 【Excel】IF函数

    判断条件: 一版判断:1>2大于   1<2小于   1=2等于   1<>2不等于  1>=2 1<=2 交集:AND() 并集:OR() 多条件:以后补

  9. three.js 居中-模型

    api: 代码: <!DOCTYPE html> <html lang="en"> <head> <title>three.js w ...

  10. 蒟蒻所见之DP

    本文有错是正常的,因为这只是一部成长史,并非教学博文. 会常年更下去. 2019.10.24 DP,核心只是"表格法"而已. DP题真正所考察的,是: 1.对问题的描述.简化以及归 ...