结构体与对齐原则

结构体

结构体(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)),可以减少甚至消除填充,从而减小结构体的大小,但这可能会降低访问速度,影响性能。

小结