8-2 结构体的内存对齐

1. 看一段代码,回答一个问题:

#include <stdio.h>


int main() {

  typedef struct Person{
    char *name;
    int age;
    char *id;
  }Person;

  printf("sizeof(Person): %d\n", sizeof(Person));   // sizeof(Person): 24
  printf("sizeof(char *): %d\n", sizeof(char *));   // sizeof(char *): 8
  printf("sizeof(int): %d\n", sizeof(int));         // sizeof(int): 4

  return 0;
}

结构体内部三个类型变量,每个类型变量对应的字节数是 8,4,8,共占据 20 个字节,那为什么该结构体占据的字节数是 24?这便是结构体对齐的由来

2. 结构体对齐,结构体内部的每个类型对应的地址是其类型大小的倍数,比如 char * 对应的地址一定是 sizeof(char *) 的倍数,举一个 demo

typedef struct {
    char a;   // 1
    char b;   // 1
    int c;    // 4
    short d;  // 2
    double e; // 8
}Align;

Align align = {.a='a', .b='b', .c=3, .d=4, .e=5};
printf("sizeof(align): %d\n", sizeof(align));    // sizeof(align): 24

可以看出,a b 存放在一起,之后空两个字节,在 12 的位置上存储了 c,在 0 的位置上存储了 d,然后空了 6 个字节,将 e 存储在地址倍数为 8 的地方(结构体内部的每个类型对应的地址是其类型大小的倍数),所以不合理的结构体会造成空间的浪费,24 个字节中 只有 16 个字节是真正存储东西的,浪费了 8个字节,就是阴影部分的 cc

将结构体内部的属性稍微改变顺序:

typedef struct {
    char a;   // 1
    char b;   // 1
    short d;  // 2
    int c;    // 4
    double e; // 8

}Align;

Align align = {.a='a', .b='b', .c=3, .d=4, .e=5};
printf("sizeof(align): %d\n", sizeof(align));  // 16

3. 除了改变结构体内部的属性顺序,编译器也允许改变结构体储存方式,不会让结构体按照上面的方式进行对齐

#pragma pack(2)  // 编译器默认以 2 的倍数进行对齐,不再按照类型的大小

typedef struct {
    char a;   // 1
    char b;   // 1
    int c;    // 4
    short d;  // 2
    double e; // 8

}Align;
Align align = {.a='a', .b='b', .c=3, .d=4, .e=5};

本来是占据 24 字节,采用对齐方式后,仅占据 16个字节,int c 的地址是从 11 开始的,11-14

4. 如果是 gcc 编译器,有一个内置的函数,可以改变结构体内部单个属性的对齐方式(暂且试验失败),试验了其他博客,也失败,推测是 gcc 编译器版本问题。https://blog.csdn.net/21aspnet/article/details/6729724 这篇博客讲述了为什么要字节对齐,可以看看内部的原理。(字节对齐是为了 CPU 访问效率更高

5. C11 提供了一个新的特性,也是改变对齐方式的:_Alignas(8) int c; 只不过还是仅 gcc 编译器可用,msvc 编译器不通过;此外,该特性对齐方式的大小有要求,最小是对应数据类型的大小,比如  _Alignas(n) int c,n 至少为 4,不能取 4 以下的数,换成 double 则 n 至少为 8

typedef struct {
    char a;   // 1
    char b;   // 1
    _Alignas(8) int c;    // 4
    short d;  // 2
    double e; // 8
}Align;

Align align = {.a='a', .b='b', .c=3, .d=4, .e=5};
printf("sizeof(align): %d\n", sizeof(align));  // sizeof(align): 24

6. 介绍一个 C11 的新东西,_Alignof() 查询一个字段对齐的倍数,比如刚才的 int c,由于前面添加了 _Alignas(8),所以 _Alignof(align.c) 的结果就是 8,这个东西也是仅 gcc 可用,msvc 不可用

7. 介绍一个 msvc 和 gcc 都可用的 offsetof(Align, e),第一个参数是结构体类型,第二个参数是结构体内部的属性,整体的意思是该属性偏移结构体初始位置多少个字节

#include <stdio.h>
#include <stddef.h>

int main() {

  typedef struct {
    char a;   // 1
    char b;   // 1
    int c;    // 4
    short d;  // 2
    double e; // 8
  }Align;

  Align align = {.a='a', .b='b', .c=3, .d=4, .e=5};

  printf("%d", offsetof(Align, e));  // 16
  return 0;
}

深究一下 offsetof,可以看到(gcc下面看不到,msvc可以看到)

首先注意,s 是传进来的结构体类型,m 是传进来的结构体成员,(s *)0 是骗编译器说有一个指向类(或结构)s 的指针,该指针的值为 0,((s *)0)->m 是利用指针指向结构体的成员 m,&((s *)0)->m 是要取得类 s 中成员变量 m 的地址。由于这个类(或结构)的基址为 0(因为指针的值为 0),这时 m 的地址当然就是 m 在 s 中的偏移了。

(s *)0 是把 0 地址转换为 s 指针类型,类比于 (char*) a 这种感觉。恰好这时的 0 是 s 类型的,因此可以取成员变量。从这个指针上“取” m 成员再取址,而 m 成员的地址转换后结果就是 m 成员相对于整个对象的偏移量(既然是从 0 地址开始算的,就不用再减去起始地址0)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值