前置必懂:指针大小区分(X86 / X64) X86 (32 位环境):地址编号 32 位 → 所有指针占 4 字节 X64 (64 位环境):地址编号 64 位 → 所有指针占 8 字节
int、double、结构体指针、数组指针,不管指向谁,指针本身大小全都一样。
补充:指针类型决定的是指针加减时的步长,不决定指针本身占用字节。
一、结构体 & 内存对齐(核心重点)
内存对齐 4 条黄金规则(必须背)
- 第一个成员,放在偏移量为 0 的地址。
- 其余成员,要对齐到对齐数的整数倍地址。 对齐数 = min (编译器默认对齐数,当前成员所占字节大小) VS 默认对齐数 = 8
- 结构体总大小,必须是本结构体最大对齐数的整数倍,不够就补空闲填充字节(浪费的空间,也叫填充字节 / 补丁)。
- 如果结构体里面嵌套另一个结构体:嵌套结构体要对齐到「它自己的最大对齐数」的整数倍地址;最外层结构体总大小,是全部成员 (含嵌套结构体) 最大对齐数的整数倍。
为什么要有内存对齐? CPU 读取内存是整块读取(一次读 4 字节 / 8 字节)。 对齐以后,CPU 读取一个成员只需要读 1 次内存;不对齐就需要读 2 次内存再拼接数据。 用少量空闲内存,换取更快的访问速度。空间换时间。
例题讲解
struct stu1 { char a; //1字节 short b; //2字节 int c; //4字节 };偏移计算: a:偏移 0,占 1 字节(0 号地址) b:对齐数 min (8,2)=2,地址必须是 2 倍数 → 偏移 2,占用 2‑3 c:对齐数 min (8,4)=4,地址必须 4 倍数 → 偏移 4,占用 4‑7 已占用到 7 号地址。最大对齐数 = 4。 总大小 8,是 4 的倍数。 sizeof (struct stu1) = 8
struct stu2 { char a; //1 int c; //4 short b; //2 };a:偏移 0,占用 0 c:对齐数 4,偏移 4,占用 4‑7 b:对齐数 2,偏移 8,占用 8‑9 目前用到 9 号地址。最大对齐数 = 4。 总大小必须是 4 倍数,9 往后最近的 4 倍数就是 12。 sizeof (struct stu2)=12
修改默认对齐数#pragma pack(n),n 只能填 1,2,4,8,16 作用:把编译器默认对齐数临时改成 n。 对齐数公式变为:对齐数 = min (n , 成员字节大小)
例:
#pragma pack(4) struct stu7 { int a; //4 0~3 char b; //1 4 double c; //8,min(4,8)=4,对齐数4 →偏移8,8‑15 };用到 15,总大小 16,是最大对齐数 4 的倍数。sizeof=16。
二、位段(结构体的进阶玩法,节省内存)
基础格式
struct 名字 { 整型类型 成员名 : 位数; };位数:代表这个成员占用多少个二进制位 (bit),不是字节! 1 字节 = 8 bit
示例
struct Test { unsigned int a : 4; //占用4个比特位 unsigned int : 2; //无名位段,占位,这2个位不能使用 unsigned int b : 6; //占用6个比特位 };两个特殊位段 0 位段unsigned int : 0;剩下没用完的空位直接舍弃,强制下一个成员放到一块全新的存储单元。
无名位段:只有冒号和位数,没有成员名。用来填充空位、对齐,这块空间永远不能读写。
位段硬性限制(小白必背坑)
- 成员类型只能是整型家族:int /unsigned int /signed int;char 少数编译器支持,但不推荐,不能写浮点。
- 单个成员冒号后面的位数不能超过它本身类型的总比特数,int 最多写:32。
- 不能对位段成员取地址
&a。地址是按字节编号,位是字节里面拆分出来的,没有独立地址。 - 不能做成数组
a[3]:2,语法报错。 - n 位无符号数取值范围:0 ~ 2ⁿ‑1;有符号:‑2ⁿ⁻¹ ~ 2ⁿ⁻¹‑1。赋值超出范围高位截断,数据出错。
补充:位段内部 bit 排布(大端 / 小端)C 标准没有规定,不同编译器结果不同。考试只考上面 5 条规则,不考 bit 内部顺序。 优点:存放大量小范围标记数据,大幅节省内存空间。 缺点:不可跨平台,工程开发慎用。
三、枚举 enum(一组有关系的整型常量)
基础语法
enum 枚举名 { 元素1, 元素2, 元素3 };默认规则:第一个元素默认值 = 0,往后依次 + 1。
enum Week { Mon, //0 Tue, //1 Wed //2 };手动赋值
enum Color { red=2, green, //自动+1 → 3 blue=6, yellow //自动+1 →7 };定义变量两种写法 写法 1:后面定义(推荐)
int main() { enum Week day; day = Mon; }写法 2:定义枚举类型的时候顺便创建变量
enum Sex { man, woman } s1,s2;typedef 简化
typedef enum { open, close }Status; int main() { Status st = open; }枚举核心特性
- 枚举里面的元素都是常量,不能修改值。
- 枚举变量本质就是 int,可以做加减运算、比较大小。
- C 语言语法允许给枚举变量随便赋一个整数,但逻辑上不推荐,破坏枚举本意。
- 优先用 enum 代替
#define定义常量:枚举调试更方便,可读性更强。
四、联合(共用体) union【高频易混,和结构体区分】
核心本质:所有成员共用同一块内存空间,同一时刻只能使用其中 1 个成员。一个赋值,其余成员数据立刻被覆盖。
语法格式
union 联合名 { 成员1; 成员2; 成员3; };示例
union Data { int i; float f; char c; };大小计算规则 联合所占字节大小 = 联合里面最大成员的字节大小 ⚠但是!还要对齐!联合的总大小,必须是最大对齐数的整数倍。
例:
union Data { char c; // 1字节 int i; // 4字节 };最大成员 4 字节,sizeof (union Data)=4。
补充例子:
union Test { char arr[5]; //5字节 int i; //4字节 };最大成员 5 字节,最大对齐数 4,向上取最近 4 的倍数,sizeof=8。
使用演示
union Data d; d.i = 10; //现在这块内存存整数 printf("%d", d.i); d.c = 'A'; //覆盖原来i的数据 printf("%c", d.c);结构体 VS 联合对比表
表格
| 类型 | 内存关系 | 内存大小 |
|---|---|---|
| struct 结构体 | 所有成员各自独立,互不干扰,同时共存 | 所有成员空间相加 + 对齐填充字节 |
| union 联合 | 所有成员共用一块内存,互斥使用 | 最大成员大小 (还要对齐) |
五、匿名结构体
普通结构体(带名字,可以反复创建变量)
struct Student { int age; }; struct Student s1; struct Student s2;匿名结构体:struct 后面不写结构体名字
struct { int age; char name[20]; } stu; //定义的时候,必须立刻写上变量名!匿名结构体三条铁律
- struct 后面没有类型名字
- 只能在定义的那一行直接创建变量;后面不能再拿这个类型定义新变量。
- 只能一次性使用,不能复用。
最常见用法:嵌套在另一个结构体里面
struct Person { int id; struct { int year; int month; } birthday; }; //访问方式 struct Person p; p.birthday.year = 2000;struct /union/ 位段 /enum 汇总对比
表格
| 类型 | 内存特点 | 核心考点 |
|---|---|---|
| struct 结构体 | 成员独立,同时共存,内存相加 + 对齐填充 | 内存对齐,sizeof 计算 |
| union 联合 | 成员共用同一块内存,互斥使用 | 共用内存,sizeof 取最大成员并对齐 |
| 位段 | 在整型内部拆分 bit 位,节省空间 | 不能取地址、位数限制、不能跨平台 |
| enum 枚举 | 一组整型常量,不是变量 | 默认从 0 自增,元素是常量 |
刷题坑汇总
- 指针本身大小只看 32/64 位平台,和指针指向什么类型无关;指针类型决定
+1跳多少字节。 - 内存对齐计算两步:先算每个成员偏移,最后结构体总大小要等于最大对齐数整数倍。
#pragma pack(n)只对当前编译单元临时生效,n 推荐 1/2/4/8/16。- 位段不能取地址
&,不能做数组,位数不能超过基础类型总 bit。 - union 所有成员共用内存,修改一个,其他成员值被覆盖。
- 匿名结构体:定义时必须紧跟变量名,后面不能重复使用这个类型。
- 枚举成员是常量,不能赋值修改;枚举变量本质是整型。