B 结构体与对齐原则

结构体与对齐原则

May 12, 2020

结构体

结构体(struct)是一种用户自定义的数据类型,它允许我们将不同类型的数据组合在一起形成一个整体,从而更好地表示复杂的数据。

结构体是 C 语言中非常重要的一种数据类型,广泛应用于各种编程场景。

声明和定义结构体时,需要在语句末尾添加分号(";")

#include <stdio.h>

struct student{
    int num;
    char name[20];
    char gender;
    int age;
    float socre;
    char addr[40];
};//语句末尾的分号(;)必不可少

int main()
{
    struct student s = {9001,"Alice",'M',24,98.9,"LA"};
    struct student sarr[3];
    int i;
    for(i=0; i<3; i++)
    {
        scanf("%d%s %c%d%f%s",&sarr[i].num,&sarr[i].name,&sarr[i].gender,&sarr[i].age,&sarr[i].socre,&sarr[i].addr);
    }
    printf("%d %s %c %d %.2f %s\n",s.num,s.name,s.gender,s.age,s.socre,s.addr);
    for(i=0;i<3;i++)
    {
        printf("%d %s %c %d %.2f %s\n",sarr[i].num,sarr[i].name,sarr[i].gender,sarr[i].age,sarr[i].socre,sarr[i].addr);
    }
}

结构体嵌套: 结构体中可以嵌套其他结构体。

struct Student
{
    int num;
    char name[10];
    struct Date
    {
        int year;
        int month;
        int day;
    }birthday;
    char gender;
};

C 语言允许定义无类型名的结构类型 ,常用于内嵌的结构类型:

struct
{
    int year;
    int month;
    int day;
}birthday;

当 类型名 “Date” 省略时,必须后随结构变量 “birthday” 的定义。

结构体在内存中的存储

  • 成员变量的顺序: 结构体成员在内存中的排列顺序与它们在结构体定义中的顺序一致。
  • 对齐原则: 为了提高内存访问效率,编译器会对结构体成员进行对齐。每个成员的起始地址必须是其类型大小的整数倍。
  • 结构体大小: 结构体的大小通常大于其所有成员大小的总和,这是由于对齐所产生的填充字节。

对齐数

  • 定义: 对齐数是指编译器为每个成员变量所设置的一个最小存储单位。
  • 作用: 对齐数是为了保证每个成员变量都能从一个合适的位置开始访问。
  • 计算: 结构体大小通常是其最大成员大小和所有成员对齐数的最小公倍数。

结构体对齐的优点和缺点

  • 优点:
    • 提高内存访问效率,减少内存访问次数。
    • 符合硬件的访问要求,提高程序运行速度。
  • 缺点:
    • 浪费内存空间,由于填充字节的存在,结构体的大小可能大于其所有成员大小的总和。

结构体对齐原则

在 C 语言中,结构体成员在内存中的存储位置不是简单的连续排列,而是遵循一定的对齐规则,目的是为了提高内存访问效率。一般来说,编译器会按照以下原则进行对齐:

结构体的大小受多个因素影响,其中包括对齐方式和填充(padding)。对齐要求通常是由结构体成员中最大的数据类型决定的。

示例 1:简单结构体

struct Simple {
    char a;   // 1 byte
    int b;    // 4 bytes
    char c;   // 1 byte
};

计算步骤:

  1. 成员大小

    • char a: 1 byte
    • int b: 4 bytes
    • char c: 1 byte
  2. 对齐要求

    • char 类型通常对齐到1字节。
    • int 类型通常对齐到4字节。
  3. 填充

    • char a后的下一个成员int b需要对齐到4字节边界,因此在a之后会有3个字节的填充。
    • int b占4字节,对齐到4字节边界,不需要填充。
    • char c占1字节,但为了结构体大小对齐到4字节边界,后面会有3个字节的填充。
  4. 最终结构体大小

    • sizeof(Simple) = 1 (a) + 3 (padding after a) + 4 (b) + 1 (c) + 3 (padding after c) = 12 bytes

示例 2:带嵌套结构体

struct Inner {
    char x;   // 1 byte
    int y;    // 4 bytes
};

struct Outer {
    char a;           // 1 byte
    struct Inner b;   // 8 bytes (参照下面的计算)
    int c;            // 4 bytes
};

计算步骤:

  1. Inner 结构体的大小

    • char x: 1 byte
    • int y: 4 bytes
    • x之后有3字节的填充来对齐y
    • sizeof(Inner) = 1 (x) + 3 (padding after x) + 4 (y) = 8 bytes
  2. Outer 结构体的大小

    • char a: 1 byte
    • struct Inner b: 8 bytes (已经计算过)
    • int c: 4 bytes
  3. 对齐与填充

    • char a后面填充3个字节对齐到struct Inner b因为b需要4字节对齐
    • struct Inner b占8字节,不需要填充。
    • int c占4字节,不需要额外填充。
  4. 最终结构体大小

    • sizeof(Outer) = 1 (a) + 3 (padding after a) + 8 (b) + 4 (c) = 16 bytes

示例 3:不同对齐方式的影响

假设使用编译器提供的指令更改对齐方式,如#pragma pack(1)

#pragma pack(1)
struct Packed {
    char a;   // 1 byte
    int b;    // 4 bytes
    char c;   // 1 byte
};
#pragma pack()

计算步骤:

  1. 成员大小与对齐

    • 由于使用了#pragma pack(1),所有成员按1字节对齐,没有填充。
    • char a: 1 byte
    • int b: 4 bytes
    • char c: 1 byte
  2. 最终结构体大小

    • sizeof(Packed) = 1 (a) + 4 (b) + 1 (c) = 6 bytes

在默认对齐的情况下,填充字节通常用于保证对齐,因此结构体的大小会大于各个成员大小的总和。

通过控制对齐方式(如#pragma pack(1)),可以减少甚至消除填充,从而减小结构体的大小,但这可能会降低访问速度,影响性能。

小结

  • 成员的类型和顺序: 不同类型的成员大小不同,它们的排列顺序也会影响结构体的大小。
  • 编译器的对齐规则: 不同的编译器可能会有略微不同的对齐规则。
  • 指定的对齐数: 使用 #pragma pack 指令可以指定对齐数,从而影响结构体的大小。
  • 编译器的优化选项: 编译器的一些优化选项也可能会影响结构体的大小。
TouchingFish.top