C语言——结构体(struct)对齐

目录

前言

一、结构体对齐规则

1、结构体的总大小对齐规则

2、结构体成员的对齐规则

3、数组和结构体的对齐规则

二、改变编译器对齐数(#pragma pack)

三、如何减小结构体占用内存

1、 重新排列成员顺序

2、使用#pragma pack指令

3、使用位域

4、其他

总结



前言

        本文主要介绍C语言中,结构体的对齐规则、如何改变对齐数,对齐规则对内存的影响以及如何减小结构体占用的内存。


提示:小编系统为64位CentOS系统,默认编译器默认对齐数为8字节,如果结构体成员的大小大于编辑器的默认对齐数时,要以编辑器的对齐数为准,即二者取小。

一、结构体对齐规则

1、结构体的总大小对齐规则

        一个结构体的总共内存大小是其类型最大成员大小的整数倍如果结构体的最后一个成员之后没有足够的空间来满足对齐要求,编译器会在结构体末尾添加填充字节。

#include <stdio.h>

struct A{
    int i; //4字节
    char c;  //1字节
};

int main()
{
    printf("%d\n",sizeof(struct A));
    return 0;
}

 运行结果为:

8

        上面展示的例子中,结构体A中分别有两个成员,第一个成员为int型,占4个字节,第二成员为char型,占1个字节。根据结构体的总大小对齐规则,找出该结构体中类型最大的成员,即int型(4字节),所以结构体的总体大小必须为4字节的整数倍。将成员所占的字节数加起来为5个字节,但不是4的整数倍,所以要继续填充3个字节,一共8个字节,才能满足规则。

2、结构体成员的对齐规则

        结构体的每个成员相对于结构体开头的偏移量是该成员大小的整数倍。如果成员的大小小于对齐要求,编译器会在成员之间插入填充字节以满足对齐要求。

        偏移量:在计算机汇编语言中,偏移量是指存储单元的实际地址与其所在段的段地址之间的距离,也称为“段内偏移”或“有效地址”。它是程序的逻辑地址与段首地址的差值。

#include <stdio.h>  
  
struct B{  
    char a; // 1 byte 
    int b;  // 4 bytes  
    short c; // 2 bytes  
};  
  
int main() 
{  
    printf("%d\n", sizeof(struct B)); 
    return 0;  
}

 运行结果为:

12

        上面的例子中,结构体B一共有三个成员变量,其中int型(4字节)最大,根据规则一,最后结构体的总体大小应该为4的整数倍。其中第一变量a的偏移量为0,是其大小的整数倍;第二个变量b大小为4字节,即需要其偏移量为4的整数倍,所以b的偏移量为4,而a与b变量之间的空只能用字节填充,即填充三个字节。到此一共占了8个字节大小,第三个变量c大小为2个字节,同理,其偏移量需要为2的整数倍,由于前面刚好满足4字节对齐,所以偏移量为2的整数倍就自然满足了,即c的偏移量为8。到此一共占了10字节大小,但由于规则一,c变量后面还需要填充2个字节,一共12字节,满足4的整数倍。

3、数组和结构体的对齐规则

        数组中的每个元素都要满足上述对齐规则,即数组的每个元素都相对于数组开头的偏移量是其大小的整数倍。对于嵌套的结构体,嵌套的结构体本身也要满足其内部的对齐规则,并且嵌套结构体相对于外部结构体的偏移量也要满足对齐要求。

#include <stdio.h>  
struct C{  
    char a[3]; // 3 bytes
    int b;     // 4 bytes  
};  
struct D{  
    char i;        // 1 byte 
    struct C c_t;  // 8 bytes
    int j;         // 4 bytes  
};  
 
int main() 
{  
    printf("%d\n", sizeof(struct C));
    printf("%d\n", sizeof(struct D)); 
    return 0;  
}

 运行结果为:

8
16

        上面例子中,结构体C中,第一个变量为char型数组,根据数组的特点,其三个元素偏移量必然对齐,且占三个字节。第二个变量为int型,4字节,偏移量为4的整数倍,即偏移量为4。满足规则一,规则二,所以结构体C大小为:8字节。

        在结构体D中,存在嵌套的结构体C,根据上面的规则一二,你很有可能算出来结构体D的大小为24,但其实它为16。为什么呢?因为在找最大类型变量是不能将结构体c_t当作一个整体,应该将其拆分来看,即最大类型的变量为4字节,总体大小需是4的整数倍。然后再看成员对其,第一个变量和第二个变量之间填充7字节,最终一共16字节。

二、改变编译器对齐数(#pragma pack)

        #pragma pack 指令:是用来修改编译器对结构体成员的对齐方式的。具体来说,它可以用来修改结构体或联合体中成员的偏移量对齐数,以及整个结构体或联合体的总大小对齐数。使用 #pragma pack(n) 时,n 指定了新的对齐字节数。结构体的成员将按照 n 字节对齐,同时整个结构体的总大小也将按照 n 字节对齐。

#include <stdio.h>  
struct C{  
    char a[3]; // 3 bytes
    int b;     // 4 bytes  
};  

#pragma pack(1)
struct D{  
    char a[3]; // 3 bytes
    int b;     // 4 bytes  
};  

#pragma pack(2)
struct E{  
    char a[3]; // 3 bytes
    int b;     // 4 bytes  
};  
 
int main() 
{  
    printf("%d\n", sizeof(struct C));
    printf("%d\n", sizeof(struct D)); 
    printf("%d\n", sizeof(struct E));
    return 0;  
}

  运行结果为:

8
7
8

          在上面的示例中,三个结构体的成员一摸一样,在编译结构体C时,还是原来的编译器对齐数,但在编译结构体D时,编译器对齐数改为了1,根据开头提示,这时候规则一二均取对齐数1,所以结构体E的大小变成了7。在编译结构体E时,对于总体大小对齐而言,在4和2之间选择2;对于成员偏移量对齐而言,第一个变量在1和2之间选择1,对于第二个变量在2和4之间选择2。最终算出结构体E的大小仍为8。

三、如何减小结构体占用内存

1、 重新排列成员顺序

        将占用空间小的成员放在前面,大的成员放在后面,并且尽量让相邻的成员类型相同,这样可以减少由于对齐而产生的填充字节。

        如上述规则二中的例子,将其重新排列后为:

#include <stdio.h>  
  
struct B{  
    char a;  // 1 byte 
    short c; // 2 bytes  
    int b;   // 4 bytes  

};  
  
int main() 
{  
    printf("%d\n", sizeof(struct B)); 
    return 0;  
}

 运行结果为:

8
        相比于原来的12,现在结构体的大小变为了8,减小了4字节的空间。

2、使用#pragma pack指令

        通过#pragma pack(n)指令可以修改编译器默认的对齐字节数。将n设置为一个较小的值可以减少由于对齐而产生的填充字节,从而减小结构体的总体大小。但是,这可能会影响程序的性能,因为非对齐的内存访问通常比对齐的内存访问要慢。 

#include <stdio.h>  
struct C{  
    char a; 
    int b;   
    double c;  
};  

#pragma pack(1)
struct D{  
    char a; 
    int b;   
    double c;  
};  

int main() 
{  
    printf("%d\n", sizeof(struct C));
    printf("%d\n", sizeof(struct D)); 
    return 0;  
}

   运行结果为:

16
13
        相同成员的结构体,D比C小了3个字节。

3、使用位域

        如果结构体中的某些成员只需要占用几个位,那么可以使用位域来定义这些成员。位域允许你将多个小成员打包到一个字节或更大的整数类型中,从而减少浪费的空间。

4、其他

        检查结构体定义,去除那些实际上并不需要的成员,可以直接减小结构体的总体大小。如果可能的话,尽量使用占用空间更小的数据类型。例如,如果某个成员的值范围较小,可以考虑使用charshort代替int


总结

        了解结构体的对齐规则对于提高内存使用效率、保证访问速度、避免硬件异常以及提高代码的可移植性都具有重要意义。在编写涉及结构体操作的代码时,我们应该了解和考虑这些对齐规则。

相关内容可以查看:C语言——结构体(Struct)详解+运用举例_struct在c语言中的用法-CSDN博客

有误之处望指正!!

  • 12
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C语言中的结构体对齐是一个经常被问到的面试题。这里我来简单解释一下。 在C语言中,结构体是一种用户自定义的数据类型,它允许我们把多个不同类型的变量组合在一起,以便更方便地管理和使用它们。 在内存中,结构体的存储空间是按照成员变量的顺序依次分配的。但是,为了提高访问效率,编译器会对结构体进行对齐操作,也就是将结构体的起始地址调整为成员变量大小的整数倍。 例如,一个包含三个成员变量的结构体: ``` struct Test { char a; int b; short c; }; ``` 在32位系统中,char占1个字节,int占4个字节,short占2个字节,所以这个结构体的大小应该是1 + 4 + 2 = 7字节。但是,如果我们直接按顺序分配,结构体的起始地址是一个奇数,这样访问效率会降低。因此,编译器会在结构体的成员变量之间插入一些字节,使得结构体的起始地址是4的倍数。这样,结构体的大小就会变成12字节。 具体的对齐规则和字节数是由编译器决定的,不同的编译器可能会有不同的规则。但是,大多数编译器都会使用和本例相似的规则。 在面试中,经常会出现一些关于结构体对齐的问题,例如: 1. 结构体的大小是多少? 2. 结构体成员变量的顺序会影响结构体的大小吗? 3. 如何使用#pragma pack指令来控制结构体对齐? 4. 等等。 对于这些问题,我们需要对结构体对齐有一个深刻的理解,才能够正确回答。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值