C 语言结构体进阶指南:从基础定义到内存对齐与位段实战
前言说到结构体我们首先来回忆一下之前学的数组数组的作用是来存储一批类型相同的数据比较intfloat等但我们发现数组只能存储类型相同的数据有时候我们需要存储不同类型的数据比如存储一个学生需要用到的类型有年龄姓名、学号、成绩等C语言为了解决这个问题增加了结构体这种自定义的数据类型让程序员可以自己创造适合的类型。结构是一些值的集合这些值称为成员变量。结构的每个成员可以是不同类型的变量如标量、数组、指针甚至是其他结构体。目录前言1.结构体类型和结构体变量1.1结构体声明1.2结构体特殊声明1.3 结构体自引用1.4 结构体变量的定义和初始化1.5 结构体成员访问操作符2.结构体内存对齐2.1对齐规则2.2为什么存在内存对齐2.3修改默认对齐数3.结构体传参4.结构体实现位段4.1什么是位段4.2位段的内存分配4.3位段的跨平台问题4.4位段的应用4.5位段使用的注意事项1.结构体类型和结构体变量1.1结构体声明structtag//标签{member-list;//结构体成员有1个或多个}variable-list;//可有可无分号(;)必须有struct是用于定义具体结构体类型的关键字tag是结构体标签member-list结构体成员有1个或多个variable-list结构体变量名可有可无分号(;)必须有例描述一个学生structStu{charname[20];//名字intage;//年龄charsex[5];//性别charid[20];//学号};//分号不能丢要注意的是结构体类型名如上例中的Stu是定义的类型名而不是变量名就好像整型的类型名为int双精度实型的类型名为double字符型的类型名为char一样只不过整型、双精度实型、字符型等基本数据类型是C编译系统已经定义的用户可以直接用它们来定义相应类型的变量而结构体类型是用户根据数据处理的需要临时定义的一种类型一经定义就可以像系统定义的类型一样使用了。但系统并不为类型分配存储空间为了能在程序中使用结构体类型的数据必须定义结构体类型的变量。1.2结构体特殊声明在声明结构的时候可以不完全的声明。//匿名结构体类型只能使用一次后续不能使用这个类型创建变量//(假设后续你要创建但你不知道类型名所以不能使用这个类型创建变量struct{inta;charb;floatc;}x;struct{inta;charb;floatc;}a[20],*p;//a[20]结构体数组变量p存放的是结构体的地址上面的两个结构在声明的时候省略掉了结构体标签tag。那么问题来了//在上⾯代码的基础上下⾯的代码合法吗px;警告编译器会把上面的两个声明当成完全不同的两个类型所以是非法的。p的类型是struct 匿名的第二个结构体 *x的类型是struct 匿名的第一个结构体 *二者不能隐式转换也不能直接赋值。匿名的结构体类型如果没有对结构体类型重命名的话基本上只能使用⼀次。1.3 结构体自引用在结构中包含⼀个类型为该结构本身的成员是否可以呢比如定义⼀个链表的节点structNode{intdata;structNodenext;//结构体内创建结构体无限死循环};上述代码正确吗如果正确那sizeof(struct Node)是多少仔细分析其实是不行的因为⼀个结构体中再包含⼀个同类型的结构体变量这样结构体变量的大小就会无穷的大是不合理的。正确的自引用方式structNode{intdata;structNode*next;};在结构体自引用使用的过程中夹杂了typedef对匿名结构体类型重命名也容易引入问题看看下面的代码可行吗typedefstruct{intdata;Node*next;}Node;答案是不行的原因在于typedef定义的新类型名Node在结构体定义结束之后才生效而你在结构体内部已经试图使用Node* next;此时编译器还不认识Node。注如果在 C 代码上面的错误写法反而是合法的因为 C 中类名在类体内会立即生效。但在 C 语言中必须遵守上述规则。解决方案如下定义结构体不要使用匿名结构体了typedefstructNode{intdata;structNode*next;}Node;1.4 结构体变量的定义和初始化结构体变量的定义(1) 先声明结构体类型再定义变量名// 声明结构体类型带有标签 studentstructstudent{charname[20];intage;floatscore;};// 之后使用 struct student 定义变量structstudentstu1;(2) 在声明类型的同时定义变量// 声明结构体类型带有标签 studentstructstudent{charname[20];intage;floatscore;}stu1stu2*p;(3) 不指定类型名而直接定义结构体类型变量// 声明结构体类型带有标签 studentstruct{charname[20];intage;floatscore;}stu1stu2;结构体变量的初始化(1)直接初始化structstudentstu1{“彭于晏”,20,89.5f};(2)指定成员初始化C99标准structstudentstu1{.age22,.name彭于晏,.score89.5f};1.5 结构体成员访问操作符结构体成员的直接访问结构体成员的直接访问是通过点操作符.访问的。点操作符接受两个操作数。如下所示#includestdio.hstructPoint{intx;inty;}p{1,2};//结构体变量并赋值为12intmain(){printf(x:%d y:%d\n,p.x,p.y);//通过点.访问return0;}使用方式结构体变量名.成员名结构体成员的间接访问有时候我们得到的不是⼀个结构体变量⽽是得到了⼀个指向结构体的指针。如下所示#includestdio.hstructPoint{intx;inty;};intmain(){structPointp{3,4};structPoint*ptrp;ptr-x10;//通过指针指向x并赋值ptr-y20;printf(x %d y %d\n,ptr-x,ptr-y);return0;}使用方式结构体指针-成员名综合举例#includestdio.h#includestring.hstructStu{charname[20];intage;};//通过点(.)打印内容voidprint_stu(structStus){printf(%s %d\n,s.name,s.age);}//通过-来访问结构体voidset_stu(structStu*ps){strcpy(ps-name,lisi);ps-age30;}intmain(){structStus{zhangsan,20};//为struct Stu进行初始化print_stu(s);set_stu(s);print_stu(s);return0;}2.结构体内存对齐接下来我们在讨论⼀个问题计算结构体的大小。这也是⼀个特别热门的考点结构体内存对⻬内存对齐的根本原因在于优化计算机硬件特别是CPU访问内存的效率并确保跨硬件平台的兼容性。2.1对齐规则⾸先得掌握结构体的对⻬规则结构体的第1个成员对⻬到和结构体变量起始位置偏移量为0的地址处。从第2个成员变量开始都要对⻬到某个对⻬数的整数倍的地址处。-对⻬数 编译器默认的⼀个对⻬数 与 该成员变量⼤⼩的较⼩值。- VS 中默认的值为 8- Linux中 gcc 没有默认对⻬数对⻬数就是成员⾃⾝的⼤⼩结构体总⼤⼩为最⼤对⻬数结构体中每个成员变量都有⼀个对⻬数所有对⻬数中最⼤的的整数倍。如果嵌套了结构体的情况嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处结构体的整体⼤⼩就是所有最⼤对⻬数含嵌套结构体中成员的对⻬数的整数倍。在Linux中gcc环境下1.成员按照自身的对齐值对齐如果不在对齐边界编译器会再加上填充字节2.结构体的总大小为结构体最宽基本类型成员大小的整数倍如有需要编译器会在最末一个成员之后加上填充字节trailing padding代码#includestdio.h//求结构体的对齐数intmain(){//练习1structS1{charc1;//1 8 1inti;//4 8 4charc2;//1 8 1};printf(%zu\n,sizeof(structS1));//12//练习2structS2{charc1;//1 8 1charc2;//1 8 1inti;//4 8 4};printf(%zu\n,sizeof(structS2));//8//练习3structS3{doubled;//8 8 8charc;//1 8 1inti;//4 8 4};printf(%zu\n,sizeof(structS3));//16//练习4-结构体嵌套问题structS4{charc1;//1 8 1structS3s3;//8 8 8doubled;//8 8 8};printf(%zu\n,sizeof(structS4));//32return0;}示例图如struct S12.2为什么存在内存对齐大部分参考资料解释如下1、平台原因 (移植原因)不是所有的硬件平台都能访问任意地址上的任意数据的某些硬件平台只能在某些地址处取某些特定类型的数据否则抛出硬件异常。2、性能原因数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于为了访问未对⻬的内存处理器需要作两次内存访问⽽对齐的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对齐成8的倍数那么就可以⽤⼀个内存操作来读或者写值了。否则我们可能需要执行两次内存访问因为对象可能被分放在两个8字节内存块中。总体来说结构体的内存对齐是拿空间来换取时间的做法。那在设计结构体的时候我们既要满足齐又要节省空间如何做到?答让占用空间小的成员尽量集中在⼀起。例//例如structS1{charc1;inti;charc2;};structS2{charc1;charc2;inti;};};S1和S2类型的成员⼀模⼀样但是S1和 S2 所占空间的⼤⼩有了⼀些区别。2.3修改默认对齐数注在vs中可以修改在Linux中没有对齐数#includestdio.h#pragmapack(1)//设置默认对⻬数为1structS{charc1;inti;charc2;};#pragmapack()//取消设置的对⻬数还原为默认intmain(){//输出的结果是什么printf(%d\n,sizeof(structS));return0;}结构体在对齐方式不合适的时候我们可以自己更改默认对齐数。3.结构体传参我们首先来看一段代码#includestdio.hstructS{intdata[1000];intnum;};structSs{{1,2,3,4},1000};//结构体传参voidprint1(structSs){printf(%d\n,s.num);}//结构体地址传参voidprint2(structS*s){printf(%d\n,s-num);}intmain(){print1(s);//传结构体print2(s);//传地址return0;}上⾯的print1和print2函数哪个好些答案首选print2函数函数传参的时候参数是需要压栈会有时间和空间上的系统开销。如果传递⼀个结构体对象的时候结构体过大参数压栈的的系统开销比较大所以会导致性能的下降。结论结构体传参的时候要传结构体的地址。4.结构体实现位段4.1什么是位段位段的声明和结构是类似的有两个不同位段的成员必须是 int、unsigned int 或signed int 在C99中位段成员的类型也可以选择其他整型家族类型⽐如char。位段的成员名后边有⼀个冒号和⼀个数字。比如structA{int_a:2;int_b:5;int_c:10;int_d:30;};A就是一个位段类型。那位段A所占内存的大小是多少printf(%d\n,sizeof(structA));//输出84.2位段的内存分配①位段的成员可以是intunsignedintsigned int或者是char等类型②位段的空间上是按照需要以4个字节 int 或者1个字节 char 的⽅式来开辟的。③位段涉及很多不确定因素位段是不跨平台的注重可移植的程序应该避免使用位段。//⼀个例⼦structS{chara:3;charb:4;charc:5;chard:4;};structSs{0};s.a10;s.b12;s.c3;s.d4;//空间是如何开辟的//输出printf(%d\n,sizeof(structS));//3示例图4.3位段的跨平台问题①int 位段被当成有符号数还是无符号数是不确定的。②位段中最大位的数目不能确定。16位机器最⼤1632位机器最⼤32写成27在16位机器会出问题。③位段中的成员在内存中从左向右分配还是从右向左分配标准尚未定义。④当⼀个结构包含两个位段第⼆个位段成员⽐较⼤⽆法容纳于第⼀个位段剩余的位时是舍弃剩余的位还是利⽤这是不确定的。总结跟结构相比位段可以达到同样的效果并且可以很好的节省空间但是有跨平台的问题存在。4.4位段的应用下图是网络协议中IP数据报的格式我们可以看到其中很多的属性只需要几个bit位就能描述这里使用位段能够实现想要的效果也节省了空间这样网络传输的数据报大小也会较小⼀些对网络的畅通是有帮助的。4.5位段使用的注意事项位段的几个成员共有同⼀个字节这样有些成员的起始位置并不是某个字节的起始位置那么这些位置处是没有地址的。内存中每个字节分配⼀个地址⼀个字节内部的bit位是没有地址的。所以不能对位段的成员使⽤操作符这样就不能使用scanf直接给位段的成员输⼊值只能是先输入放在⼀个变量中然后赋值给位段的成员。structA{int_a:2;int_b:5;int_c:10;int_d:30;};intmain(){structAsa{0};scanf(%d,sa._b);//这是错误的//正确的⽰范intb0;scanf(%d,b);sa._bb;return0;}总结位段就是让你按“位”来定义和使用内存的一种工具主要好处是省内存、方便位操作特别适合硬件寄存器、协议头解析和标志位打包。代价是牺牲了一些可移植性且不能取地址。在现代 C 编程中当需要精细控制某几个位时位段仍是一种有效的选择。最后本次结构体到这里就结束了有任何不懂的地方欢迎评论区一起讨论。路虽远行则将至。保持持续学习咱们下篇内容再会

相关新闻

TwinCAT3 TCP/IP自由协议通讯:从原理到工程实践

TwinCAT3 TCP/IP自由协议通讯:从原理到工程实践

1. 项目缘起:当标准协议不够用的时候在工业自动化领域,Beckhoff的TwinCAT3平台以其强大的实时性和开放性,成为了许多复杂、高性能控制系统的首选。我们常常用它来集成伺服驱动器、IO模块、机器人,甚至是第三方设备。大多数时候&am…

2026/9/24 21:26:35 阅读更多 →
基于腾讯云WorkBuddy低成本部署OpenClaw AI智能体实战指南

基于腾讯云WorkBuddy低成本部署OpenClaw AI智能体实战指南

1. 项目概述:当OpenClaw遇上腾讯云WorkBuddy 最近在AI圈子里,OpenClaw和腾讯云WorkBuddy这两个词的热度有点高。很多朋友,尤其是个人开发者和技术爱好者,都在讨论怎么把OpenClaw这个听起来很“高大上”的AI框架,用更简…

2026/9/23 12:15:36 阅读更多 →
2019款iMac硬件升级指南:SSD、内存与双系统安装全解析

2019款iMac硬件升级指南:SSD、内存与双系统安装全解析

1. 项目概述:一次全面的iMac硬件升级与系统重构手头这台2019款的21.5寸iMac,用了几年后,感觉越来越力不从心了。开机要等半天,打开几个设计软件就开始转彩球,存储空间也频频告急。相信很多同款用户都有类似的困扰。这台…

2026/9/25 15:21:41 阅读更多 →

最新新闻

AI Agent标准架构拆解:用TaoToken统一Key打通LLM与Tools的Loop

AI Agent标准架构拆解:用TaoToken统一Key打通LLM与Tools的Loop

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/26 16:41:45 阅读更多 →
Claude Code新手实战:用TaoToken统一Key蒸馏出“叶金荣”Skill的完整配置

Claude Code新手实战:用TaoToken统一Key蒸馏出“叶金荣”Skill的完整配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/26 16:41:45 阅读更多 →
支持Function Call的本地ollama模型对比评测:开发代理agent的配置与验证

支持Function Call的本地ollama模型对比评测:开发代理agent的配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/26 16:41:45 阅读更多 →
自建CRM系统实战:从Docker部署到团队落地全流程复盘

自建CRM系统实战:从Docker部署到团队落地全流程复盘

客户信息分散在微信聊天、邮件、Excel表格和个人便签里,需要回看半年前的沟通记录时,得来回切换四五个窗口,最后仍然拼不出完整过程——这是我决定认真部署一套CRM系统的直接导火索。DeskcommCRM 是我近期从选型、部署到逐步推广给团队使用的…

2026/9/26 16:41:44 阅读更多 →
AI导航与语义SLAM技术进展:TaoToken统一Key接入ROS2 Nav2的配置与验证

AI导航与语义SLAM技术进展:TaoToken统一Key接入ROS2 Nav2的配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/26 16:41:44 阅读更多 →
代码阅读工作流实战:用 TaoToken 统一 Key 打通文件搜索、符号跳转与提问策略

代码阅读工作流实战:用 TaoToken 统一 Key 打通文件搜索、符号跳转与提问策略

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/26 16:40:44 阅读更多 →

日新闻

数据库课后习题答案别硬背:当测试用例集刷,效率翻倍

数据库课后习题答案别硬背:当测试用例集刷,效率翻倍

简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、…

2026/9/26 0:00:25 阅读更多 →
学校官网模拟全流程实践:从页面布局到后端接口与部署

学校官网模拟全流程实践:从页面布局到后端接口与部署

如果你正在找一门 Web 大作业的题目,或者刚开始接触 Web 前端开发想做点能拿来展示的东西,“学校官网模拟”几乎是最稳的选择。题目看着简单,但要把导航、新闻列表、轮播 Banner、二级页面、后台数据都串起来,其实已经把前端布局、…

2026/9/26 0:00:25 阅读更多 →
超级玛丽游戏源码C++:从零搭建横版跳跃游戏工程

超级玛丽游戏源码C++:从零搭建横版跳跃游戏工程

简介:这是一份面向游戏开发初学者与C进阶学习者的超级玛丽(超级马里奥)游戏源码,基于C面向对象编程实现,适合想通过经典项目理解游戏主循环、角色类设计、地图关卡加载与物理碰撞检测的读者参考。压缩包共49个文件&…

2026/9/26 0:00:25 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/25 19:27:14 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/25 11:15:26 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/25 20:29:09 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/25 20:29:43 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/25 20:29:31 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/25 19:27:26 阅读更多 →