结构体与对齐原则
结构体
结构体(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
};
计算步骤:
-
成员大小:
char a
: 1 byteint b
: 4 byteschar c
: 1 byte
-
对齐要求:
char
类型通常对齐到1字节。int
类型通常对齐到4字节。
-
填充:
char a
后的下一个成员int b
需要对齐到4字节边界,因此在a
之后会有3个字节的填充。int b
占4字节,对齐到4字节边界,不需要填充。char c
占1字节,但为了结构体大小对齐到4字节边界,后面会有3个字节的填充。
-
最终结构体大小:
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
};
计算步骤:
-
Inner
结构体的大小:char x
: 1 byteint y
: 4 bytesx
之后有3字节的填充来对齐y
sizeof(Inner) = 1 (x) + 3 (padding after x) + 4 (y) = 8 bytes
-
Outer
结构体的大小:char a
: 1 bytestruct Inner b
: 8 bytes (已经计算过)int c
: 4 bytes
-
对齐与填充:
char a
后面填充3个字节对齐到struct Inner b
,因为b
需要4字节对齐。struct Inner b
占8字节,不需要填充。int c
占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()
计算步骤:
-
成员大小与对齐:
- 由于使用了
#pragma pack(1)
,所有成员按1字节对齐,没有填充。 char a
: 1 byteint b
: 4 byteschar c
: 1 byte
- 由于使用了
-
最终结构体大小:
sizeof(Packed) = 1 (a) + 4 (b) + 1 (c) = 6 bytes
在默认对齐的情况下,填充字节通常用于保证对齐,因此结构体的大小会大于各个成员大小的总和。
通过控制对齐方式(如#pragma pack(1)
),可以减少甚至消除填充,从而减小结构体的大小,但这可能会降低访问速度,影响性能。
小结
- 成员的类型和顺序: 不同类型的成员大小不同,它们的排列顺序也会影响结构体的大小。
- 编译器的对齐规则: 不同的编译器可能会有略微不同的对齐规则。
- 指定的对齐数: 使用
#pragma pack
指令可以指定对齐数,从而影响结构体的大小。 - 编译器的优化选项: 编译器的一些优化选项也可能会影响结构体的大小。