C语言中结构体(struct
)的大小计算涉及到几个关键概念,包括数据对齐(alignment)、填充(padding)、以及可能的尾部填充。结构体的总大小不仅取决于其成员的大小,还受到成员的排列顺序和编译器的对齐要求的影响。
数据对齐
数据对齐是指结构体中每个成员相对于结构体起始地址的偏移量必须是该成员大小的整数倍。这是为了提高内存访问效率。例如,如果一个[int](file:///home/book/APUE/solution.cpp#8%2C5-8%2C5)类型的成员大小为4字节,那么它的地址偏移量应该是4的倍数。
填充(Padding)
为了满足数据对齐的要求,编译器可能会在结构体成员之间或结构体末尾插入额外的空间(填充)。这意味着结构体的实际大小可能大于其所有成员大小的总和。
尾部填充
即使在结构体的最后一个成员之后,也可能添加填充,以确保结构体的总大小是某个数(通常是结构体中最大成员大小)的倍数。
示例 1
假设有以下结构体:
struct Example {
char a; // 字符型占用1字节
int b; // 整型通常占用4字节
char c; // 字符型占用1字节
};
在大多数情况下,编译器对int
类型的对齐要求是4字节。因此,上述结构体的大小计算如下:
char a;
占用1字节,之后为了满足int b;
的对齐要求,会插入3字节的填充。int b;
占用4字节。char c;
占用1字节,之后可能还会有3字节的尾部填充,以确保结构体大小是4的倍数(这取决于具体的编译器和编译器的设置)。
因此,这个结构体的总大小可能是12字节。
示例2
struct stu {
char a; // 字符型占用1字节
short int b; // 短整型通常占用2字节
int c; // 整型通常占用4字节
} a;
计算时需要考虑到编译器的数据对齐规则,这些规则可能因编译器和目标平台的不同而有所差异。以下是一个基于常见对齐规则的计算过程:
-
char a;
占用1字节。之后,为了满足short int b;
的对齐要求(通常是2字节对齐),编译器会在a
和b
之间插入1字节的填充。 -
short int b;
占用2字节。 -
int c;
由于int
类型通常要求4字节对齐,而在b
之后,当前总大小为4字节(char a
+ 1字节填充 +short int b
),所以可以直接紧跟b
存放int c
,无需额外填充。
因此,结构体stu
的总大小为:
char a;
-> 1字节- 填充 -> 1字节
short int b;
-> 2字节int c;
-> 4字节- 总计:8字节
示例3
struct Test1
{
char c1;
short s;
char c2;
int i;
};
struct Test2
{
char c1;
char c2;
short s;
int i;
};
结构体 Test1
char c1;
占用1字节,之后为了满足short s;
的对齐要求(通常是2字节对齐),编译器会在c1
和s
之间插入1字节的填充。short s;
占用2字节。char c2;
占用1字节,之后为了满足int i;
的对齐要求(通常是4字节对齐),编译器会在c2
和i
之间插入3字节的填充。int i;
占用4字节。
结构体Test1
的总大小为:1 + 1(填充)+ 2 + 1 + 3(填充)+ 4 = 12字节。
结构体 Test2
char c1;
占用1字节。char c2;
紧接着c1
占用1字节,两个char
类型连续放置不需要填充。short s;
由于short
通常要求2字节对齐,当前总大小为2字节(char c1
+char c2
),所以可以直接紧跟c2
存放short s
,无需额外填充。int i;
由于int
类型通常要求4字节对齐,而在s
之后,当前总大小为4字节,所以可以直接紧跟s
存放int i
,无需额外填充。
结构体Test2
的总大小为:1 + 1 + 2 + 4 = 8字节。
这个例子展示了如何通过重新排列结构体成员的顺序来减少因对齐引入的填充,从而减小结构体的总大小。实际的大小可能因编译器和目标平台的不同而有所变化,但通常情况下,Test2
由于更有效的成员排列,会比Test1
有更小的总大小。**
测试验证
#include <stdio.h>
struct Example {
char a; // 字符型占用1字节
int b; // 整型通常占用4字节
char c; // 字符型占用1字节
};
struct stu {
char a; // 字符型占用1字节
short int b; // 短整型通常占用2字节
int c; // 整型通常占用4字节
} ;
struct Test1 {
char c1;
short s;
char c2;
int i;
};
struct Test2 {
char c1;
char c2;
short s;
int i;
};
int main() {
printf("Size of Example: %zu bytes\n", sizeof(struct Example));
printf("Size of stu: %zu bytes\n", sizeof(struct stu));
printf("Size of Test1: %zu bytes\n", sizeof(struct Test1));
printf("Size of Test2: %zu bytes\n", sizeof(struct Test2));
return 0;
}
计算技巧
- 结构体的起始地址总是对齐的。
- 结构体的大小是其最宽基本成员大小的倍数。
- 重新排列结构体成员的顺序可能减少因对齐引入的填充,从而减小结构体的总大小。
注意
实际的对齐规则和填充量可能因编译器和目标平台的不同而有所差异。可以通过编译器特定的属性(如GCC的__attribute__((packed))
)来控制对齐行为,但这可能会影响性能或引起其他问题。