news 2026/8/25 13:33:10

C语言:结构体进阶

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C语言:结构体进阶

文章目录

  • 前言
    • 本文旨在系统性地介绍C语言中结构体的进阶知识。
  • 一、结构体类型声明
    • 1.1、嵌套结构体类型
    • 1.2、结构体的自引用
    • 1.3、typedef简化结构体类型
    • 1.4、匿名结构体类型
      • 1.4.1、匿名结构体类型唯一
      • 1.4.2、匿名结构体作为成员
  • 二、结构体数组
  • 三、结构体内存对齐
    • 3.1、对齐规则
    • 3.2、存在内存对齐原因
    • 3.3、结构体设计技巧
    • 3.4、修改默认对齐数
  • 四、结构体的位段(位域)成员
    • 4.1、位段的内存分配
    • 4.2、位段的跨平台不确定因素
    • 4.3、位段使用的注意事项
    • 4.4、位段的特殊写法

前言

本文旨在系统性地介绍C语言中结构体的进阶知识。

一、结构体类型声明

1.1、嵌套结构体类型

在一个结构体中使用另一个结构体类型作为成员。
代码示例:

//学生有生日,生日包含年/月/日 → 把"生日"单独设计成一个结构体,再放进"学生"结构体里。// 第一步:定义日期结构体structDate{intyear;intmonth;intday;};// 第二步:在学生结构体中嵌套日期结构体structStudent{charname[20];structDatebirthday;// 嵌套charid[20];};intmain(){// 嵌套结构体的初始化structStudents={"aoyun",{2008,8,8},"2026010102"};// 成员的访问printf("⽣⽇:%d-%d-%d\n",s.birthday.year,s.birthday.month,s.birthday.day);return0;}

1.2、结构体的自引用

结构体内部包含一个指向同类型结构体的指针。
代码示例:

#include<stdio.h>#include<stdlib.h>// 定义链表节点(自引用结构体)structNode{intdata;structNode*next;// 指向下一个节点};intmain(){// 创建 3 个节点structNoden1,n2,n3;// 存储数据n1.data=1;n2.data=2;n3.data=3;// 连接成链表n1.next=&n2;// n1 指向 n2n2.next=&n3;// n2 指向 n3n3.next=NULL;// n3 是最后一个节点// 遍历链表并打印structNode*p=&n1;while(p!=NULL){printf("%d ",p->data);p=p->next;}// 输出:1 2 3return0;}

1.3、typedef简化结构体类型

写法1:定义结构体 + typedef 分开写
代码示例:

// 先定义结构体structStudent{charname[20];intage;};// 再用 typedef 起别名typedefstructStudentStudent;// 使用Student s1;// 等价于 struct Student s1;

写法2:定义结构体的同时使用 typedef
代码示例:

typedefstructStudent{charname[20];intage;}Student;// 使用Student s1;

1.4、匿名结构体类型

匿名结构体是指省略了结构体标签(tag) 的结构体类型。它没有名字,因此通常只能在定义时直接创建变量。
代码示例:

// 普通结构体(有名字)structX{inta;charb;floatc;};structXx;// 可以多次使用// 匿名结构体(没有名字)struct{inta;charb;floatc;}x;// 只能使用这一次

使用 typedef 重命名后,它就变成了一个可复用的类型,解决了匿名结构体的限制。
代码示例:

// 使用 typedef 给匿名结构体重命名typedefstruct{inta;charb;floatc;}X;// X 是新的类型名X x1;// 创建全局变量 x1(类型 X)X arr[20];// 创建数组X*p=&x1;// 类型匹配intmain(){X x2;// 创建局部变量 x2return0;}

1.4.1、匿名结构体类型唯一

即使两个匿名结构体的成员完全相同,编译器也视为不同类型,不能相互赋值。
代码示例:

struct{inta;charb;floatc;}x;// 创建变量 x,类型是“匿名结构体类型A”struct{inta;charb;floatc;}*p=&x;// 错误,类型A ≠ 类型B

1.4.2、匿名结构体作为成员

情况1:匿名结构体成员直接展开
代码示例:

structS{inta;charb;struct// 匿名结构体(未创建变量){floatc;intd;};};intmain(){structSs;s.a=10;s.c=3.5f;// 直接访问,成员 c 和 d 会“上浮”成为外层结构体的成员。s.d=100;return0;}

情况2:匿名结构体作为具名成员
代码示例:

structS{inta;charb;struct// 创建变量 x{floatc;intd;}x;};intmain(){structSs;s.a=10;s.x.c=3.5f;// 需要通过 x 访问s.x.d=100;return0;}

二、结构体数组

结构体数组就是元素类型为结构体的数组,用于存储多个结构体数据。
代码示例:

// 定义日期结构体structDate{intyear;intmonth;intday;};// 定义学生结构体(包含嵌套结构体)typedefstructStudent{charname[20];structDatebirthday;// 嵌套结构体charid[20];}Student;intmain(){// 结构体数组初始化Student stu_arr[3]={{"zhangsan",{2008,8,8},"2026010101"},{"lisi",{2009,1,10},"2026010102"},{"wangwu",{2009,3,20},"2026010103"}};// 遍历访问结构体数组for(inti=0;i<3;i++){printf("name:%s\tbirth:%d-%d-%d\tid:%s\n",stu_arr[i].name,stu_arr[i].birthday.year,stu_arr[i].birthday.month,stu_arr[i].birthday.day,stu_arr[i].id);}return0;}

三、结构体内存对齐

结构体内存对齐是指:编译器为了提升 CPU 访问内存的效率,会在结构体成员之间插入“填充字节”(padding),使得每个成员的起始地址都位于其“对齐边界”上。

内存对齐是用少量内存填充字节(空间),换取 CPU 一次读取完成(时间)。

3.1、对齐规则

规则1:结构体的第1个成员对齐到0偏移处(结构体起始位置)
规则2:从第2个成员开始,每个成员对齐到其对应的对齐数的整数倍偏移处,对齐数 = min(编译器默认对齐数, 成员大小)
规则3:结构体的总大小必须是最大对齐数(包含嵌套结构体的最大对齐数)的整数倍
规则4:嵌套的结构体类型成员对齐到其自身最大对齐数的整数倍偏移处

VS 默认对齐数固定为 8,Linux环境中GCC 没有默认对齐数(对齐数 = 成员自身大小)。

代码示例:

#include<stdio.h>structs1{doubled;// 大小 8,对齐数 = min(8, 8) = 8// 放在偏移 0(第1个成员,从0开始)charc;// 大小 1,对齐数 = min(8, 1) = 1// 放在偏移 8inti;// 大小 4,对齐数 = min(8, 4) = 4// 放在偏移 12// 当前已占:0~15(共 16 字节)// 最大对齐数 = max(8, 1, 4) = 8// 总大小必须是 8 的倍数 → 16 已是 8 的倍数};structs2{charc1;// 大小 1,对齐数 = min(8, 1) = 1// 放在偏移 0structs1s1_member;// 嵌套结构体,对齐数 = 自身最大对齐数 = 8// 放在偏移 8// s1_member 内部占 16 字节(偏移 8~23)doubled;// 大小 8,对齐数 = min(8, 8) = 8// 放在偏移 24,占 8 字节(24~31)// 当前已占:0~31(共 32 字节)// 最大对齐数 = max(1, 8, 8) = 8// 总大小必须是 8 的倍数 → 32 已是 8 的倍数};intmain(){structs1s1={0};structs2s2={0};printf("sizeof(struct s1) = %zu\n",sizeof(structs1));// 16printf("sizeof(struct s2) = %zu\n",sizeof(structs2));// 32return0;}

3.2、存在内存对齐原因

平台原因(移植性):某些 CPU 只能从特定地址读取特定类型的数据,否则会抛出硬件异常
性能原因(效率):CPU 以总线宽度(4/8 字节)为粒度发起内存访问,对齐的数据一次读完,未对齐的数据需要多次读取+拼接

概念定义决定者作用
CPU总线宽度CPU与内存之间一次传输的数据位数硬件决定 CPU 能一次读多少数据
默认对齐数编译器设定的对齐上限编译器决定结构体成员最大按多少字节对齐

默认对齐数 ≤ CPU总线宽度,这样才能保证对齐后的数据不会超过 CPU 一次读取的能力。

3.3、结构体设计技巧

将占用空间小的成员集中放在一起,可以有效减少填充字节,节省内存。

核心原理:char 类型对齐数为 1,可以放在任意偏移位置,不会产生填充。将多个 char 放在一起,它们会连续排列,共用同一个填充“间隙”。
代码示例:

#include<stdio.h>structS1{charc1;// 偏移 0inti;// 偏移 4(对齐数4,偏移1~3填充)charc2;// 偏移 8};// 总大小:12 字节(最大对齐数4,12是4的倍数,9→12)structS2{charc1;// 偏移 0charc2;// 偏移 1(对齐数1,紧挨着)inti;// 偏移 4(对齐数4,偏移2~3填充)};// 总大小:8 字节(最大对齐数4,8是4的倍数)

3.4、修改默认对齐数

#pragma pack(n) 可以临时修改默认对齐数,控制结构体的紧凑程度。pack(1) 消除填充节省空间,但可能降低访问速度;用完后及时 #pragma pack() 恢复默认。

代码示例:

#include<stdio.h>#pragmapack(1)// 设置默认对齐数为 1structS{charc1;// 对齐数 = min(1, 1) = 1inti;// 对齐数 = min(1, 4) = 1charc2;// 对齐数 = min(1, 1) = 1};#pragmapack()// 取消设置,恢复默认对齐数intmain(){printf("%zu\n",sizeof(structS));// 6(1+4+1,无填充)return0;}

四、结构体的位段(位域)成员

在结构体定义中,如果成员名后面加上一个冒号和数字,就称为位段成员。这个数字表示该成员占用的比特位数,而不是字节数。

C语言标准规定:C89/C90:位段成员的类型必须是 int、unsigned int 或 signed int。C99:新增支持 _Bool 类型。

现代编译器的扩展,大多数编译器(GCC、Clang、MSVC)在实际实现中放宽了类型限制,额外支持:char、unsigned char、short、unsigned short、long、unsigned long、long long、unsigned long long、枚举类型(enum)。
注意:这些扩展支持取决于具体编译器,不是C标准强制要求的。如果追求可移植性,建议只使用标准规定的类型。

代码示例:

structA{unsignedinta:2;// 占用2位unsignedintb:5;// 占用5位unsignedintc:10;// 占用10位unsignedintd:30;// 占用30位};

4.1、位段的内存分配

①成员类型:位段成员可以是 int、unsigned int、signed int 或 char 等类型(现代编译器通常支持更丰富的类型)。

②按类型申请空间:成员为 unsigned int / signed int 时,每次按 4 字节申请内存空间。成员为 unsigned char / signed char 时,每次按 1 字节申请内存空间。按需扩展,当前单元不够用时申请下一个单元。

③实现依赖:位段对内存的使用细节(填充、跨单元、顺序等)高度依赖编译器实现,C标准未作严格规定。

代码示例:

//分配方向:从低位到高位分配// 剩余位策略:当前单元剩余位不够时,舍弃填充,申请新单元#include<stdio.h>// 定义位段结构体// 成员类型为 unsigned char → 每次按 1 字节(8位)申请空间structS{unsignedchara:3;// a 占用 3 位(bit0~bit2)unsignedcharb:4;// b 占用 4 位(bit3~bit6),当前字节剩余5位,足够存放unsignedcharc:5;// c 占用 5 位,当前字节只剩1位(bit7),不够// → 申请第2个字节,c 从新字节的 bit0 开始存放// → 第1字节剩余 bit7 被填充废弃unsignedchard:4;// d 占用 4 位,当前字节(第2字节)剩余3位(bit3~bit5),不够// → 申请第3个字节,d 从新字节的 bit0 开始存放// → 第2字节剩余 bit3~bit7 被填充废弃};// 总大小 = 3 字节intmain(){structSs={0};// 将所有字节初始化为 0// 赋值注意:值不能超过位段能容纳的范围// a 占 3 位,最大能存 2^3 - 1 = 7s.a=10;// 10 = 二进制 1010,但 a 只有 3 位 → 截断为 010(即 2)// b 占 4 位,最大能存 2^4 - 1 = 15s.b=12;// 12 = 二进制 1100,完整存入 b 的 4 位// c 占 5 位,最大能存 2^5 - 1 = 31s.c=3;// 3 = 二进制 00011,完整存入 c 的 5 位// d 占 4 位,最大能存 2^4 - 1 = 15s.d=4;// 4 = 二进制 0100,完整存入 d 的 4 位return0;}

4.2、位段的跨平台不确定因素

不确定因素说明影响层面
符号性int 位段被当作有符号还是无符号,由编译器决定语义(数据解释)
最大位数16位机器最大16位,32位机器最大32位,写成27在16位机器上会出问题语义(数据范围)
分配顺序从高位还是低位开始分配,标准未定义布局(顺序)
剩余位处理当前单元剩余位不够时,是填充废弃还是跨单元利用,标准未定义布局(填充/跨单元)

注:①填充:剩余位被废弃不用,称为填充。②跨单元:剩余位不够时,编译器可选择将成员放入下一个单元(舍弃剩余位),也可选择跨单元存储(部分在当前单元,部分在下一个单元)。③顺序:小端系统从低位(bit0)开始分配,大端系统从高位(bit7)开始分配。

位段能节省内存,但存在大量实现定义(编译器必须在文档中说明)行为,跨平台性差。注重可移植性的程序应避免使用位段,或针对平台做专门处理。

4.3、位段使用的注意事项

①sizeof 返回的是字节数,而位段成员可能只占几个 bit,不够 1 个字节。C 标准规定 sizeof 不能作用于位段成员。

②内存中每个字节分配一个地址,而字节内部的 bit 位没有独立地址。位段成员可能从某个字节的中间位置开始,没有对应的地址,因此不能使用 & 操作符。

代码示例:

structA{unsignedint_a:2;unsignedint_b:5;unsignedint_c:10;unsignedint_d:30;};intmain(){structAsa={0};// printf("%zu\n", sizeof(sa._a)); // 错误,位段成员不能作为 sizeof 的操作数// scanf("%d", &sa._b); // 错误,不能对位段成员取地址//正确,先读入普通变量,再赋值给位段成员intb=0;scanf("%d",&b);sa._b=b;return0;}

4.4、位段的特殊写法

① 无名位段(跳过指定位数)
作用:在当前存储单元中跳过指定位数,不分配给任何成员。跳过的位被填充废弃,且无法被程序访问。

代码示例:

structExample{unsignedchara:4;// 占低4位unsignedchar:4;// 匿名位段,跳过4位(不命名,无法访问)unsignedcharb:8;// 从下一个字节开始};

② 零宽度位段(强制对齐到下一个存储单元)
作用:告诉编译器当前存储单元剩余位全部废弃,下一个成员从新的存储单元起始位置开始分配。

代码示例:

structExample2{unsignedchara:4;// 占当前字节的低4位unsignedchar:0;// 零宽度位段:强制结束当前存储单元unsignedcharb:1;// 从新存储单元(下一个字节)开始unsignedcharc:8;// 再占1个字节};
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/25 13:32:24

React Hooks 为什么不能在条件分支/循环中调用?

React Hooks 为什么不能在条件分支/循环中调用&#xff1f;一、核心思路&#xff08;一句话&#xff09;React Hooks 通过"调用顺序&#xff08;位置索引&#xff09;"在 Fiber 节点的单向链表上匹配状态&#xff0c;而非通过名称或 key——任何改变调用顺序的写法都…

作者头像 李华
网站建设 2026/8/25 13:32:21

python学习笔记4 --Python的容器类型

第5章 Python的容器类型 5.1 容器概述 Python 提供了多种内置的容器类型,用于存储和组织数据。 内置的主要容器类型有: –列表list、元组tuple、字符串str、集合set、字典dict等, 另外Python还在collections模块中提供了更多容器类型,例如:双端队列deque等。 5.1.1 常见…

作者头像 李华
网站建设 2026/8/25 13:15:16

rocketMQ proxy架构分析

介绍proxy 模块是 RocketMQ 5.x 的 无状态代理组件 &#xff0c;核心思路是&#xff1a;对外提供 gRPC 协议&#xff08;面向多语言客户端&#xff09;&#xff0c;对内把请求翻译成 Remoting 协议访问 Broker/Namesrv&#xff0c;并原生支持 pop 消费模型。它有两种部署模式&a…

作者头像 李华
网站建设 2026/8/25 13:10:44

商业计划书最容易被忽略的加分项:清晰的图表表达

你好&#xff0c;我是小索奇。 本文围绕“商业计划书最容易被忽略的加分项&#xff1a;清晰的图表表达”展开&#xff0c;重点说明如何用流程图、结构图和数据图提升内容表达效率。本文重点&#xff1a;明确问题、拆分逻辑、选择图表类型&#xff0c;并用清晰段落帮助读者快速获…

作者头像 李华
网站建设 2026/8/25 13:10:24

2026年靠谱冠层光合工具哪家好?这份实用测评指南帮你甄选

凌晨5点的麦田里&#xff0c;你还在扛着几十斤的同化箱蹲点测光合&#xff1f;好不容易密封好&#xff0c;一阵风刮来数据全乱&#xff1b;折腾一天测10个样本&#xff0c;数据还没法代表整个冠层的真实光合效率&#xff1f;2026年做冠层光合研究&#xff0c;选对工具能让你少走…

作者头像 李华
网站建设 2026/8/25 13:06:21

8英寸产能被AI挤占 2026年“选芯“逻辑正在重写

一、三重挤压&#xff1a;一颗MCU的涨价逻辑 如果说2025年的关键词是"复苏"&#xff0c;那么2026年上半年&#xff0c;消费电子行业的主旋律只有两个字——成本。 从去年第四季度开始&#xff0c;晶圆代工产能告急&#xff0c;MCU芯片全线涨价5%15%。紧接着&#xff…

作者头像 李华