结构体

news/2025/10/20 10:09:50/文章来源:https://www.cnblogs.com/janio/p/19144073

> 结构体:从声明、初始化、指针数组到零长数组、地址对齐、位段、可移植性、占位符附堆结构体数组抽卡 Demo + 对齐示意图


一、结构体基础:声明·定义·初始化

基本概念

C语言提供了众多的基本类型,但现实生活中的对象一般都不是单纯的整型、浮点型或字符串,而是这些基本类型的综合体。比如一个学生,典型地应该拥有学号(整型)、姓名(字符串)、分数(浮点型)、性别(枚举)等不同侧面的属性,这些所有的属性都不应该被拆分开来,而是应该组成一个整体,代表一个完整的学生。

在C语言中,可以使用结构体来将多种不同的数据类型组装起来,形成某种现实意义的自定义的变量类型。结构体本质上是一种自定义类型。

基础操作

① 声明(不产生内存)

向编译器系统明确我们自己定义的数据类型的模版,声明语句中不会产生任何的内存分配和释放。
(只是告诉编译器,自定义类型的样子,并不需要实际申请空间)
语法:

  • 声明语句一般写于函数体外(与全局变量位置相当)
  • struct 是结构体声明的关键字
  • 结构体标签 用于区分不同的结构体
  • { } 内部 结构体成员 , 可以是任何类型的数据,甚至也可以是一个结构体(嵌套)
  • 结构体的声明语句结尾必须加 ; 分号
struct 结构体标签
{成员1;  // 每一个成员之间使用分号隔开成员2;...
};  // 结构体的声明语句结尾必须加 ; 分号实例:
// 声明结构体类型
//(并不是定义结构体变量,不产生内存空间)
struct Cat
{char * Color ;char * Name ;float Weight;float Long ;int Age ;//.......///.........
};

定义

当我们声明了一个结构体变量类型后,便可以把他在内存内存中定义出来,可以是堆,也可以是栈,也可是静态数据。

// 全局的结构体变量
struct Cat Tom1 ;int main(int argc, char const *argv[])
{// 局部的结构体变量struct Cat Tom2;// 在堆中申请结构体变量的内存空间struct Cat * Ptr = malloc( sizeof(struct Cat) );return 0;
}

初始化

结构体的初始化有两种方案

  • 顺序初始化类似数组的元素初始化
// 顺序初始化(必须从头到尾依次初始化,中间不能留空)
struct Cat Tom3 = { "Red" , "Tom" , 19.34 , 60.45 , 380    
};
  • 指定成员初始化【推荐】
// 指定成员初始化 
// (没有顺序要求,可以任意初始化已知属性,未知的可以忽略)
struct Cat Tom4 ={.Name = "Tom",.Age = 498 ,.Color = "Blue",
};

使用(访问)

访问结构体的成员也有两种方式:

  • 对于普通结构体变量的访问可以使用.作为成员引用符
Tom4.Weight = 78.56;
Tom4.Long = 1.76 ;
  • 对于结构体指针变量的访问可以使用 -> 作为成员引用符
void showCat( const struct Cat * cat )
{// -> 成员引用符号(指针)printf("Name:%s\n" , cat->Name) ;printf("Color:%s\n" , cat->Color) ;printf("Weight:%f\n" , cat->Weight) ;printf("Long:%f\n" , cat->Long) ;printf("Age:%d\n" , cat->Age) ;
}

结构体数组

概念: 它是一个数组,每一个元素都是结构体类
语法:

// 声明结构体类型
struct Stud
{int Num ;char * Name ;int Age ;
};// 定义但未初始化
struct Stud arr [3] ;// 定义并初始化一部分数据
struct Stud arr1 [3] = {{ 123 , "Even" , 25 }, // 顺序初始化{ .Age=18 , .Name="erGou" , .Num=321 }, // 指定成员初始化
};
结构体数组

对于以上问题的解决:
可以把结构体中的指针替换成数组,因此当我们申请结构体变量时,期内部的数组内存会一并分配,不需要使用堆内存,使用堆内存时需要添加额外管理代码。

// 声明结构体类型
struct Stud
{int Num ;char Name [32];int Age ;
};


结构体指针

概念: 它是一个指针,指向的数据类型为结构体。
语法:

struct stud * ptr ;// ptr 指向了结构体数组的第一个元素的入口地址
struct Stud * ptr = arr1 ;
printf("Name:%s\n" , ptr->Name) ;
printf("Age:%d\n" , ptr->Age) ;
printf("Num:%d\n" , ptr->Num) ;


结构体指针数组

概念: 它是一个数组,该数组中存放了多个结构体类型的指针。
语法:

struct Stud * arr[5]  ;


结构体数组指针

概念: 它是一个指针,该指针指向的是一个数组,而数组中每一个元素都是结构体。
语法:

struct Stud  (*ptr) [5] ;


结构体指针数组指针

概念: 它是一个指针,指向的是一个数组,数组中每一个元素都是指向结构体类型的指针。
语法:

struct Stud * (*ptr) [5] ;


二、结构体声明语句的变形

变形1: 声明时顺便定义变量

struct Node
{int Num ;char Name [32];
} Even , *Ptr  ;
// 在生声明结构体类型时,顺便定义了两个变量
//  Even普通结构体变量 
// *Ptr 结构体的指针变量

变形2: 省略标签
这种写法并不多见,可以用于进制定义新的结构体类型,一般用于结构体内部嵌套的小结构体。

// 由于省略的结构体的标签
// 因此该结构体无法单独定义任何变量
// 只允许在声明的同时定义变量
struct 
{int Num ;char Name [32];
} Even , *Ptr  ;
// 在生声明结构体类型时,顺便定义了两个变量
//  Even普通结构体变量 
// *Ptr 结构体的指针变量// 在结构体内部嵌套一个小结构体时
// 如果不希望该小结构体被用户单独定义变量
// 可以把他的标签省略
// 在声明时直接定义变量
struct Stud
{int Num ;char Name [32];// 嵌套在大结构体内部的一个属性变量// 该书属性变量无法被单独定义struct {float Socre ;int Type ;char Msg[16];} Info ;};

变形3: 给类型取别名【常见用法】

// typedef用于给某一个类型取别名
typedef struct Node
{int Num ;char Name [32];
} Node_t , *P_Node_t  ;
// Node_t 是 struct Node 的别名
// struct Node Even ; 等价于  Node_t Even ;
// P_Node_t 是 struct Node * 的别名
// struct Node *  p ; 等价于 P_Node_t p ;// 以下语句中虽然没有了结构体的标签
// 但是有取别名,因此依然可以使用别名进行定义变量
typedef struct 
{int Num ;char Name [32];
} Test_t , *P_Test_t  ;

三、零长数组

概念: 长度为0的数组,可以把他放置于结构体的最后一个元素,并在给结构体申请堆内存空间时多分配一些,用于存储额外信息。
示例:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>struct Stud
{int Num;char Name [32];// 使用小结构体来前调某一部分数据是一个整体struct {int Len ; // 用于记录数组MSG的实际长度char Msg [0] ;// 在结构体的末尾定义一个零长度的数组// 因此零长数组可以用于兼顾特殊性以及普适性}Info;
};int main(int argc, char const *argv[])
{// 申请内存空间时,可以给结构体多申请一些空间// 用于存储特殊的描述信息struct Stud * ptr = malloc( sizeof(struct Stud) + 16 );ptr->Info.Len = 16 ; // 马上使用Len来标记多得到的内存大小,避免后期访问造成越界printf("零长数组的大小:%ld\n" , sizeof(ptr->Info.Msg) ) ;strncpy( ptr->Info.Msg , "零长数组" , ptr->Info.Len);ptr->Num = 444 ;strncpy( ptr->Name , "Even" , sizeof(ptr->Name) );printf("%s %d %s\n" , ptr->Name ,ptr->Num , ptr->Info.Msg) ;int arr [10];arr[0] = 123 ;*(arr+0) = 345 ;ptr->Num = 123 ;(*ptr).Num = 456 ;return 0;
}

四、结构体的尺寸

①CPU字长

字长的概念指的是处理器在一条指令中的数据处理能力,当然这个能力还需要搭配操作系统的设定,比如常见的32位系统、64位系统,指的是在此系统环境下,处理器一次存储处理的数据可以达32位或64位

处理器处理数据

②地址对齐

CPU字长确定之后,相当于明确了系统每次存取内存数据时的边界,以32位系统为例,32位意味着CPU每次存取都以4字节为边界,因此每4字节可以认为是CPU存取内存数据的一个单元。
如果存取的数据刚好落在所需单元数之内,那么我们就说这个数据的地址是对齐的,如果存取的数据跨越了边界,使用了超过所需单元的字节,那么我们就说这个数据的地址是未对齐的。
(地址对齐的操作注意用以就是让CPU能一次性读取绝不分成两次,能两次读取绝不分成三次)
地址未对齐的情形

地址未对齐处理情况"
地址对齐处理情况"

从图中可以明显看出,数据本身占据了8个字节,在地址未对齐的情况下,CPU需要分3次才能完整地存取完这个数据,但是在地址对齐的情况下,CPU可以分2次就能完整地存取这个数据。

总结:
如果一个数据满足以最小单元数存放在内存中,则称它地址是对齐的,否则是未对齐的。地址对齐的含义用大白话说就是1个单元能塞得下的就不用2个;2个单元能塞得下的就不用3个。
如果发生数据地址未对齐的情况,有些系统会直接罢工,有些系统则降低性能。


③普通变量的m值

以32位系统为例,由于CPU存取数据总是以4字节为单元,因此对于一个尺寸固定的数据而言,当它的地址满足某个数的整数倍时,就可以保证地址对齐。这个数就被称为变量的m值。
根据具体系统的字长,和数据本身的尺寸,m值是可以很简单计算出来的。

  • 举例:
char   c; // 由于c占1个字节,因此c不管放哪里地址都是对齐的,因此m=1
short  s; // 由于s占2个字节,因此s地址只要是偶数就是对齐的,因此m=2
int    i; // 由于i占4个字节,因此只要i地址满足4的倍数就是对齐的,因此m=4
double f; // 由于f占8个字节,因此只要f地址满足4的倍数(64位系统则为8)就是对齐的,因此m=4/8printf("%p\n", &c); // &c = 1*N,即:c的地址一定满足1的整数倍
printf("%p\n", &s); // &s = 2*N,即:s的地址一定满足2的整数倍
printf("%p\n", &i); // &i = 4*N,即:i的地址一定满足4的整数倍
printf("%p\n", &f); // &f = 4*N,即:f的地址一定满足4的整数倍

注意,变量的m值跟变量本身的尺寸有关,但它们是两个不同的概念

  • 手工干预变量的m值:
char c __attribute__((aligned(32))); // 将变量 c 的m值设置为32

语法:

  • attribute 机制是GNU特定语法,属于C语言标准语法的扩展。
  • attribute 前后都是双下划线,aligned两边是双圆括号。
  • attribute 语句,出现在变量定义语句中的分号前面,变量标识符后面。
  • attribute 机制支持多种属性设置,其中 aligned 用来设置变量的 m 值属性。
  • 一个变量的 m 值只能提升,不能降低,且只能为正的2的n次幂。
分类 属性 作用描述 应用场景
性能优化 inline 建议编译器将函数内联展开,减少函数调用开销 小型频繁调用的函数
always_inline 强制编译器内联函数,忽略优化设置 关键性能路径的短函数
aligned 控制变量或结构体的内存对齐方式 SIMD指令、缓存优化、硬件寄存器映射
hot 标记热点函数,优化代码布局 频繁执行的函数
cold 标记冷门函数,减少缓存污染 错误处理、罕见分支
代码检查 format 检查格式化字符串参数的类型安全性 printf/scanf风格的可变参数函数
noreturn 标记永不返回的函数 退出程序、无限循环、异常终止函数
nonnull 检查指针参数不能为NULL 必须非空的函数参数
warn_unused_result 强制检查函数返回值 必须处理返回值的函数
deprecated 标记已弃用的函数或变量 库版本管理、API迁移
内存布局控制 packed 取消结构体填充,紧凑内存布局 网络协议、硬件寄存器、二进制格式
aligned 指定变量或结构体的对齐要求 缓存行对齐、DMA缓冲区、原子操作
section 将代码或数据放入特定段 引导代码、加密段、特殊内存区域
cleanup 变量作用域结束时自动调用清理函数 自动资源管理、RAII模式
语言功能扩展 constructor 在main函数前自动执行的初始化函数 库初始化、全局构造器
destructor 在main函数后自动执行的清理函数 资源释放、全局析构器
constructor(priority) 指定初始化函数的执行优先级 依赖关系明确的初始化
destructor(priority) 指定清理函数的执行优先级 依赖关系明确的清理
cleanup 变量离开作用域时自动调用清理函数 自动内存管理、文件关闭
兼容性与链接 weak 声明弱符号,允许被重定义 库函数重载、默认实现
alias 为函数或变量创建别名 API兼容性、函数重命名
visibility 控制符号的可见性(default/hidden) 动态库符号导出控制
used 防止未使用的符号被优化删除 强制保留特定函数或变量
deprecated 标记已弃用的接口,编译时警告 向后兼容、API演进

④结构体的M值

概念:

  • 结构体的M值,取决于其成员的m值的最大值。即:M = max{m1, m2, m3, …};
  • 结构体的和地址和尺寸,都必须等于M值的整数倍。

示例:

struct node
{short  a; // 尺寸=1,m值=2double b; // 尺寸=8,m值=4char   c; // 尺寸=1,m值=1
};struct node n; // M值 = max{2, 4, 1} = 4;

以上结构体成员存储分析:
1.结构体的M值等于4,这意味着结构体的地址、尺寸都必须满足4的倍数。
2.成员a的m值等于2,但a作为结构体的首元素,必须满足M值约束,即a的地址必须是4的倍数
3.成员b的m值等于4,因此在a和b之间,需要填充2个字节的无效数据(一般填充0)
4.成员c的m值等于1,因此c紧挨在b的后面,占一个字节即可。
5.结构体的M值为4,因此成员c后面还需填充3个无效数据,才能将结构体尺寸凑足4的倍数。

⑤可移植性

可移植指的是相同的一段数据或者代码,在不同的平台中都可以成功运行。

  • 对于数据来说,有两方面可能会导致不可移植:
  • 数据尺寸发生变化 (比如Long 在32位 = 4 在64位=8)
  • 数据位置发生变化 (比如long 在32位中M= 4 在64位M=8)

第一个问题,起因是基本的数据类型在不同的系统所占据的字节数不同造成的,解决办法可以参考之前的知识可移植性数据类型即可。现在主要讨论第二个问题。
考虑结构体:

struct node
{int8_t  a;int32_t b;int16_t c;
};

以上结构体,在不同的的平台中,成员的尺寸是固定不变的,但由于不同平台下各个成员的m值可能会发生改变,因此成员之间的相对位置可能是飘忽不定的,这对数据的可移植性提出了挑战。


解决的办法有两种:

  • 第一,固定每一个成员的m值,也就是每个成员之间的塞入固定大小的填充物固定位置:
    • 该方法可以保证数据依然存在地址对齐的优势(访问相对高效)
struct node
{int8_t  a __attribute__((aligned(1))); // 将 m 值固定为1int64_t b __attribute__((aligned(8))); // 将 m 值固定为8int16_t c __attribute__((aligned(2))); // 将 m 值固定为2
};

  • 第二,将结构体压实,也就是每个成员之间不留任何空隙:
    • 该方案取消的成员之间的空隙,也就是无视了地址对齐对于访问效率会有一定的破坏
struct node
{int8_t  a;int64_t b;int16_t c;
} __attribute__((packed));//示例
#pragma pack(push, 1) // 保存当前对齐状态,并设置为 1 字节对齐
struct MyPackedStruct {char a;  // 1 byteint b;   // 4 bytes (现在可以从任何地址开始了)short c; // 2 bytes// 没有 padding 被插入
};
#pragma pack(pop) // 恢复之前保存的对齐状态// 现在这个结构体的大小是 1 + 4 + 2 = 7 字节

⑥结构体的占位符

占位符顾名思义,可以在声明结构体成员是把每一个成员所占的二进制位的数量进行设置,做到极致的内存利用率。但是需要注意数据溢出的问题!!

#include <stdio.h>struct Node
{// 结构体中使用:可以直接指定该成员占用的二进制位的位数unsigned int Num:2; // Num 只占用两个二进制位char Type:2;// Type 只占用两个二进制位short s:4 ; // s  只占用四个二进制位// 因此以上三个数据加起来也就刚好一个字节char ch ;short sh ;
};int main(int argc, char const *argv[])
{printf("%ld\n" , sizeof( struct Node ));struct Node data ;data.Num = 4 ;printf("%d\n" , data.Num );return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/940944.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

专栏导航:《数据中心网络与异构计算:从瓶颈突破到架构革命》 - 实践

专栏导航:《数据中心网络与异构计算:从瓶颈突破到架构革命》 - 实践pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: &q…

2025年10月长白山旅游度假酒店推荐:五家热选对比排行与实用评测

一、引言 十月初秋,长白山进入全年色彩最饱和的时段,枫叶与初雪同框,摄影爱好者、亲子家庭、自驾车队、企业团建四方客流叠加,度假酒店成为行程成败的关键节点。对计划2025年10月赴长白山的消费者而言,核心需求集…

CTFHub 信息泄露通关笔记9:Git泄露 Index - 指南

CTFHub 信息泄露通关笔记9:Git泄露 Index - 指南pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas",…

2025年10月抗老面霜推荐:小鸟传领衔五强对比评测榜

一、引言 抗老面霜已从“可选”变为25岁以上消费者的“刚需”。熬夜、蓝光、压力叠加,皮肤胶原流失速度提前,细纹、松弛、暗沉同步出现。对精致妈妈、新锐白领、Z世代而言,选对面霜意味着在有限预算内同时解决“紧致…

基于STM32芯片通过CAN总线控制电机运动

一、硬件架构设计 1. 系统组成 STM32主控模块 CAN总线网络 电机驱动模块 ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ STM32F407…

2025 酒店家具厂家最新推荐榜:北木斋领衔五大新锐品牌,品质与创新双优之选全解析

随着酒店行业向高端化、特色化加速升级,家具作为塑造空间格调与入住体验的核心载体,其选型直接影响酒店口碑与运营效益。当前市场中,既有深耕多年的龙头企业,也涌现出大批聚焦细分领域的新锐品牌,然而信息不对称导…

文献阅读笔记格式

Abstract gap: goal: 解决方案的关键: Introduction background:用于支撑的背景知识创新点: Method Experiment result: limitation: Conclusion Inspiration:(你认为可改进的点) 其他: 有用的论点、句子、…

企业AI应用的数据策略 - 实践

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

深入解析:c++的STL:string类与string类的手动基础实现

深入解析:c++的STL:string类与string类的手动基础实现pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas&…

JS中的值传递和引用传递

JS中的值传递和引用传递 JS没有引用传递 (arguments除外)值传递:内存独立,互不影响引用传递:共享一块内存空间,指向同一个地址var a = {} var b = a b.n = 3 a // {n:3}var a = {} var b = a b = {n:3} a // {…

乐理和蜂鸣器的实现

在学习计算机无源蜂鸣器的发声过程中,想到可以借此机会掌握乐理知识。 B站有一个从零基础讲解的非常好的视频: 20分钟乐理通俗讲解 这里想写一篇博客记录一下学习历程1.首先用Aduino+无源蜂鸣器实现《小星星》的旋律…

CF1288C Two Arrays 分析

题目概述 题目链接:https://www.luogu.com.cn/problem/CF1288C。 长度为 \(m\) 的序列 \(a,b\),值域为 \([1,n]\),求 \((a,b)\) 的数量满足:\(a\) 单调不降。 \(b\) 单调不升。 对于每个 \(i\),满足 \(a_i\leq b_…

基于Java+Springboot+Vue开发的母婴商城管理系统源码+运行步骤

项目简介该项目是基于Java+Springboot+Vue开发的母婴商城管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Java编程技能,同时锻炼他们的项目设计与开发能力。通过…

2025智能客服管理系统哪个好?对比国产主流5款工具中怎么选? - RAIN

2025智能客服管理系统哪个好?对比国产主流5款工具中怎么选?前言:智能客服成企业服务竞争核心,选型难题待解 在电商、金融、教育、SaaS等行业,客户咨询量激增、服务时段延长、问题类型多样化,导致企业面临人力成本…

一文详解 | 纷享销客CRM如何助力快消巨头蒙牛实现全场景数字化转型

当蒙牛乳业这样的千亿级快消巨头启动全场景、全链路数字化改革,需要怎样的合作伙伴? 过去3年,纷享销客通过构建「访」「销」「费」「促」「商」「店」数字化体系,助力蒙牛落地三流合一、战场扫描、终端费用直投、b…

基于进化算法的自动神经架构搜索

阅读文献: A Survey on Evolutionary Neural Architecture Search 是由 Yuqiao Liu 、Yanan Sun 等人发表在 IEEE TRANSACTIONS ON NEURAL NETWORKS AND LEARNING SYSTEMS (JCR-Q1)上的一篇综述 abstract 首先指出深…

基于MATLAB的谐波分析实现方案

基于MATLAB的谐波分析实现方案,包含信号生成、FFT处理、谐波参数提取及可视化模块:一、核心代码 function [harmonics] = analyze_harmonics(signal, fs, fundamental) % 输入参数: % signal: 输入时域信号 (列向量…

AI生成代码系列:开源代码片段检测的有效方法

AI生成代码系列:开源代码片段检测的有效方法随着代码片段通过AI增强的IDE自动补全功能及外部AI提示进入专有代码库,企业必须识别可能涉及许可义证务、安全风险或来源问题的开源软件(OSS)片段。此时,具备开源代码片…

稀疏大规模多目标优化问题

阅读文献: An Enhanced Competitive Swarm Optimizer With Strongly Convex Sparse Operator for Large-Scale Multiobjective Optimization 是由王翔宇、王健等人发表在 IEEE TRANSACTIONS ON EVOLUTIONARY COMPUTAT…

2025 年高端月子会所中心推荐:西安女王臻瑷月子会所 —— 专注母婴护理 10 年,打造高品质母婴护理服务标杆

行业背景随着居民生活水平提升与育儿观念升级,高端月子护理需求持续增长。现代家庭对产后母婴护理的专业性、安全性、舒适性要求愈发严苛,不仅期待科学的护理方案,更注重整体居住环境与配套服务的完整性。然而,当前…