Pwn 基础之C/C++(五):结构体、联合体与内存对齐
本篇目标:掌握复合类型,并能够从字节偏移角度理解结构体布局。
预计阅读时间:15~20 分钟。
1. 结构体
结构体将多个相关字段组合为一个类型:
struct User {
int id;
char name[32];
unsigned int level;
};
创建和初始化:
struct User user = {
.id = 1,
.name = "Alice",
.level = 10
};
访问成员:
printf("%d\n", user.id);
printf("%s\n", user.name);
2. 结构体指针
struct User *pointer = &user;
访问成员:
pointer->id = 2;
等价于:
(*pointer).id = 2;
.用于结构体对象;->用于指向结构体的指针。
3. typedef
typedef struct {
int id;
char name[32];
} User;
之后可以直接写:
User user;
User *pointer = &user;
typedef 创建类型别名,不会创建新变量。
4. 枚举
enum State {
STATE_IDLE,
STATE_RUNNING,
STATE_STOPPED
};
默认值通常依次为 0、1、2。
enum State state = STATE_RUNNING;
也可以显式指定:
enum Permission {
PERMISSION_READ = 1,
PERMISSION_WRITE = 2,
PERMISSION_EXEC = 4
};
这类值适合配合位运算表示多个状态。
5. 联合体
联合体的所有成员共享同一块存储:
union Value {
uint32_t number;
unsigned char bytes[4];
};
union Value value;
value.number = 0x12345678;
number 和 bytes 从同一地址开始。联合体大小通常满足最大成员的大小和对齐要求。
结构体与联合体的区别:
结构体:各成员通常拥有各自的存储位置
联合体:各成员共享同一块存储
6. 为什么需要内存对齐
考虑:
struct Example {
char a;
int b;
char c;
};
字段大小之和通常是:
1 + 4 + 1 = 6
但 sizeof(struct Example) 在常见环境中可能是 12。
一种常见布局:
偏移 0 a
偏移 1~3 填充
偏移 4~7 b
偏移 8 c
偏移 9~11 尾部填充
填充字节称为 padding。它使成员地址满足平台和 ABI 的对齐要求,也保证结构体数组中的每个元素都正确对齐。
7. 查看成员偏移
#include <stddef.h>
#include <stdio.h>
struct Example {
char a;
int b;
char c;
};
int main(void)
{
printf("sizeof: %zu\n", sizeof(struct Example));
printf("a: %zu\n", offsetof(struct Example, a));
printf("b: %zu\n", offsetof(struct Example, b));
printf("c: %zu\n", offsetof(struct Example, c));
return 0;
}
不要用“字段大小简单相加”猜测最终布局,应通过目标环境验证。
8. 成员顺序会影响大小
struct A {
char a;
int b;
char c;
};
struct B {
int b;
char a;
char c;
};
两个结构体拥有相同类型的字段,但布局和最终大小可能不同。将对齐要求相近的成员放在一起,通常可以减少填充。
9. 结构体数组
User users[3];
每个元素占 sizeof(User) 字节:
&users[1] - &users[0]
按字节理解就是一个完整结构体大小。尾部填充保证下一个元素的成员仍满足对齐要求。
10. 结构体传参
按值传递会复制整个结构体:
void print_user(User user);
只读时可以传指针:
void print_user(const User *user)
{
printf("%d %s\n", user->id, user->name);
}
这避免复制完整对象,并通过 const 表明函数不打算修改它。
本篇练习
- 定义
Student结构体并创建数组。 - 打印结构体每个成员的地址和偏移。
- 调整字段顺序,比较
sizeof。 - 使用联合体逐字节观察
uint32_t。 - 使用枚举实现一个简单状态机。
本篇小结
- 结构体组合多个字段;
.访问对象成员,->访问指针所指对象的成员;- 联合体让多个成员共享存储;
- 枚举为状态值提供可读名称;
- padding 会使结构体大小大于字段大小之和;
offsetof可以查看实际成员偏移。
下一篇
下一篇进入程序内存模型,学习栈、堆、生命周期及动态内存管理。

说些什么吧!