C语言结构体字节对齐

  默认字节对齐

C语言结构体字节对齐是老生常谈的问题了,也是高频面试题,现在我们来深入研究这个问题,彻底弄懂到底是怎么回事,给你一个结构体定义和平台机器位数就能手动计算出结构体占用字节数,现在我们不使用宏#pragma pack,采用默认字节对齐方式。

先抛出结论:

  • 在一个结构体中第一个成员变量放在偏移为0的位置,以后的变量都存储在该变量占用字节数整数倍的地址上。

  • 结构体总大小,必须是内部最大成员变量的整数倍,不足的补齐。

好了,现在我们直接写个小程序验证并分析是否真是这样一回事。

struct st{
    short a1;
    short a2;
    short a3;
};

struct st2{
    long a1;
    short a2;
};

这里我们定义了两个很简单的结构体,short占用2个字节,struct st我们一眼就知道大小了6个字节,但是struct st2呢?笔者电脑是64位,那么long占用8个字节,short占用2个字节。我们先来按照结论进行分析,在struct st2中成员变量a1在偏移0处存储且占用8个字节,成员变量a2占用2个字节,由于8是2的倍数,所以a2在偏移8的位置存储,又因为有结论2,我们根据结论2可以得出,struct st2必须占用8的倍数大小,所以struct st2总大小是16个字节,不足的后面补齐。现在我分别打印出struct st1和struct st2占用字节数大小和struct st2各个成员变量地址,观察是否和分析的一样。

int main() {
    struct st2 st_val2;

    printf("sizeof(long) = %d\n", sizeof(long));
    printf("sizeof(struct st) = %d\n", sizeof(struct st));
    printf("sizeof(struct st2) = %d\n", sizeof(struct st2));
    printf("st_val2 addr = %p\n", &st_val2);
    printf("st_val2 a1 addr = %p\n", &st_val2.a1);
    printf("st_val2 a2 addr = %p\n", &st_val2.a2);

    return 0;
}

编译运行输出:

sizeof(long) = 8
sizeof(struct st) = 6
sizeof(struct st2) = 16
st_val2 addr = 0x7ffee107f3b8
st_val2 a1 addr = 0x7ffee107f3b8
st_val2 a2 addr = 0x7ffee107f3c0

现在我们看一下输出结果,struct st如我们所愿占用6个字节大小,struct st2也按照我们分析的一样占用16个字节。我们在程序中定义了一个struct st2类型变量st_val2,从输出中可以看出变量st_val2的a1成员变量和st_val2变量地址一样,成员变量a2在偏移8处存储(0x c0 = 0xb8 8)。一切如我们所愿,看起来好像挺简单的,我们知道C语言有丰富的数据类型,下面我们再定义一个更复杂的结构体。

struct st3{
    int a1;
    char a2;
    short a3;
    long a4;
    char a5;
};

这个结构体包含了大量数据类型成员变量,再复杂的结构体也能按照我们的结论分析到底占用了几个字节。

在struct st3中int型成员变量a1占用4个字节,在偏移0处存储,char型成员变量a2占用2个字节那么应该放在2的倍数地址处存储,a1已经占用了4个字节,所以a2应该在偏移4的地址存储。
short型成员变量a3占用2个字节,也应该放在2的倍数地址处存储,所以a3在偏移6的地址处存储,a2后面填充1个字节。

long型成员变量a4占用8个字节,应该放在8的倍数地址上存储,前面我们已经知道a3在偏移6的地址处存储,且占用2个字节8 = 6 2,所以a4应该在偏移8的地址处存储。

最后一个char型成员变量a5占用一个字节,那么a5在偏移16地址处存储。

现在我们计算一下struct st3结构体占用空间大小,从a5偏移出计算16 1 = 17。在struct st3中最大成员变量占用8个字节,所以结构体总大小应该是8的倍数,最后结构体总大小是17 7 = 24,这里的7个字节在最后补齐。

我们依旧写一个小程序输出struct st3类型变量各个成员变量地址和结构体总大小。

int main() {
    struct st3 st_val3;
    printf("sizeof(struct st3) = %d\n", sizeof(struct st3));
    printf("st_val3 addr = %p\n", &st_val3);
    printf("st_val3.a1 addr = %p\n", &st_val3.a1);
    printf("st_val3.a2 addr = %p\n", &st_val3.a2);
    printf("st_val3.a3 addr = %p\n", &st_val3.a3);
    printf("st_val3.a4 addr = %p\n", &st_val3.a4);
    printf("st_val3.a5 addr = %p\n", &st_val3.a5);

    return 0;
}

编译运行输出:

sizeof(struct st3) = 24
st_val3 addr = 0x7ffeed0c33b0
st_val3.a1 addr = 0x7ffeed0c33b0
st_val3.a2 addr = 0x7ffeed0c33b4
st_val3.a3 addr = 0x7ffeed0c33b6
st_val3.a4 addr = 0x7ffeed0c33b8
st_val3.a5 addr = 0x7ffeed0c33c0

从输出我们可以看出,和我们分析的完全一样。

枚举类型变量和联合体类型变量都可以作为结构体的成员变量,在分析这些结构体占用大小时,分析方法和我们上面的一模一样,只需要把内部任何一种数据类型变量当做一个普通变量看待即可,但是结构体类型成员变量有点不一样,它不适用于结论2,我们举个例子。

struct st4{
    char a1[3];
    int a2;
    long a3;
    struct st3 a4;
};

在struct st4中我们定义了一个struct st3类型成员变量,前面我们已经分析过了struct st3占用24个字节。成员变量a1占用3个字节,成员变量a2占用4个字节,所以a2存储在偏移4的地址上,在a1后面填充一个字节。成员变量a3占用8个字节,则a3存储在偏移8的地址上。那么结构体总共占用字节数大小是:8   8 24 = 40。

最后我们写一个程序验证一下是否如此。

int main() {
    struct st4 st_val4;
    printf("sizeof(struct st4) = %d\n", sizeof(struct st4));
    printf("st4 addr = %p\n", &st_val4);
    printf("st_val4.a1 addr = %p\n", &st_val4.a1);
    printf("st_val4.a2 addr = %p\n", &st_val4.a2);
    printf("st_val4.a3 addr = %p\n", &st_val4.a3);
    printf("st_val4.a4 addr = %p\n", &st_val4.a4);

    return 0;
}

编译运行输出:

sizeof(struct st4) = 40
st4 addr = 0x7ffeec1263a0
st_val4.a1 addr = 0x7ffeec1263a0
st_val4.a2 addr = 0x7ffeec1263a4
st_val4.a3 addr = 0x7ffeec1263a8
st_val4.a4 addr = 0x7ffeec1263b0

和我们分析的一模一样。

声明:

本文于网络整理,版权归原作者所有,如来源信息有误或侵犯权益,请联系我们删除或授权事宜。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值