C语言结构体、联合、位段内存大小的计算

8/25/2023 C语言

# C 语言结构体、联合、位段内存大小的计算

# 结构体

先看一段代码,思考一下s1和s2的大小是多少呢?

#include <stdio.h>

struct S1 {
    char c1;
    int i;
    char c2;
};

struct S2 {
    char c1;
    char c2;
    int i;
};

int main() {
    struct S1 s1;
    struct S2 s2;

    printf("%d %d\n", sizeof(s1),sizeof(s2));
    return 0;
}

答案是:12 8,为什么结构体数据一样,而所占内存空间大小不一样,是因为结构体在存储过程中需要进行内存对齐。那么如何计算呢?

对齐规则:

  1. 结构体第一个成员对齐到相对结构体变量起始位置偏移量为 0 的地址处
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处 对齐数 = 编译器默认的一个对齐数与该成员变量大小的 较小值
  • VS 中默认的指为 8
  • Linux 没有对齐数,对齐数就是成员自身的大小
  1. 结构体总大小为 最大对齐数 (结构体中每个成员变量都有一个对齐数,所有对齐数中最大的)的整数倍
  2. 如果结构体嵌套结构体的情况,嵌套的结构体对齐到自己成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍

那么我们按照规则分析一下上面的代码:

先计算一下s1:

![image-20230825122903298](/Users/mac/Library/Application Support/typora-user-images/image-20230825122903298.png)

再计算一下s2:

![image-20230825123524334](/Users/mac/Library/Application Support/typora-user-images/image-20230825123524334.png)

# offsetof

offsetof (type,member)

Return member offset

This macro with functional form returns the offset value in bytes of member member in the data structure or union type type.

The value returned is an unsigned integral value of type size_t (opens new window) with the number of bytes between the specified member and the beginning of its structure.

返回成员偏移量 该函数形式的宏返回数据结构或联合类型中成员成员的字节偏移值。(相对于起始位置的偏移量)

返回的值是 size_t 类型的无符号整数值,其中包含指定成员与其结构开头之间的字节数。

#include <stdio.h>
#include <stddef.h>

struct S1 {
    char c1;
    int i;
    char c2;
};

struct S2 {
    char c1;
    char c2;
    int i;
};

int main() {
    printf("%u\n", offsetof(struct S1, c1)); // 0
    printf("%u\n", offsetof(struct S1, i));// 4
    printf("%u\n", offsetof(struct S1, c2));// 8

    printf("%u\n", offsetof(struct S2, c1));// 0
    printf("%u\n", offsetof(struct S2, c2));// 1
    printf("%u\n", offsetof(struct S2, i));// 4

    return 0;
}

再看一个嵌套结构体的例子:

struct S3 {
    double d;
    char c;
    int i;
};

struct S4 {
    char c1;
    struct S3 s3;
    double d;


int main() {
   struct S4 s4;

    printf("%d\n", sizeof(s4)); // 32

    return 0;
}

![image-20230825132527911](/Users/mac/Library/Application Support/typora-user-images/image-20230825132527911.png)

# 为什么存在内存对齐

  1. 平台原因(移植原因)

    不是所有的硬件平台都能访问任意地址上的任意数据,某些硬件平台智能在某些地址触取某些特定类型的数据,否则跑出硬件异常。

  2. 性能原因

    数据结构(尤其是栈)应该尽可能的在自然边界上对齐。

    原因在于,为了访问未对齐的内存,处理器需要做两次内存访问,而对齐的内存访问仅需要一次访问。

总体来说,结构体内存对齐是拿空间来换取时间的做法。

在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:

  • 让占用空间小的成员尽量集中到一起

# 修改默认对齐数

#include <printf.h>

// 默认偏移量改为4
#pragma pack(4)
struct S{
    char c;
    double d;
};

// 恢复默认偏移量
#pragma pack()
struct S1{
    char c;
    double d;
};

int main() {
    struct S s;
    struct S1 s1;

    printf("%d\n", sizeof(s)); // 12
    printf("%d\n", sizeof(s1)); // 16

    return 0;
}

# 位段

  1. 位段段成员必须是int,unsigned int,signed int
  2. 位段段成员名后边有一个冒号和数字
#include <printf.h>

struct S {
    int _a;
    int _b;
    int _c;
    int _d;
};

struct S1 {
    int _a: 2; // _a这个成员只占2个bit位
    int _b: 5; // _b这个成员只占5个bit位
    int _c: 10;
    int _d: 30;
};
// 1. 首先开辟一个int 4 个字节的空间
// 2. _a 占 2 个,还剩 30
// 3. _b 占 5 个,还剩 25
// 4. _c 占 10 个,还剩 15
// 5. _d 占 30 个,不够 30 继续开辟 4 个字节

int main() {

    printf("%d\n", sizeof(struct S)); // 16
    printf("%d\n", sizeof(struct S1)); // 8

    return 0;
}

位段的设计是为了节省空间,不存在对齐。

位段的内存分配:

  • 位段段成员可以是int,unsigned int,signed intchar(char属于整型家族)类型
  • 位段的空间上是按照需要以4个人字节(int)或者一个字节(char)的方式来开辟的
  • 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应避免使用位段。

位段的应用:

IP协议包

![image-20230825140221656](/Users/mac/Library/Application Support/typora-user-images/image-20230825140221656.png)

# 联合体

联合体成员是共用一块内存空间的,至少是最大成员的大小。

#include <printf.h>

union Un {
    int i;
    char c;
};

int main() {
    union Un un;

    printf("%d\n", sizeof(un)); // 4

    printf("%p\n", &un); // 0x7ff7bf78f3c8
    printf("%p\n", &(un.i)); // 0x7ff7bf78f3c8
    printf("%p\n", &(un.c)); // 0x7ff7bf78f3c8
		
    un.i = 0x11223344;
    un.c = 0x55;

    printf("%x\n", un.i); // 11223355
  
    return 0;
}

![image-20230825143423154](/Users/mac/Library/Application Support/typora-user-images/image-20230825143423154.png)![image-20230825143511333](/Users/mac/Library/Application Support/typora-user-images/image-20230825143511333.png)

![image-20230825143536827](/Users/mac/Library/Application Support/typora-user-images/image-20230825143536827.png)

联合体在计算大小时也需要内存对齐

#include <printf.h>

union Un {
    char arr[5]; // 大小 5, 对齐数 1
    int i; // 大小 4 对齐数 4
};

int main() {
    union Un un;

    // 总大小为最大对齐数的倍数
    printf("%d\n", sizeof(un)); // 8

    return 0;
}
Last Updated: 3/20/2024, 8:15:43 AM
강남역 4번 출구
Plastic / Fallin` Dild