C语言结构体深入解析:声明、内存对齐与传参
结构体基础声明
结构体是一种用户自定义的数据类型,它将不同类型的数据组合成一个整体。每个组成部分被称为成员变量。
声明语法
struct 标签
{
数据类型 成员1;
数据类型 成员2;
// ... 更多成员
}; // 注意分号
例如,定义一个学生结构体:
struct Student {
char name[30];
int age;
char gender[10];
};
变量创建与初始化
#include <stdio.h>
struct Student {
char name[30];
int age;
char gender[10];
char student_id[20];
};
int main() {
// 按成员顺序初始化
struct Student stu1 = {"Alice", 22, "Female", "202401001"};
printf("Name: %s\n", stu1.name);
printf("Age: %d\n", stu1.age);
printf("Gender: %s\n", stu1.gender);
printf("ID: %s\n", stu1.student_id);
// 指定成员初始化
struct Student stu2 = {.age = 20, .name = "Bob", .student_id = "202401002", .gender = "Male"};
printf("Name: %s\n", stu2.name);
printf("Age: %d\n", stu2.age);
printf("Gender: %s\n", stu2.gender);
printf("ID: %s\n", stu2.student_id);
return 0;
}
匿名结构体
声明结构体时可以省略标签,创建匿名结构体类型:
// 匿名结构体,直接定义变量
struct {
int x;
char y;
float z;
} point;
// 可用于数组或指针
struct {
int x;
char y;
float z;
} points[10], *ptr;
匿名结构体无法重复使用,仅适用于一次性定义。
结构体自引用
结构体内部不能包含自身类型的完整实例,否则会导致无限递归。正确的做法是使用指针:
// 错误声明
struct Node {
int data;
struct Node next; // 编译错误
};
// 正确声明
struct Node {
int data;
struct Node* next; // 使用指针
};
当与typedef结合时需注意:
// 错误用法:匿名结构体内无法使用Node
typedef struct {
int data;
Node* next; // 编译错误:Node尚未定义
} Node;
// 正确用法
typedef struct Node {
int data;
struct Node* next;
} Node;
结构体内存对齐
结构体大小并非成员大小的简单相加,而是遵循内存对齐规则。这是由硬件和性能需求决定的。
对齐规则
- 第一个成员:从偏移量为0的地址开始存储。
- 其他成员:存储地址必须是对齐数的整数倍。对齐数 = min(编译器默认对齐数, 成员自身大小)。
- 结构体总大小:必须是最大对齐数的整数倍。
- 嵌套结构体:嵌套结构体对齐到其内部最大对齐数的整数倍处,总大小为所有最大对齐数(含嵌套结构体)的整数倍。
注意:VS默认对齐数为8,Linux GCC无默认对齐数(对齐数等于成员自身大小)。
示例分析
struct S1 {
char c1;
int i;
char c2;
}; // 大小:12字节
struct S2 {
char c1;
char c2;
int i;
}; // 大小:8字节
struct S3 {
double d;
char c;
int i;
}; // 大小:16字节
struct S4 {
char c1;
struct S3 s3;
double d;
}; // 大小:32字节
可以看到,成员顺序影响结构体大小。将小成员集中放置可节省空间,如S2比S1节省了4字节。
修改默认对齐数
#include <stdio.h>
#pragma pack(1) // 设置对齐数为1
struct PackedStruct {
char c1;
int i;
char c2;
}; // 大小:6字节
#pragma pack() // 恢复默认对齐数
int main() {
printf("Size: %zu\n", sizeof(struct PackedStruct));
return 0;
}
结构体传参
传递结构体时,推荐使用指针而非值传递,以减少系统开销:
#include <stdio.h>
struct LargeData {
int array[1000];
int count;
};
struct LargeData data = {{1, 2, 3, 4}, 1000};
// 值传递:复制整个结构体,开销大
void print_by_value(struct LargeData d) {
printf("Count: %d\n", d.count);
}
// 指针传递:仅复制地址,效率高
void print_by_address(struct LargeData* pd) {
printf("Count: %d\n", pd->count);
}
int main() {
print_by_value(data); // 传结构体
print_by_address(&data); // 传地址
return 0;
}
指针传递避免了结构体的完整拷贝,特别是当结构体较大时,能显著提升性能。