结构体字节对齐

结构体偏移量

在定义结构体类型后,则结构体中的成员内存布局就定下了。

#include <stdio.h>

typedef struct Test
{
    int a;
    int b;
    int c;
}Test;

int main()
{
    Test  t;
    Test*p = NULL;
    p = &t;

    //b相对于结构体Test的偏移量,n1=4
    int n1 = (int)&(p->b) - (int)p; 
    //绝对0地址b的偏移量, n2=4
    int n2 = (int)&( ( (Test *)0 )->b);

    printf("n1:%d \n", n1);
    printf("n2:%d \n", n2);

    return 0;
}

结构体成员偏移量

内存字节对齐

在用sizeof运算符求算某结构体所占空间时,并不是简单地将结构体中所有元素各自占的空间相加,这里涉及到内存字节对齐的问题。从理论上讲,对于任何变量的访问都可以从任何地址开始访问,但是事实上不是如此,实际上访问特定类型的变量只能在特定的地址访问,这就需要各个变量在空间上按一定的规则排列, 而不是简单地顺序排列,这就是内存对齐。

测试代码:

#include <stdio.h>

typedef struct Test
{
    char a[5]; //5字节
    int b;  //4字节
    int c;  //4字节

}Test;

int main()
{
    Test  t;
    Test*p = NULL;
    p = &t;

    //b相对于结构体Test的偏移量, n1的值为8,不是5
    int n1 = (int)&(p->b) - (int)p;

    //绝对0地址b的偏移量
    int n2 = (int)&( ( (Test *)0 )->b);

    printf("n1:%d \n", n1);
    printf("n2:%d \n", n2);

    //sizeof(Test) = 8+4+4 = 16, 不是5+4+4
    printf("sizeof(Test) = %d\n", sizeof(Test));

    return 0;
}

内存字节对齐原因

  • 某些平台只能在特定的地址处访问特定类型的数据;
  • 提高存取数据的速度。比如有的平台每次都是从偶地址处读取数据,对于一个int型的变量,若从偶地址单元处存放,则只需一个读取周期即可读取该变量;但是若从奇地址单元处存放,则需要2个读取周期读取该变量。

内存字节对齐原则

  • 原则1:数据成员的对齐规则(默认以最大的类型字节为单位)
    结构体(struct)的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存放在offset为该数据成员类型大小的整数倍的地方(比如int在32位机器为4字节,则要从4的整数倍地址开始存储)

  • 原则2:结构体作为成员的对齐规则
    如果一个结构体B里嵌套另一个结构体A,还是以最大成员类型的字节对齐,但是结构体A存储起点为A内部最大成员整数倍的地方。(struct B里存有struct A,A里有char,int,double等成员,那A应该从8的整数倍开始存储。),结构体A中的成员的对齐规则仍满足原则1、原则2。
    注意:
    1)结构体A所占的大小为该结构体成员内部最大元素的整数倍,不足补齐。
    2)不是直接将结构体A的成员直接移动到结构体B中。

  • 原则3:收尾工作
    结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐。

默认情况下,以结构体最大成员的类型字节大小对齐,当然也可以通过程序控制,指定字节对齐的单位:

#pragma pack(xx)   //xx必须是2的n次方

#pragma pack(1)     //1字节对齐
#pragma pack(2)     //2字节对齐
#pragma pack(4)     //4字节对齐
#pragma pack(8)     //8字节对齐
#pragma pack(16)    //16字节对齐

注意:尽管可以通过pragma pack()指定字节对齐的单位,但是,只有当结构体成员大于或等于pragma pack()指定字节对齐的单位,指定字节对齐的单位才有效,否则,还是以结构体最大成员的类型字节大小对齐

案例分析(64位架构平台测试)

结构体(struct)的数据成员,第一个数据成员放在偏移量为0的地方,以后每个数据成员存放在偏移量为该数据成员类型大小的整数倍的地方,但是,如果该数据成员类型大小大于字节对齐大小,那么偏移量为字节对齐大小的整数倍

普通结构体

案例1
struct
{
    char a;
    int b;
    short c;
}A;
  1. 以1字节对齐
    当以1字节对齐时,结构体的大小则为各成员空间大小相加:
    sizeof(A) = 1 + 4 + 2 = 7

  2. 以2字节对齐
    a: 1 * 0 = 0
    b: 2 * 1 = 2 (int为4字节, 大于对齐字节(2字节),以2字节计算)
    c: 2 * 3 = 6
    以2字节对齐
    综上:
    a放在偏移量为0的地方,由于a为char类型,大小为1字节,所以,占1个格子。
    b放在偏移量为2的地方,由于b为int类型,大小为4字节,所以,占4个格子,放2行,而上面的a,只占一个字节,不足对齐字节(2字节),故不足不齐(以”*”描述)。
    c放在偏移量为6的地方,由于c为short类型,大小为2字节,所以,占2个格子。
    所以,sizeof(A) = 8

  3. 以4字节对齐
    a: 1 * 0 = 0
    b: 4 * 1 = 4
    c: 2 * 4 = 8
    以4字节对齐
    sizeof(A) = 12

  4. 以8或16字节对齐
    由于结构体成员最大的类型字节为4,小于8或16,所以,尽管通过pragma pack(xx)指定对齐的字节,但,还是以结构体成员最大的类型字节对齐,还是以4字节对齐。

案例2
struct
{
    char a[5];
    double b;
    short c;
}A;
  1. 以2字节对齐
    a: 1 * 0 = 0
    b: 2 * 3 = 6 (double为8字节, 大于对齐字节(2字节),以2字节计算)
    c: 2 * 7 = 14
    以2字节对齐
    sizeof(A) = 16

  2. 以4字节对齐
    a: 1 * 0 = 0
    b: 4 * 2 = 8 (double为8字节, 大于对齐字节(4字节),以4字节计算)
    c: 2 * 8 = 16
    以4字节对齐
    sizeof(A) = 20

  3. 以8或16字节对齐
    a: 1 * 0 = 0
    b: 8 * 1 = 8
    c: 2 * 8 = 16
    以8或16字节对齐
    sizeof(A) = 24

结构体嵌套结构体

案例1
struct A
{
    char e;
    short f;
};

struct
{
    int a;
    char b;
    struct A c;
    char d;
}B;

结构体B里嵌套另一个结构体A,则以两个结构体中最大的成员对齐,B中最大成员为int,4字节,则以4字节对齐。但是,A中的最大成员为short,2个字节,A存储起点位置必须为2的整数倍

a: 4 * 0 = 0
b: 1 * 4 = 4
结构体起点位置:2*3=6, 为结构体A最大成员的整数倍
 e: 6 + 1*0 = 6
 f: 6 + 2*1 = 8
d: 1 * 10 = 10
这里写图片描述
sizeof(B) = 12

案例2
struct A
{
    char e;
    short f;
    int g;
};

typedef struct B
{
    int a;
    char b;
    struct A c;
    char d;
}B;

结构体B里嵌套另一个结构体A,则以两个结构体中最大的成员对齐,B中最大成员为int,4字节,则以4字节对齐。但是,A中的最大成员为int,4个字节,A存储起点位置必须为4的整数倍

a: 4 * 0 = 0
b: 1 * 4 = 4
结构体起点位置:2*4=8, 为结构体A最大成员的整数倍
 e: 8 + 1*0 = 8
 f: 8 + 2*1 = 10
 g: 8 + 4*1 = 12
d: 1 * 16 = 16
这里写图片描述
sizeof(B)=20

案例3
struct A
{
    char e;
    short f;
    double g;
};

typedef struct B
{
    int a;
    char b;
    struct A c;
    char d;
}B;

结构体B里嵌套另一个结构体A,则以两个结构体中最大的成员对齐,最大成员为double,8字节,则以8字节对齐。但是,A中的最大成员为double,8个字节,A存储起点位置必须为8的整数倍

a: 4 * 0 = 0
b: 1 * 4 = 4
结构体起点位置:8*1=8, 为结构体A最大成员的整数倍
 e: 8 + 1*0 = 8
 f: 8 + 2*1 = 10
 g: 8 + 8*1 = 16
d: 1 * 24 = 24
这里写图片描述
sizeof(B) = 32

位域结构体的字节对齐

使用位域的主要目的是压缩存储,其大致规则为:

  • 一个位域必须存储在同一个字节中,不能跨两个字节。如一个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域。
  • 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止。
  • 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍。
  • 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,vc6.0和vs采取不压缩方式,Dev-C++和gcc采取压缩方式。
  • 如果位域字段之间穿插着非位域字段,则不进行压缩。
struct A
{
    int     a1 : 5;
    int     a2 : 9;
    char    b;
    int     c : 4;
    short   d;
}B;

由于a1和a2是紧挨着,而且类型相同,故a1和a2压缩放在一起,a1+a2=14位 < 32位,小于int类型的大小,故a1和a2只需要1个int类型大小的空间,尽管没有只占14位,后面的不足补齐。

c尽管只占4位,但由于前后的成员(b, d)类型都不是int,故c需要4字节(32位)空间存放。

  1. 以1字节对齐
    a1+a2 = 14位,小于4字节(int类型为4字节),所以,a1和a2放在一起。
    sizeof(B) = 4 + 1 + 4 + 2 = 11

  2. 以2字节对齐
    a1+a2: 2 * 0 = 0 (int为4字节, 大于对齐字节(2字节),以2字节计算)
    b: 1 * 4 = 4
    c: 2 * 3 = 6 (int为4字节, 大于对齐字节(2字节),以2字节计算)
    d: 2 * 5 = 10
    这里写图片描述
    sizeof(B) = 12

  3. 以4、8或16字节对齐
    a1+a2: 4 * 0 = 0
    b: 4 * 1 = 4
    c: 4 * 2 = 8
    d: 2 * 6 = 12
    这里写图片描述
    sizeof(B) = 16

参考资料:结构体字节对齐

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值