C-一文搞懂c语言字节对齐

一文搞懂c语言字节对齐

如果你对c语言的字节对齐总感觉模糊,不能从理论上推导出一个复杂结构体实际占用的内存大小,那么你必须要看看本博文,硬干货!!!

测试环境是ubuntu 64位

基本概念

要透彻理解本文,需要先知道三个概念:自身对齐,默认对齐,有效对齐

自身对齐

结构体的成员的自身大小对齐

char a;         //1字节
short b;        //2字节
int c;          //4字节
float d;        //4字节
double e;       //8字节

默认对齐

32位系统一般为4字节对齐,64位系统一般为8字节对齐

我的服务器内存的数据位宽位64bits,也就是8字节。
在这里插入图片描述

有效对齐

min{自身对齐,默认对齐}.即自身对齐和默认对齐的较小值.

对齐规则

网上有很多文章,给出了很多对齐规则,我觉得太繁杂,我这里就一条规则

按有效对齐分配内存

接下来,通过一个详细的例子来分析 复杂结构体的大小分配以及每个成员的对齐分布

typedef struct _d_s
{
    char a;         //1字节
    short b;        //2字节
    int c;          //4字节
    float d;        //4字节
}d_t; //12字节, 有效对齐4字节
// 如果不考虑对齐因素, d_t的原始大小为11字节;但是该结构体中,有效对齐为4字节,那么d_t的实际大小为12字节

typedef char string[31];
typedef struct _g_s
{
    char a;         //1字节
    short b;        //2字节
    int c;          //4字节
    float d;        //4字节
    double e;       //8字节
    d_t f;          //12字节
    char x;         //1字节
    string g;       //31字节
    short h;        //2字节
    int i;          //4字节
}g_t; //默认8字节对齐;1+2+4+4+8+12+1+31+2+4=69


//下面为g_t变量的实际大小及内存地址分布
printf("size of g_t:%ld\n", sizeof(g_t));
g_t g;
printf("address of g.a:%p\n", &g.a);
printf("address of g.b:%p\n", &g.b);
printf("address of g.c:%p\n", &g.c);
printf("address of g.d:%p\n", &g.d);
printf("address of g.e:%p\n", &g.e);
printf("address of g.f:%p\n", &g.f);
printf("address of g.x:%p\n", &g.x);
printf("address of g.g:%p\n", &g.g);
printf("address of g.h:%p\n", &g.h);
printf("address of g.i:%p\n", &g.i);

我们先看看实际运行时,g_t变量所占内存大小及地址分布情况:
在这里插入图片描述

这里有个疑问,有效对齐为8字节,而g_t的原始大小为69字节,理论上g_t的实际大小为72字节应该是足够存储,但为什么实际大小是80字节呢?
在这里插入图片描述

上图中,每一个小格代表一个字节。每8个字节看作一个默认对齐单元

已知g.a的起始地址为0x7ffc425478e0(每次运行地址都会变,以自己实际运行地址为准,但地址一定是8字节对齐),g.a占一个字节;

由于g.b占2字节,g.b的有效对齐为2字节,所以g.b的起始地址为0x7ffc425478e2,那么,0x7ffc425478e1为填充字节;

由于g.c占4字节,g.c的有效对齐为4字节,刚好0x7ffc425478e0 代表的默认对齐单元能够存放下g.c,所以g.c的起始地址为0x7ffc425478e4

由于g.d占4字节,g.d的有效对齐为4字节,所以g.d的起始地址为0x7ffc425478e8

由于g.e占8字节,g.e的有效对齐为8字节,!!!重点部分,注意理解!!! 但是0x7ffc425478e8所在的默认对齐单元只剩下4个字节,起始地址为0x7ffc425478ec,显然该地址0x7ffc425478ec不是8字节对齐,故g.e不能存放在0x7ffc425478e8所在的默认对齐单元了;既然不能放,那就只能往后再开辟一个新的默认对齐单元,即0x7ffc425478f0,且刚好放满。
在这里插入图片描述

由于g.f占12字节,因为f的数据类型是一个自定义类型d_t,且d_t的有效对齐为4字节;那么,g.f会占满0x7ffc425478f8单元,也会占0x7ffc42547900的4个字节;

由于g.x占1个字节,显然g.x的起始地址为0x7ffc42547904

由于g.g占31字节,因为成员g的数据类型也是一个自定义类型string,且string的有效对齐为1字节,那么g.g的起始地址为0x7ffc42547905,直至占满后面的31个字节;(分布情况查看图例)
在这里插入图片描述

由于g.h占2字节,g.h的有效对齐为2字节,g.h的起始地址可以是0x7ffc42547924;

由于g.i占4字节,g.i的有效对齐为4字节,显然地址0x7ffc42547926不是4字节对齐,故g.i的起始地址不能是0x7ffc42547926;必须重新开辟一个新的默认对齐单元,所以g.i的起始地址为0x7ffc42547928;那么0x7ffc42547920默认对齐单元的最后两个字节为填充字节。

最后,对于结构体g_t整体,它的有效对齐为8字节,所以0x7ffc42547928起始的8个字节都要算作是g_t所占用的内存,而不是0x7ffc42547928起始的4个字节(虽然实际只使用了前面4字节)。

好了,前面提出的疑问"有效对齐为8字节,而g_t的原始大小为69字节,理论上g_t的实际大小为72字节应该是足够存储,但为什么实际大小是80字节呢?",你搞清楚了吗!

为什么对齐

到了这个阶段,可能有人会问,为什么要有对齐呢?

对齐的设计是为了提高代码的可移植性。因为不同架构平台(X86,ARM等),memory bus,cache,memory page都是8字节或8字节整数倍。有些平台仅支持对齐的数据访问,那么在这种平台上进行非对齐的数据访问操作,就会导致程序异常。当前,目前大多数平台都支持非对齐,那么代价就是 做同样一件事,非对齐比对齐消耗的资源更多,处理速度相对较慢。例如,想要将某个非对齐的数据写入当前地址,首先访问当前地址,发现不对齐,那么需要继续"找到"一个对齐的地址,然后再次访问这个对齐的新地址,然后写入数据。在这个过程中,显然发生了两次访问地址的动作。如果数据是对齐的,那么一次访问就可以搞定。其实,对齐的设计体现了以空间换时间的思想。

我们知道,默认对齐是8字节(64位系统),有些应用场景下,结构体中不能有填充字节(1字节对齐),否则数据的解析会发生未知的异常,这时就需要我们修改默认对齐字节数,那么,要怎么做呢?

对齐设置

将结构体定义为1字节对齐,有两种方法,都是将结构体成员的内存紧凑分配

方法一

#pragma pack(1)
typedef struct _apple_s
{
    struct _fruit_s* p_fruit;
    char* name;
}apple_t;

typedef struct _fruit_s
{
    int type;
    int weight;
    float price;
    char a;
}fruit_t;
#pragma pack(0)

方法二

typedef struct _apple_s
{
    struct _fruit_s* p_fruit;
    char* name;
}__attribute__((packed)) apple_t;

typedef struct _fruit_s
{
    int type;
    int weight;
    float price;
    char a;
}__attribute__((packed)) fruit_t;

QQ讨论群:679603305
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sif_666

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值