C语言中,sizeof 运算符用于计算数据类型(如变量或结构体)的大小,但对于结构体(struct)而言,sizeof(struct) 计算的结果可能不等于各个成员 sizeof() 结果的总和。主要是由于内存对齐和填充字节(padding)的存在。

1、内存对齐和填充

C语言中,编译器通常会为了提高内存访问的效率,对结构体的成员进行内存对齐。也就是编译器会按照系统的要求,将结构体中的某些成员存储在特定的内存地址上,这些地址通常是成员类型大小的整数倍。为了实现这种对齐,编译器可能会在某些成员之间插入填充字节,导致结构体的实际大小比其各个成员的大小之和要大。

内存对齐的规则,每个数据成员的地址必须是该数据类型大小的整数倍。例如,int 类型的成员通常需要存储在能被 4 整除的地址上(因为 int 通常是 4 字节)。

结构体的总大小必须是其最大对齐成员大小的倍数,这可能会在结构体末尾添加填充字节。

#include <stdio.h>

struct Example {
    char a;    // 1 字节
    int b;     // 4 字节
    char c;    // 1 字节
};

int main() {
    printf("Size of char: %zu\n", sizeof(char));   // 1
    printf("Size of int: %zu\n", sizeof(int));     // 4
    printf("Size of struct Example: %zu\n",
     sizeof(struct Example));  // 12(有填充)
    
    return 0;
}

注意:按常规计算,这些成员的总和是 1 + 4 + 1 = 6 字节,但实际 sizeof(struct Example) 可能会返回 12,而不是 6。原因如下:

1)对齐规则

int 通常需要按 4 字节对齐,因此 char a 后面会有 3 个字节的填充,使 b 能够在 4 字节边界对齐。

2)填充规则

最后一个成员 char c 也需要填充字节来确保结构体的总大小是 int 类型(最大对齐成员)的倍数,这样在数组中下一个结构体实例也能正确对齐。

2、检查填充字节

可以通过打印各个成员的地址来查看编译器如何进行内存对齐,代码如下,

#include <stdio.h>

struct Example {
    char a;    // 1 字节
    int b;     // 4 字节
    char c;    // 1 字节
};

int main() {
    struct Example ex;
    printf("Address of a: %p\n", (void*)&ex.a);
    printf("Address of b: %p\n", (void*)&ex.b);
    printf("Address of c: %p\n", (void*)&ex.c);
    
    printf("Size of struct Example: %zu\n", sizeof(struct Example));
    
    return 0;
}

3、减少结构体中的填充字节方法

为了减少结构体的填充字节,可以通过调整成员的顺序来优化内存使用。如将较大的数据类型放在较小的数据类型之前等方法,

#include <stdio.h>

struct OptimizedExample {
    int b;     // 4 字节
    char a;    // 1 字节
    char c;    // 1 字节
};

int main() {
  printf("Hello, World!");
  return 0;
}

4、sizeof() 使用示例

使用 sizeof() 计算基本数据类型、变量、数组、指针、结构体和动态内存分配的大小。

#include <stdio.h>
#include <stdlib.h>

// 定义一个结构体
struct Person {
    char name[50];
    int age;
    float height;
};

int main() {
    // 1. 获取基本数据类型的大小
    printf("基本数据类型的大小:\n");
    printf("sizeof(char): %lu 字节\n", sizeof(char));
    printf("sizeof(short): %lu 字节\n", sizeof(short));
    printf("sizeof(int): %lu 字节\n", sizeof(int));
    printf("sizeof(long): %lu 字节\n", sizeof(long));
    printf("sizeof(float): %lu 字节\n", sizeof(float));
    printf("sizeof(double): %lu 字节\n", sizeof(double));
    
    // 2. 获取变量的大小
    int x = 10;
    double y = 20.5;
    printf("\n变量的大小:\n");
    printf("sizeof(x): %lu 字节\n", sizeof(x));
    printf("sizeof(y): %lu 字节\n", sizeof(y));

    // 3. 获取数组的大小
    int arr[10];
    printf("\n数组的大小:\n");
    printf("数组 arr 的总大小:%lu 字节\n", sizeof(arr));
    printf("数组元素个数:%lu 个\n", sizeof(arr) / sizeof(arr[0]));

    // 4. 获取指针的大小
    int *ptr;
    char *cptr;
    printf("\n指针的大小:\n");
    printf("sizeof(ptr): %lu 字节\n", sizeof(ptr));
    printf("sizeof(cptr): %lu 字节\n", sizeof(cptr));

    // 5. 获取结构体的大小
    struct Person person;
    printf("\n结构体的大小:\n");
    printf("结构体 Person 的大小:%lu 字节\n", sizeof(person));
    printf("name 成员的大小:%lu 字节\n", sizeof(person.name));
    printf("age 成员的大小:%lu 字节\n", sizeof(person.age));
    printf("height 成员的大小:%lu 字节\n", sizeof(person.height));

    // 6. 使用 sizeof 计算动态内存分配
    // 动态分配 5 个 int 类型的内存空间
    int *dynamic_array = (int *)malloc(5 * sizeof(int));  
    if (dynamic_array == NULL) {
        printf("内存分配失败!\n");
        return 1;
    }
    printf("\n动态内存分配的大小:\n");
    printf("已分配的内存大小:%lu 字节\n", 5 * sizeof(int));

    // 释放动态分配的内存
    free(dynamic_array);

    // 7. 使用 size_t 获取 sizeof 返回值
    size_t size = sizeof(x);  // 使用 size_t 接收 sizeof 返回值
    printf("\nsize_t 的使用:\n");
    printf("变量 x 的大小为:%zu 字节\n", size);

    return 0;
}

推荐文档