- 结构体 struct
普通结构体:每个成员单独分配内存,互不共用,存在内存对齐。
struct A1
{
int _a;
int _b;
int _c;
};
每个 int 占 4 字节,sizeof(struct A1) = 12。
位段(是结构体的特殊版本!)
c
struct A
{
int _a : 2; // 只用2个bit
int _b : 5; // 只用5个bit
int _c : 10; // 只用10个bit
int _d : 30; // 只用30个bit
};
语法:类型 名字 : bit数; 冒号后面代表占用多少个二进制位,不是字节!
直接给环境,int的给32bit,char的给8bit,看看给的bit够不够装完元素的bit,如果装不完就直接补一个环境,原先剩下的bit位就不要了。
优点:极度省内存;
⚠️缺点:跨平台不确定!
bit 从左往右放,还是右往左放,C 标准没规定;
一块内存剩下的 bit 不够放下下一个成员,是丢弃还是继续用,编译器自己决定;
工程上要写跨平台代码,尽量不要用位段,笔试只考概念。
2. 联合体 union
所有成员共享同一块内存!!
c
union Un
{
char c;
int i;
};
union 大小:至少等于最大成员的大小,并且要满足对齐
这个例子最大成员 int=4 字节,sizeof(union Un)=4
看最后赋值的才生效,联合体内部的结构顺序无关
经典用途:判断大小端
union Un
{
char c;
int i;
};
int main()
{
union Un u;
u.i = 1;//这里才是最后赋值的,这个生效
if(u.c == 1)
printf(“小端\n”);
else
printf(“大端\n”);
return 0;
}
原理:给 int 写入 1,再通过 char 看内存第一个字节。
小端:低地址存低字节 → 首字节 = 1;大端首字节 = 0。
重点:同一时刻,联合体只有最后赋值的成员是完整有效的;其他成员只是读取同一块内存的视图。
3. 枚举 enum
把一组相关常量打包,每个枚举名本质是整数常量。
c
enum Day
{
Mon, // 默认0
Tues, // 1
Wed, //2
Thur,
Fri,
Sat,
Sun //这里没有;
}; // 这里有;
默认:第一个 = 0,后面依次 + 1;
可以手动赋值:
enum Sex
{
MALE=4,
FEMALE, //自动5
SECRET //自动6
};
✅ 和#define对比
#define 只是文本替换,没有类型检查,这个是宏定义
enum 是 C 的类型,有类型,调试能看见名字,推荐用来写状态码。
4. 结构体传参
// 值传递:拷贝整个结构体,开销大
void Print1(struct S t)
{
}
// 地址传递(推荐):只传指针,只拷贝8字节(64位)
void Print2(struct S* ps)
{
}
结构体尽量传指针,不要直接传整个结构体,性能差。
struct:成员独立内存,对齐;
位段属于 struct 的特殊写法,省 bit,但跨平台不稳定
union:成员共享同一块内存,大小看最大成员,用来判断大小端
enum:一组打包的整型常量,用来做选项、状态码
5.对齐规则
一、4 条核心对齐规则
对齐数 = min (编译器默认对齐数,成员自身大小,每个成员单独计算,互补影响)
结构体第一个成员,偏移量固定是 0,放在结构体起始地址。
其它成员:存放地址必须是自身对齐数的整数倍。
VS(MSVC)默认对齐数 = 8
Linux gcc:无默认对齐数,对齐数 = 成员本身大小
结构体总大小:必须等于该结构体最大对齐数的整数倍。
结构体嵌套结构体:
内嵌结构体对齐到【内嵌内部成员最大对齐数】的整数倍;
整个外层结构体总大小,是全体最大对齐数的整数倍。
二、#pragma pack (n) 语法
c
#pragma pack(n) // 设置默认对齐数为 n
// 结构体定义
#pragma pack() // 取消自定义对齐,恢复编译器默认值
作用:强制修改默认对齐数,压缩结构体内存,网络数据包、硬件寄存器场景高频使用。
三、示例代码
c
#pragma pack(2)
struct S
{
char c1; // 1字节,偏移0
int i; // 4字节,对齐数=min(2,4)=2,放在偏移2,占用2,3,4,5
char c2; // 1字节,偏移6
};
#pragma pack()
计算:
占用地址:0 (c1)、2~5 (i)、6 (c2),一共占用 7 字节。
最大对齐数 = 2,总大小必须是 2 的倍数,补 1 字节填充。
✅ sizeof(struct S) = 8
对比:不加 pack,VS 默认 8
c
struct S
{
char c1;
int i;
char c2;
};
c1 (0),填充 3 字节空洞;i 从 4 开始占 4 字节;c2 在 8。
最大对齐数 = 4,总大小补齐到 4 倍数。
✅ sizeof = 12
四、快速做题步骤(背这个流程)
确定当前默认对齐数(VS 默认 8 /pack (n) 就是 n /gcc 无默认)
逐个成员,算出每个成员对齐数 = min (默认对齐,成员大小)
从偏移 0 开始放每个成员,成员起始地址必须是对齐数整数倍,中间空的就是填充字节
记录当前结构体所有成员的最大对齐数
最后总大小向上取整,为【最大对齐数】的整数倍
对应规则套进楼层
房子(内存)大小固定不变,地基只有一套。
全屋整体装修(写 int):全部房间刷新,num 完整有效。窗口 ch 看到一楼房间新的样子。
只单独装修一楼(写 char):只改第一个房间,上层房间保留旧数据。此时 ch 有效;整套房子 (num) 是新旧装修混在一起,数据残缺,直接乱掉。
和结构体对比楼层
struct:独立多层公寓,每个成员单独一套房间,互不干扰,可以同时全部装修,全部完好。
union:单套房子,全屋装修或者局部房间翻新,新装修会覆盖旧的,同一时间只能保证一套完整装修。
位段:同一个房间内部,用隔板切出很多极小的迷你格子,在一个房间内细分 bit 存放标记。
一句话:
房子本体(内存)永远在。最后一次装修操作,决定哪部分是完好可用的;没被重新装修的区域,保留旧的二进制,跨成员读取就会出现数据错乱。
6.结构体/联合体的最大对齐数的选择
成员对齐数 = min (成员自身类型对齐数,pack 指定的默认对齐数)
但是!联合体 / 结构体的【最大对齐数】,是所有成员各自对齐数里面最大的那一个。
拿 Un2 举例(无 pack,默认 8 字节对齐,x64):
c
union Un2
{
short s[7]; // short自身对齐2 → min(2,8)=2
int i; // int自身对齐4 → min(4,8)=4
};
成员各自对齐数:2 和 4。
所以整个联合体的最大对齐数 = max (2,4) = 4,不是 2。
完整两条规则合并
单个成员的对齐数:min(该类型自身对齐数,pack默认对齐数)
结构体 / 联合体整体对齐要求:整体大小必须是【所有成员对齐数中最大值】的整数倍。
放到 Un2 计算
最大成员字节数:short [7] = 14
最大对齐数:4
14 不是 4 倍数,向上补到 16 → sizeof=16
如果加 #pragma pack (2)
c
#pragma pack(2)
union Un2
{
short s[7];
int i;
};
每个成员对齐:
short:min (2,2)=2
int:min (4,2)=2
最大对齐数变成 2。
最大成员 14,14 是 2 的倍数,sizeof=14。
一句话:
先算每一个成员的对齐数(取 min),再在这批数字里面挑最大的,作为整体对齐标准。
7.读取结构体的段位的写法
不是越位,核心原因:位段成员不能取地址 &sa._b
struct A
{
int _a :2;
int _b :5;
int _c :10;
int _d :30;
};
struct A sa = {0};
scanf(“%d”, &sa._b); // ❌ 报错!
&sa._b 想拿 _b 的内存地址,但是:
CPU 最小访问单位是字节(8bit),没办法单独定位某几个零散 bit 的地址。
_b 只是一个 int 里面切出来的 5 个 bit 碎片,没有独立地址,所以不能用&。
正确写法(视频里面的方案)
c
int b = 0;
scanf(“%d”, &b); // 普通int,可以取地址,读取输入
sa._b = b; // 再把值赋值给位段成员
先读到普通变量,再拷贝到位段里面。
顺带提醒一个坑
赋值的时候还要注意范围:int _b:5
如果是 signed int,5bit 范围是 -16 ~ 15。
如果你输入的数字超出这个范围,就溢出出错。
8.数组和结构体的区别
核心区分
数组(array)
相同元素的集合。访问靠下标,步长固定(元素大小)。
int arr[5];
语义:一串一模一样的数据。
结构体(struct)
不同元素或者相同元素的集合;成员有独立名字,偏移受内存对齐控制。
struct S { char c; int i; };
语义:一组含义不同的变量打包。
容易混淆的两个点
① 结构体里面可以放数组
struct S{
int arr[10]; // ✅ 结构体的成员是数组
};
数组是结构体的成员,不等于数组本身就是结构体。
② 数组里面可以放结构体
struct S arr[10]; // ✅ 数组的每一个元素都是struct S
数组元素是结构体,数组本体仍然是数组类型。
最本质一句话:
struct:异构集合(成员类型可以不一样,每个成员有名字)
数组:同构集合(全部元素类型完全相同,用下标访问)
二者是并列的两种复合类型,互不隶属。
本文深入讲解C语言中struct内存对齐规则、位段(bit-field)的定义与跨平台限制、union判断大小端的原理、enum作为类型安全常量的优势,以及结构体传参推荐指针方式。重点剖析#pragma pack对齐控制、联合体最大对齐数计算、位段成员不可取地址的原因及正确赋值方法,并对比数组与结构体在数据组织语义上的本质区别。
6677

被折叠的 条评论
为什么被折叠?



