书里给了一段代码,假如有个结构体如下:
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. ORACLE隐藏参数查看及修改

    查看隐藏参数 select x.ksppinm name, y.ksppstvl value, y.ksppstdf isdefault, decode(bitand(y.ksppstvf,7),1, ...

  2. OPATCH在线补丁

    如果补丁中有online目录就是在线补丁,不需要数据库停机,在线的又分集群和非集群,如下 查看readme可以得知在线补丁打法 $ cat README.txt Oracle Database 11g ...

  3. oracle 查询角色具有的权限

    select * from dba_tab_privs where GRANTEE='角色名';

  4. nginx 安装第三方模块(lua)并热升级

    需求: nginx上将特定请求拒绝,并返回特定值. 解决办法: 使用lua脚本,实现效果. 操作步骤: 安装Luajit环境 重新编译nginx(目标机器上nginx -V 配置一致,并新增两个模块n ...

  5. node 淘宝镜像

    永久使用 打开终端执行 npm config set registry https://registry.npm.taobao.org 临时使用 npm --registry https://regi ...

  6. django 自定义分页,网址存储缓存,CBV

    1. 通过切片控制分页 自定义分页: from django.shortcuts import render # Create your views here. from app01.models i ...

  7. VMware Xcode真机调试

    原因如下:VMware12默认使用usb3.0 ,先给苹果系统关机,然后打开虚拟机设置,更改usb控制器为USB2.0 就可以成功连接了. 问题提示:could not launch “name” p ...

  8. 201671030126 赵佳平 实验十四 团队项目评审&课程学习总结

    项目 内容 这个作业属于那个课程 2016级计算机科学与工程学院软件工程(西北师范大学) 这个作业的要求在哪里 实验十四 团队项目评审&课程学习总结 作业学习目标 掌握软件项目评审会流程:反思 ...

  9. JAVA web 框架集合

    “框架”犹如滔滔江水连绵不绝, 知道有它就好,先掌握自己工作和主流的框架: 在研究好用和新框架. 主流框架教程分享在Java帮帮-免费资源网 其他教程需要时间制作,会陆续分享!!! 152款框架,你还 ...

  10. 转:ubuntu16安装python3.6并将环境设置为系统默认

    按照本文升级到3.6,但pip无法运行了,问题没有解决 1.添加python3.6安装包,并且安装 sudo apt-get install software-properties-common su ...