C/C++ struct数据对齐补齐

数据结构对齐补齐

  1. 数据对齐Alignment
  • 结构体中不同类型的数据在内存中按照一定的规则排列,而不总是一个挨一个的顺序排放(便于寻址);
  • 任何成员变量的地址必须是其对齐参数A的整数倍,这个规则叫做数据对齐;

数据对齐会造成结构体内部不同成员变量之间有空隙;

  • 每个成员变量的对齐参数A取值规则:
    • a=min(n, max(self)) 普通类型取#pragma pack(n)、自身大小,二者最小值;
    • a=min(n, max(sizeof element...)) 结构体类型取#pragma pack(n)、最大子变量,二者最小值;

    n为系统对齐参数,一般默认为4,可以通过#pragma pack()类宏自定义

  1. 数据补齐Completion
  • 一个结构体变量的大小必须是C的整数倍,这个规则叫数据补齐;
  • 这种补齐可能造成结构体在最后多占用一些浪费的字节;
  • 结构体补齐参数C的取值规则:c=max(A) 即所有成员的对齐参数A的最大值(一定不会超过pack值,试证明之);

分析举例

// x *
// y y
// z *
typedef struct {
    char c1;  //s=1, a=min(4,1), @+0;对齐参数a=1;
    short s;  //s=2, a=min(4,2), @+2;对齐参数a=2;
    char c2;  //s=1, a=min(4,1), @+4;对齐参数a=1;
} ST1; //6 = n*2, c=max(a)==2;补齐参数c=2

// x x y y
// y y y y
// z z z z
// m * * *
typedef struct {
    short s;  //s=2, a=min(4,2), @+0;对齐参数a=2;
    ST1 st1;  //s=6, a=min(4,max(1,2,1)), @+2;对齐参数a=2;
    int i;    //s=4, a=min(4,4), @+8;对齐参数a=4;
    char c;   //s=1, a=min(4,1), @+12;对齐参数a=1;
} ST7; //16 = n*4, c=max(a)==4;补齐参数c=4;

struct成员对齐,总体补齐

  • 普通类型成员,对齐参数A为(自身类型大小和指定对齐参数n)二者的最小值,即a=min(n, max(self));
  • 结构体类型的成员,对齐参数A为其所有成员对齐参数中的最大值,即a=min(n, max(sizeof element...));
  • 结构体总长度为补齐参数C的整数倍,补齐参数C为所有成员对齐参数最大值,即c=max(A);

结构体中子变量的顺序会影响结构体的大小,占用空间小的子变量写前边可以节约内存空间;

为什么需要内存对齐?

  • CPU对内存的读取是不连续的,而是分成块读取的,块的大小值可以是1,2,4,8,16字节;
  • 当读取操作的数据未对齐,则需要两次总线周期来访问内存,因此性能会大打折扣;
  • 某些硬件平台只能从规定的地址处取某些特定类型的数据,否则抛出硬件异常;

数据结构对齐补齐演示

// 数据结构对齐补齐演示
#include <stdio.h>
#pragma pack(4)

//假设存储位置都起始于0
#define OFFSET_OF(type, member)  ((size_t)(&((type *)0)->member))

#define OO1(t, m1)                 #m1,OFFSET_OF(t, m1)
#define OO2(t, m1, m2)             OO1(t, m1),             OO1(t, m2)
#define OO3(t, m1, m2, m3)         OO2(t, m1, m2),         OO1(t, m3)
#define OO4(t, m1, m2, m3, m4)     OO3(t, m1, m2, m3),     OO1(t, m4)
#define OO5(t, m1, m2, m3, m4, m5) OO4(t, m1, m2, m3, m4), OO1(t, m5)

#define SHOW_OO1(t, m1)                 \
    printf("\n%s offset: %s:%ld\n",                     #t,OO1(t,m1))
#define SHOW_OO2(t, m1, m2)             \
    printf("\n%s offset: %s:%ld, %s:%ld\n",                #t,OO2(t,m1,m2))
#define SHOW_OO3(t, m1, m2, m3)         \
    printf("\n%s offset: %s:%ld, %s:%ld, %s:%ld\n",           #t,OO3(t,m1,m2,m3))
#define SHOW_OO4(t, m1, m2, m3, m4)     \
    printf("\n%s offset: %s:%ld, %s:%ld, %s:%ld, %s:%ld\n",      #t,OO4(t,m1,m2,m3,m4))
#define SHOW_OO5(t, m1, m2, m3, m4, m5) \
    printf("\n%s offset: %s:%ld, %s:%ld, %s:%ld, %s:%ld, %s:%ld\n", #t,OO5(t,m1,m2,m3,m4,m5))

int main() {
    // x x x
    typedef struct {
        char c1; //s=1, a=1, @+0
        char c2; //s=1, a=1, @+1
        char c3; //s=1, a=1, @+2
    } STC; //3 = n*1, c=max(a)==1
    SHOW_OO3(STC, c1, c2, c3); //0,1,2
    printf("STC size: %ld\n", (long)sizeof(STC)); //3

    // x *
    // y y
    // y y
    typedef struct {
        char c;  //s=1, a=1, @+0
        short s1;//s=2, a=2, @+2
        short s2;//s=2, a=2, @+4
    } STS; //6 = n*2, c=max(a)==2
    SHOW_OO3(STS, c, s1, s2); //0,2,4
    printf("STS size: %ld\n", (long)sizeof(STS)); //6

    // x x * *
    // y y y y
    typedef struct {
        char str[2]; //s=2, a=min(4,2), @+0;
        int i;       //s=4, a=min(4,4), @+4;
    } ST0; //8 = n*4, c=max(a)==4
    SHOW_OO2(ST0, str, i); //0,4
    printf("ST0 size: %ld\n", (long)sizeof(ST0)); //8

    // x *
    // y y
    // z *
    typedef struct {
        char c1;  //s=1, a=min(4,1), @+0;
        short s;  //s=2, a=min(4,2), @+2;
        char c2;  //s=1, a=min(4,1), @+4;
    } ST1; //6 = n*2, c=max(a)==2
    SHOW_OO3(ST1, c1, s, c2);//0,2,4
    printf("ST1 size: %ld\n", (long)sizeof(ST1)); //6

    // x * y y
    // z * * *
    // m m m m
    typedef struct {
        char c1;  //s=1, a=min(4,1), @+0;
        short s;  //s=2, a=min(4,2), @+2;
        char c2;  //s=1, a=min(4,1), @+4;
        int i;    //s=4, a=min(4,4), @+8;
    } ST2; //12 = n*4, c=max(a)==4
    SHOW_OO4(ST2, c1, s, c2, i); //0,2,4,8
    printf("ST2 size: %ld\n", (long)sizeof(ST2)); //16

    // x * y y
    // z * * *
    // m m m m
    // m m m m
    typedef struct {
        char c1;  //s=1, a=min(4,1), @+0;
        short s;  //s=2, a=min(4,2), @+2;
        char c2;  //s=1, a=min(4,1), @+4;
        int *pi;  //s=8, a=min(4,8), @+8;
    } ST3; //16 = n*8, c=max(a)==8
    SHOW_OO4(ST3, c1, s, c2, pi); //0,2,4,8
    printf("ST3 size: %ld\n", (long)sizeof(ST3)); //12

    // x x * *
    // y y y y
    // z m * *
    typedef struct {
        short s;  //s=2, a=min(4,2), @+0;
        int i;    //s=4, a=min(4,4), @+4;
        char c1;  //s=1, a=min(4,1), @+8;
        char c2;  //s=1, a=min(4,1), @+9;
    } ST4; //12 = n*4, c=max(a)==4
    SHOW_OO4(ST4, s, i, c1, c2); //0,4,8,9
    printf("ST4 size: %ld\n", (long)sizeof(ST4)); //12

    // x x x x
    // x x * *
    // y y y y
    // z z m *
    typedef struct {
        ST1 st1;  //s=6, a=min(4,max(1,2,1)), @+0;
        int i;    //s=4, a=min(4,4), @+8;
        short s;  //s=2, a=min(4,2), @+12;
        char c;   //s=1, a=min(4,1), @+14;
    } ST5; //16 = n*4, c=max(a)==4
    SHOW_OO4(ST5, st1, i, s, c); //0,8,12,14
    printf("ST5 size: %ld\n", (long)sizeof(ST5)); //16

    // x x x x
    // x x y y
    // z z z z
    // m * * *
    typedef struct {
        ST1 st1;  //s=6, a=min(4,max(1,2,1)), @+0;
        short s;  //s=2, a=min(4,2), @+6;
        int i;    //s=4, a=min(4,4), @+8;
        char c;   //s=1, a=min(4,1), @+12;
    } ST6; //16 = n*4, c=max(a)==4
    SHOW_OO4(ST6, st1, s, i, c); //0,6,8,12
    printf("ST6 size: %ld\n", (long)sizeof(ST6)); //16

    // x x y y
    // y y y y
    // z z z z
    // m * * *
    typedef struct {
        short s;  //s=2, a=min(4,2), @+0;
        ST1 st1;  //s=6, a=min(4,max(1,2,1)), @+2;
        int i;    //s=4, a=min(4,4), @+8;
        char c;   //s=1, a=min(4,1), @+12;
    } ST7; //16 = n*4, c=max(a)==4
    SHOW_OO4(ST7, s, st1, i, c); //0,2,8,12
    printf("ST7 size: %ld\n", (long)sizeof(ST7)); //16

    return 0;
}

系统默认对齐参数

#pragma pack能够改变编译器的默认对齐方式

#pragma pack(n)         //设置编译器按照n个字节对齐,n可以取1,2,4,8,16
#pragma pack()          //默认4字节对齐

#pragma pack(push)      //将当前的对齐字节数压入栈顶,不改变对齐字节数
#pragma pack(push,n)    //将当前的对齐字节数压入栈顶,并按照n字节对齐
#pragma pack(pop)       //弹出栈顶对齐字节数,不改变对齐字节数
#pragma pack(pop,n)     //弹出栈顶并直接丢弃,按照n字节对齐
#pragma pack(push,1)    //可以指定结构的对齐和补齐的字节数
#pragma pack(pop)       //恢复push前的值
#pragma pack()应用示例
#define ASSERT_CONCAT_(a, b) a##b
#define STATIC_ASSERT(e, msg) ;enum { ASSERT_CONCAT_(assert_line_, __LINE__) = 1/(!!(e)) }
#pragma pack(push,1)

#if 0
RTP Header
 0               1               2               3
 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P|X|   CC  |M|     PT      |       sequence number         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                           timestamp                           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|            synchronization source (SSRC) identifier           |
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
|              contributing source (CSRC) identifier            |
|                         ...defined by CC                      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+---------------------------------------------------------------+
|       payload                                                 |
|         ...                                                   |
+---------------------------------------------------------------+
#endif
// rfc3550 rtp header should encode to network sequence
// The struct must be 12 bytes long, so force the compiler to pack it
// into a 12bytes long struct and not padding it.

typedef struct _RTPHeader_T {
# if LOCAL_LITTLE_ENDIAN /* little endian */
    uint32_t CsrcCnt:4;   /* 4bit */
    uint32_t Extension:1; /* 1bit */
    uint32_t Padding:1;   /* 1bit */
    uint32_t Version:2;   /* 2bit */

    uint32_t PayloadType:7; /* 7bit */
    uint32_t Marker:1;    /* 1bit */
# else /* big endian */
    uint32_t Version:2;   /* 2bit */
    uint32_t Padding:1;   /* 1bit */
    uint32_t Extension:1; /* 1bit */
    uint32_t CsrcCnt:4;   /* 4bit */

    uint32_t Marker:1;    /* 1bit */
    uint32_t PayloadType:7; /* 7bit */
# endif
    uint32_t SeqNo:16;    /* 16bit */

    uint32_t TimeStamp;   /* 32bit */
    uint32_t Ssrc;        /* 32bit */
} RTPHeader; //12bytes

STATIC_ASSERT(sizeof(RTPHeader)==12, "RTPHeader size doesn't seem to be cool.");
#pragma pack(pop)
// decode from network sequence
RTPHeader rtpHeader;
# if _RTP_CODEC_MANUAL
    rtpHeader.Version = (((buff[0] & 0xFF) >> 6) & 0x03); /* 2bit */
    rtpHeader.Padding = (((buff[0] & 0xFF) >> 5) & 0x01); /* 1bit */
    rtpHeader.Extension = (((buff[0] & 0xFF) >> 4) & 0x01); /* 1bit */
    rtpHeader.CsrcCnt = (buff[0] & 0x0F); /* 4bit */
    rtpHeader.Marker = (((buff[1] & 0xFF) >> 7) & 0x01); /* 1bit */
    rtpHeader.PayloadType = (buff[1] & 0x7F); /* 7bit */
    rtpHeader.SeqNo = (buff[2] << 8) + buff[3]; /* 16bit */
    rtpHeader.TimeStamp = (buff[4] << 24) + (buff[5] << 16) + (buff[6] << 8) + buff[7]; /* 32bit */
    rtpHeader.Ssrc = (buff[8] << 24) + (buff[9] << 16) + (buff[10] << 8) + buff[11]; /* 32bit */
#else
    RTPHeader *pHeader = (RTPHeader *)buff;
    std::memcpy(&rtpHeader, buff, 12);
    rtpHeader.SeqNo = ntohs(pHeader->SeqNo);
    rtpHeader.TimeStamp = ntohl(pHeader->TimeStamp);
    rtpHeader.Ssrc = ntohl(pHeader->Ssrc);
# endif

结构体大小分析

#include <stdio.h>
//假设存储位置都起始于0
#define OFFSET_OF(type, member)  ((size_t)(&((type *)0)->member))

#define OO1(t, m1)                 #m1,OFFSET_OF(t, m1)
#define OO2(t, m1, m2)             OO1(t, m1),             OO1(t, m2)
#define OO3(t, m1, m2, m3)         OO2(t, m1, m2),         OO1(t, m3)
#define OO4(t, m1, m2, m3, m4)     OO3(t, m1, m2, m3),     OO1(t, m4)
#define OO5(t, m1, m2, m3, m4, m5) OO4(t, m1, m2, m3, m4), OO1(t, m5)

#define SHOW_OO1(t, m1)                 \
    printf("\n%s offset: %s:%ld\n",                     #t,OO1(t,m1))
#define SHOW_OO2(t, m1, m2)             \
    printf("\n%s offset: %s:%ld, %s:%ld\n",                #t,OO2(t,m1,m2))
#define SHOW_OO3(t, m1, m2, m3)         \
    printf("\n%s offset: %s:%ld, %s:%ld, %s:%ld\n",           #t,OO3(t,m1,m2,m3))
#define SHOW_OO4(t, m1, m2, m3, m4)     \
    printf("\n%s offset: %s:%ld, %s:%ld, %s:%ld, %s:%ld\n",      #t,OO4(t,m1,m2,m3,m4))
#define SHOW_OO5(t, m1, m2, m3, m4, m5) \
    printf("\n%s offset: %s:%ld, %s:%ld, %s:%ld, %s:%ld, %s:%ld\n", #t,OO5(t,m1,m2,m3,m4,m5))

#pragma pack(8)
//#pragma pack(4)
//#pragma pack(2)

typedef struct S1 {
    short a; // 2, a=min(pack, 2)
    long b;  // 8, a=min(pack, 8)
} S1;
typedef struct S2 {
    char c;    // 1,  a=min(pack, 1)
    S1 d;      // 2+8,a=min(pack, max(2,8))
    double e;  // 8,  a=min(pack,8)
} S2;

#pragma pack()

int main() {
    printf("%ld, %ld\n", sizeof(S1),  sizeof(S2));
    SHOW_OO2(S1, a, b);
    SHOW_OO3(S2, c, d, e);
    return 0;
}

运行结果

  • #pragma pack(8) 16,32; 0,8; 0,8,24;
  • #pragma pack(4) 12,24; 0,4; 0,4,16;
  • #pragma pack(2) 10,20; 0,2; 0,2,12;
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值