• C/C++中struct中内存对齐规则


    前言

    先来预热一下,下面两个简单的结构体,假设不在IDE运行测试,你能快速说出输出结果吗?不能,往下看。

    struct B{
       int a;
       char b;
       char c;
    };
    
    struct C{
       char a;
       int b;
       char c;
    };
    
    cout<<"Size Of Struct B: "<<sizeof(struct B)<<endl;
    
    cout<<"Size Of Struct C: "<<sizeof(struct C)<<endl;

    发现一直不能快速计算出来,故怒总结本文,否则稍微改变一下结构体又会被蒙圈了,实在不能忍受这种感觉,本文力求给出最简单明了的方法。

    内存对齐规则

    强烈建议你先跳过下面无聊的规则,看完本文再来,如果你执意要看我也没办法…

    1、没有#pragma pack宏的对齐规则

    • 1.结构体的起始存储位置必须是能够被该结构体中最大的数据类型所整除。

    • 2.每个数据成员存储的起始位置是自身大小的整数倍(比如int在32位机为4字节,则int型成员要从4的整数倍地址开始存储)。

    • 3.结构体总大小(也就是sizeof的结果),必须是该结构体成员中最大的对齐模数的整数倍。若不满足,会根据需要自动填充空缺的字节。

    • 4.结构体包含另一个结构体成员,则被包含的结构体成员要从其原始结构体内部最大对齐模数的整数倍地址开始存储。(比如struct a里存有struct b,b里有char,int,double等元素,那b应该从8的整数倍开始存储。)

    • 5.结构体包含数组成员,比如char a[3],它的对齐方式和分别写3个char是一样的,也就是说它还是按一个字节对齐。如果写:typedef char Array[3],Array这种类型的对齐方式还是按一个字节对齐,而不是按它的长度3对齐。

    • 6.结构体包含共用体成员,则该共用体成员要从其原始共用体内部最大对齐模数的整数倍地址开始存储。

    2、存在#pragma pack宏的对齐

    #pragma pack (n)    //编译器将按照n个字节对齐  
    #pragma pack ()     //取消自定义字节对齐方式

    那么对齐规则就变成下面的

    • 结构,联合,或者类的数据成员,第一个放在偏移为0的地方,以后每个数据成员的对齐,按照#pragma pack指定的数值和自身对齐模数中较小的那个。

    忽略了上面的对齐规则,下面来看看在C++中数据类型的内存分配大小。

    数据类型字节大小
    char 1
    bool 1
    short 2
    int 4
    long 4
    float 4
    double 8
    long long 8
    long double 8

    攻略

    确定一个结构体大小大致需要下面两个步骤即可

    • 步骤一:前面单元的大小必须是后面单元大小的整数倍,如果不是就补齐

    • 步骤二:整个结构体的大小必须是最大字节的整数倍

    卧槽,对比上面凌乱的对齐规则,就上面两个步骤真可以求出结构体大小?不信?下面来看看实战分析

    首先看看空结构体,C++编译器会在空类或空结构体中增加一个虚设的字节,以确保不同的对象都具有不同的地址。

    struct A  
    {  
    
    }; 
    
    cout<<"空结构体:"<<sizeof(struct A)<<endl;  //毫无疑问输出 1

    接下来分析开头的两个结构体:(S2010+64位机器下进行调试,并将地址复制出来)

    struct B{
     int a;
     char b;
     char c;
    };
    
    //测试:获取内存分配地址数据
     B b = {1,'a','b'};
     B *pb_s = &b     // 0x003efa8c
     int *pb_a = &b.a // 0x003efa8c
     char *pb_b = &b.b// 0x003efa90
     char *pb_c = &b.c// 0x003efa91
    
    cout<<"Struct B:"<<sizeof(struct B)<<endl; //输出为8

    分析:

    • 首先确定最大单元:int a -> 4字节

    • 第一个单元是int a,是4字节,第二个是char b,1个字节,那么根据步骤一检查 4 = 1*N(第一个是第二个的整数倍成立,因此不需要补齐什么了),目前累计大小为5字节

    • 再看下一个char c,同样满足步骤一,所以放下c后共6字节

    • 最后检测步骤二是否满足,6不是4的整数倍,需要补齐,所以最后结果为8字节

    再来看看第二个结构体

    struct C{
     char a;
     int b;
     char c;
    };
    
     C c = {'a',1,'b'};
     C *pc_s = &c     // 0x0047fa28
     char *pc_a = &c.a// 0x0047fa28
     int *pc_b = &c.b // 0x0047fa2c
     char *pc_c = &c.c// 0x0047fa30
    	
     cout<<"Struct C:"<<sizeof(struct C)<<endl; //输出12

    分析

    • 首先确定最大单元:int b - 4字节

    • 第一个单元是char a,是1字节,第二个是int b,4个字节,那么根据步骤一检查 1 != 4*N,所以第一个单元需要补齐到4字节才满足条件,目前累计大小为8字节

    • 再看下一个char c,4 = 1*N,所以放下c后共9字节

    • 最后检测步骤二是否满足,9不是4的整数倍,需要补齐,所以最后结果为12字节

    好了,看到这里你可以回去看看内存的对齐规则,另外struct/class/union内存对齐原则都是一样的,但需要注意union的原理。遇到复杂情况,要做的就是冷静分析。如果有错误,欢迎指出!(不敢误人子弟啊)

  • 相关阅读:
    MMSkeleton 快速开始,使用 WebCam 测试
    【数据结构】用详细图文把「栈」搞明白(原理篇)
    详解|写完这篇文章我终于搞懂链表了
    如何掌握 C 语言的一大利器——指针?
    图示加代码 搞懂线性表(一)
    Kafka工作原理与工作过程
    Zookeeper的安装及集群搭建
    Linux中JDK安装
    Nginx性能调优
    Nginx配置--静态代理&反向代理&负载均衡&动静分离&虚拟主机
  • 原文地址:https://www.cnblogs.com/maji233/p/11439880.html
Copyright © 2020-2023  润新知