结构体是 C 语言中将多个不同类型变量组合在一起的复合数据类型。结构体允许你创建自定义的数据结构,是面向对象编程中"类"概念的前身。
核心特性:
| 语法 | 说明 |
|---|---|
struct Tag { ... }; |
结构体类型声明 |
struct Tag var; |
声明结构体变量 |
struct Tag var = { .x = 1 }; |
C99 指定初始化器 |
var.member |
用 . 访问成员 |
ptr->member |
用 -> 通过指针访问成员 |
sizeof(struct Tag) |
结构体大小(含对齐填充) |
offsetof(type, member) |
成员偏移量 |
container_of(ptr, type, member) |
由成员指针反推结构体指针 |
// 12_struct_basics.c
#include <stdio.h>
#include <stddef.h>
struct Person {
char name[32];
int age;
double salary;
};
int main(void)
{
// C89 风格初始化
struct Person p1 = {"Alice", 30, 7500.50};
// C99 指定初始化器(可乱序、可省略)
struct Person p2 = {
.salary = 8200.00,
.name = "Bob",
.age = 25
};
// . 和 -> 操作符
printf("p1: %s, %d, %.2f\n", p1.name, p1.age, p1.salary);
struct Person *pp = &p2;
printf("p2: %s, %d, %.2f\n", pp->name, pp->age, pp->salary);
// 整体赋值(浅拷贝)
struct Person p3 = p1;
printf("p3: %s, %d, %.2f\n", p3.name, p3.age, p3.salary);
return 0;
}
编译运行:
gcc -Wall -Wextra -std=c99 -o 12_struct_basics 12_struct_basics.c
./12_struct_basics
// 12_struct_align.c
#include <stdio.h>
#include <stddef.h>
struct Aligned {
char a; // 1 byte + 3 padding
int b; // 4 bytes
char c; // 1 byte + 3 padding
};
struct Packed {
char a;
char c;
int b;
};
#pragma pack(push, 1)
struct PackedAttribute {
char a; // 1 byte
int b; // 4 bytes, no padding
char c; // 1 byte
};
#pragma pack(pop)
struct Nested {
int id;
struct {
double x;
double y;
} point;
char tag;
};
int main(void)
{
printf("sizeof(struct Aligned) = %zu\n", sizeof(struct Aligned));
printf(" offset of a = %zu\n", offsetof(struct Aligned, a));
printf(" offset of b = %zu\n", offsetof(struct Aligned, b));
printf(" offset of c = %zu\n", offsetof(struct Aligned, c));
printf("\n");
printf("sizeof(struct Packed) = %zu\n", sizeof(struct Packed));
printf(" offset of a = %zu\n", offsetof(struct Packed, a));
printf(" offset of c = %zu\n", offsetof(struct Packed, c));
printf(" offset of b = %zu\n", offsetof(struct Packed, b));
printf("\n");
printf("sizeof(struct PackedAttribute) = %zu\n", sizeof(struct PackedAttribute));
printf("\n");
printf("sizeof(struct Nested) = %zu\n", sizeof(struct Nested));
printf(" offset of id = %zu\n", offsetof(struct Nested, id));
printf(" offset of point = %zu\n", offsetof(struct Nested, point));
printf(" offset of tag = %zu\n", offsetof(struct Nested, tag));
return 0;
}
编译运行:
gcc -Wall -Wextra -std=c99 -o 12_struct_align 12_struct_align.c
./12_struct_align
// 12_struct_pass.c
#include <stdio.h>
#include <stddef.h>
// container_of 宏原理演示
#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member) \
((type *)((char *)(ptr) - offsetof(type, member)))
struct Node {
int data;
int priority;
struct Node *next;
};
// 值传递:拷贝整个结构体,开销大
void print_by_value(struct Node n)
{
printf("[by value] data=%d, pri=%d\n", n.data, n.priority);
}
// 指针传递:只传地址,高效(推荐)
void print_by_ptr(const struct Node *n)
{
printf("[by ptr] data=%d, pri=%d\n", n->data, n->priority);
}
// 柔性数组成员
struct Buffer {
int len;
char data[]; // C99 柔性数组,必须是最后一个成员
};
int main(void)
{
// 结构体传参
struct Node n = {42, 10, NULL};
print_by_value(n);
print_by_ptr(&n);
// container_of 演示:已知成员指针,反推结构体指针
struct Node node = {100, 5, NULL};
struct Node *prio_ptr = &node.priority; // 假设外部只拿到了 priority 的指针
struct Node *base = container_of(prio_ptr, struct Node, priority);
printf("container_of: recovered data = %d\n", base->data);
// 柔性数组:动态分配
size_t msg_len = 12;
struct Buffer *buf = malloc(sizeof(struct Buffer) + msg_len);
buf->len = msg_len;
memcpy(buf->data, "Hello World!", msg_len);
printf("Buffer len=%d, data=%.*s\n", buf->len, buf->len, buf->data);
free(buf);
return 0;
}
编译运行:
gcc -Wall -Wextra -std=c99 -o 12_struct_pass 12_struct_pass.c
./12_struct_pass
整体赋值是浅拷贝:指针成员共享同一块内存,修改一个会影响另一个。深拷贝需要手动复制指针指向的内容。
#pragma pack 只影响对齐,不影响成员排列顺序:成员在内存中的相对顺序不变。
柔性数组要求:必须是结构体的最后一个成员;包含柔性数组的结构体不能直接赋值(无固定大小);分配时 malloc(sizeof(struct) + extra)。
sizeof 与实际数据大小不同:sizeof 包含填充字节,strlen 只看字符串内容。
结构体嵌套时对齐以最大成员为基准:内嵌结构体的对齐要求取其最大成员的对齐值。
Q1:结构体内存对齐的规则是什么?
- 每个成员的偏移量必须是自身对齐值的整数倍(不足则填充)。
- 结构体整体大小必须是最大对齐值的整数倍。
Q2:#pragma pack(1) 的作用和副作用?
强制 1 字节对齐,消除填充。副作用:CPU 访问非对齐地址可能性能下降甚至崩溃(取决于硬件)。
Q3:结构体值传递和指针传递的区别?
值传递复制整个结构体(大结构体开销大),指针传递只传 4/8 字节地址。注意值传递时修改不影响原数据。
Q4:什么是柔性数组成员?
C99 允许结构体最后一个成员是未知长度的数组
char data[]。必须malloc(sizeof(struct) + length)分配,用于变长数据包。
Q5:container_of 宏的原理?
已知成员指针
ptr,通过ptr - offsetof(type, member)计算出结构体起始地址。Linux 内核广泛使用此宏实现链表等数据结构。