结构体的声明使用及存储方式

news2024/11/20 9:24:20

文章目录

一、结构体的声明与使用

1、1 结构体的简单声明

1、2 结构体的特殊声明

1、3 结构体自引用

1、4 结构体变量的定义和初始化

1、5 结构体传参

二、结构体在内存中的存储方式

2、1 结构体在内存中的存储方式的引入

2、2 结构体的内存对齐

2、3 修改默认对齐数

三、结构体的位段

3、1 什么是位段

3、2 位段的内存分配

四、总结 

  本篇文章对C语言中的结构体进行了深度剖析。对结构体的声明、使用、内存存储方式等等重点内容进行详细介绍。

一、结构体的声明与使用

1、1 结构体的简单声明

  首先我们应该知道结构体什么。结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。

   我们看结构体的声明代码模板。

struct tag
{
    member-list;
}variable-list;

  tag是标签的意思,也就是我们要声明哪一类的结构体。member-list是成员变量列表,是我们声明成员变量的地方。variable-list是对象变量列表,相当于声明对象变量。

  我们看声明一个学生类的结构体,代码如下: 

struct Stu
{
    char name[20];//名字
    int age;//年龄
    char sex[5];//性别
    char id[20];//学号
}; //分号不能丢

1、2 结构体的特殊声明

  在声明结构的时候,可以不完全的声明。比如:

//匿名结构体类型
struct
{
    int a;
    char b;
    float c;
}x;
struct
{
    int a;
    char b;
    float c;
}a[20], *p;

   我们可以看到,上面的两个结构在声明的时候省略掉了结构体标签(tag)。那么问题来了?在上面代码的基础上,下面的代码合法吗?

p=&x;

   答案是非法的。我们看到上面的两个结构在声明的时候省略掉了结构体标签(tag),虽然他们的成员变量是一摸一样的,但是编译器会把上面的两个声明当成完全不同的两个类型。
所以是非法的。。

1、3 结构体自引用

  在结构中包含一个类型为该结构本身的成员是否可以呢?我们看如下代码: 

//代码1
struct Node
{
    int data;
    struct Node next;
};
//可行否?
如果可以,那sizeof(struct Node)是多少?

   答案是不可以的。我们发现在计算结构体大小是,进入了死循环。正确的方式如下:

struct Node
{
    int data;
    struct Node* next;
};

//注意

typedef struct
{
    int data;
    Node* next;
}Node;
//这样写代码,可行否?


//解决方案:
typedef struct Node
{
    int data;
    struct Node* next;
}Node;

   我们要注意的一点,上述中的第二种方式是不对的。真或缺的解决方案我们同样给出。

1、4 结构体变量的定义和初始化

  有了结构体,就到了对结构体变量的定义和初始化。这个也是比较简单的。我们直接看代码:

struct Point
{
    int x;
    int y;
}p1; //声明类型的同时定义变量p1

struct Point p2; //定义结构体变量p2

//初始化:定义变量的同时赋初值。
struct Point p3 = {x, y};

struct Stu //类型声明
{
    char name[15];//名字
    int age; //年龄
};

struct Stu s = {"zhangsan", 20};//初始化

struct Node
{
    int data;
    struct Point p;
    struct Node* next;
}n1 = {10, {4,5}, NULL}; //结构体嵌套初始化

struct Node n2 = {20, {5, 6}, NULL};//结构体嵌套初始化

1、5 结构体传参

  我们先来看一段代码:

struct S
{
    int data[1000];
    int num;
};

struct S s = {{1,2,3,4}, 1000};
//结构体传参
void print1(struct S s)
{
    printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
    printf("%d\n", ps->num);
}
int main()
{
    print1(s); //传结构体
    print2(&s); //传地址
    return 0;
}

 上面的 print1 和 print2 函数哪个好些?答案是:首选print2函数。原因:函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。
  结论:结构体传参的时候,要传结构体的地址

二、结构体在内存中的存储方式

2、1 结构体在内存中的存储方式的引入

  前面我们学过了整型和浮点型在内存中的存储方式(带你深度剖析《数据在内存中的存储),那么结构体呢?

  我们先看两个例子,代码如下:

//练习1
struct S1
{
    char c1;
    int i;
    char c2;
};
printf("%d\n", sizeof(struct S1));


//练习2
struct S2
{
    char c1;
    char c2;
    int i;
};
printf("%d\n", sizeof(struct S2));

   我们第一次看到两个结构体的大小感觉是一样大的,并且都为6个字节。那我们来看一下答案是这样的吗?

   答案好像并非如此,到底是为什么呢?这就涉及到了结构体在内存中的存储方式了。也就是结构体的内存对齐。我们来详解以下内存对齐。

2、2 结构体的内存对齐

  那么到底什么是内存对齐呢?内存对齐如何计算?首先得掌握结构体的对齐规则

  1. 第一个成员在与结构体变量偏移量为0的地址处。
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。VS中默认的值为8。
  3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
  4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

  通过对结构体的对齐规则了解后,娜美我们就知道S1和S2类型的成员一模一样,但是S1和S2所占空间的大小有了一些区别。所以大小是不同的。

为什么存在内存对齐?大部分的参考资料都是如是说的:

  • 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
  • 性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

总体来说:结构体的内存对齐是拿空间来换取时间的做法。那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:让占用空间小的成员尽量集中在一起。

2、3 修改默认对齐数

  之前我们见过了 #pragma 这个预处理指令,这里我们再次使用,可以改变我们的默认对齐数。代码如下:

#include <stdio.h>
#pragma pack(8)//设置默认对齐数为8
struct S1
{
    char c1;
    int i;
    char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认

#pragma pack(1)//设置默认对齐数为1
struct S2
{
    char c1;
    int i;
    char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认

int main()
{
    //输出的结果是什么?
    printf("%d\n", sizeof(struct S1));
    printf("%d\n", sizeof(struct S2));
}

   我们再看一下修改默认对齐数后的的结果。

  还是有所不同的。当我们把默认对齐数修改为1后,相当与可以直接连续放在内存中。所以结构也就是哦我们最开始想的6了。 

  结构在对齐方式不合适的时候,我么可以自己更改默认对齐数。

三、结构体的位段

3、1 什么是位段

位段的声明和结构是类似的,有两个不同:

  • 位段的成员必须是 int、unsigned int 或signed int 。
  • 位段的成员名后边有一个冒号和一个数字。

比如:

struct A
{
    int _a:2;
    int _b:5;
    int _c:10;
    int _d:30;
};

  A就是一个位段类型。那位段A的大小是多少呢?

  结果好像再次出乎意料耶。这就与位段的内存分配有关了,接下来我们看一下位段的内存分配。

3、2 位段的内存分配

  位段的内存分配如下:

  1. int 位段被当成有符号数还是无符号数是不确定的。
  2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。
  3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
  4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。

  在使用位段时,我们也要注意以下几点:

  1. 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
  2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
  3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

  通过上面对位段的了解后,我们也就知道上述例题的答案为什么是8了。 上述的结果位段的标准是是从右向左分配标准、无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用。

四、总结

  结构体的声明和使用相对来说较为简单,我们需要重点掌握结构体的内存对齐规则和位段。这也是常考的内容。后续我会更新一篇关于结构体的练习的文章,大家可以用来练习巩固。 

  我们对于结构体的讲述就到这里,希望以上内容对你有所帮助。感谢观看ovo~  

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

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

相关文章

AcWing - 寒假每日一题2023(DAY 1——DAY 5)

文章目录一、AcWing 4261.孤独的照片&#xff08;简单&#xff09;1. 实现思路2. 实现代码二、AcWing 3400.统计次数&#xff08;简单&#xff09;1. 实现思路2. 实现代码三、AcWing 4366. 上课睡觉&#xff08;简单&#xff09;1. 实现思路2. 实现代码四、AcWing 3443. 学分绩…

程序员接私活最最完整攻略

接私活对于程序员这个圈子来说是一个既公开又隐私的话题&#xff0c;当你竭尽全力想要去接私活的时候一定做过这样的事情&#xff0c;百度搜索“程序员如何接私活”、“程序员在哪里接外包”等等问题&#xff0c;今天就送大家最完整攻略&#xff0c;千万别错过了。 做私活挣钱吗…

有趣且重要的Git知识合集(10)git stash操作

这种一般用于多分支&#xff0c;或者多人协同合作时会使用到的git命令 场景1&#xff1a; 当你在dev分支上写了很多代码&#xff0c;此时线上有bug&#xff0c;需要紧急在hotfix分支上修改&#xff0c;那直接git add提交又不太好&#xff0c;毕竟还没有开发完&#xff0c;那么…

JVM 学习笔记 内存结构

内存结构 程序计数器 作用&#xff1a;记录下一条JVM指令的执行地址 特点&#xff1a; 线程私有不存在内存溢出 虚拟机栈 每个线程运行时所需的内存称为虚拟机栈。每个栈由多个栈帧&#xff08;Frame&#xff09;组成&#xff0c;每个栈帧对应每次方法调用时占用的内存。每…

BIOS 的详细介绍

一、BIOS详解 对于不少新手&#xff0c;刷新BIOS还是比较神秘的。而对于一些BIOS相关的知识&#xff0c;不少人也是一知半解。在这里&#xff0c;我们将对BIOS作一次全面的了解。 1、什么是BIOS BIOS是英文"Basic Input Output System"的缩略语&#xff0c;直译…

NTN(三) Timing

微信同步更新欢迎关注同名modem协议笔记。这篇看下k_offset和k_mac&#xff0c;如38.300所述&#xff0c;k_offset是配置的调度偏移量&#xff0c;需要大于或等于service link RTT和Common TA之和&#xff1b;k_mac 是配置的偏移量&#xff0c;需要大于或等于 RP 和 gNB 之间的…

Chem. Eur. J.|针对细胞内靶点的环肽药物:肽药物发展的下一个前沿

​题目&#xff1a;Cyclic Peptides as Drugs for Intracellular Targets: The Next Frontier in Peptide Therapeutic Development 文献来源&#xff1a;Chem. Eur. J. 2021, 27, 1487 – 1513 代码&#xff1a;无&#xff08;环肽综述&#xff09; 内容&#xff1a; 1.简…

5-迷宫问题(华为机试)

题目 定义一个二维数组 N*M&#xff0c;如 5 5 数组如下所示&#xff1a; int maze[5][5] { 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, }; 它表示一个迷宫&#xff0c;其中的1表示墙壁&#xff0c;0表示可以走的路&#xff0c;只…

通用模型切片处理过程 CesiumLab系列教程

我们前面把每种格式的模型参数设置已经讲解清楚&#xff0c;下面我们应该弄清楚通用模型切片剩下的流程&#xff0c;不管是人工模型&#xff0c;还是shp矢量面、bim模型&#xff0c;剩下的处理过程都是一样的&#xff0c;这里我们一起讲述。 资源库 ​通用模型处理分为两个过程…

基于JAVA的个人信息管理系统源码,含基于VUE的PC前端及移动端,用于管理个人消费、锻炼、音乐、阅读、健康、饮食等衣食住行信息

项目介绍 完整代码下载地址&#xff1a;基于JAVA的个人信息管理系统源码 用于管理个人消费、锻炼、音乐、阅读、健康、饮食、人生经历等各个衣食住行信息的系统&#xff0c;通过提醒、计划模块利用调度系统来统计分析执行情况。 并通过积分和评分体系来综合评估个人的总体状态…

【C++】类和对象【中篇】--C++六个默认成员函数以及const成员函数

文章目录一、类的6个默认成员函数二、构造函数1.概念2.特性2.1特征分析——自动生成2.2.特征分析——选择处理2.3特征分析——默认构造3.C11补丁——缺省值三、析构函数1.概念2.特征四、拷贝构造函数1.概念2.特征2.1引用分析——引用做参数2.2特征分析——深浅拷贝五、运算符重…

Clin Nutr | 浙大儿童医院-陈洁/倪艳揭示全肠内营养对儿童克罗恩病肠道菌群和胆汁酸代谢的影响...

全肠内营养对儿童克罗恩病肠道微生物群和胆汁酸代谢的影响The impact of exclusive enteral nutrition on the gut microbiome and bile acid metabolism in pediatric Crohns diseaseResearch article&#xff0c;2022年11月30日&#xff0c;Clinical Nutrition&#xff0c;7.…

Weblogic 任意文件上传漏洞(CVE-2018-2894)复现

目录 weblogic 漏洞环境准备 漏洞复现 修复建议 weblogic WebLogic是美国Oracle公司出品的一个application server&#xff0c;确切的说是一个基于JAVAEE架构的中间件&#xff0c;WebLogic是用于开发、集成、部署和管理大型分布式Web应用、网络应用和数据库应用的Java应…

Healthcare靶机总结

Healthcare靶机渗透总结 靶机下载地址: https://download.vulnhub.com/healthcare/Healthcare.ova 打开靶机,使用nmap扫描出靶机的ip和所有开放的端口 可以看到,靶机开放了21端口和80端口 21端口为ftp的端口,一般遇到这种,就可以考虑ftp的匿名登录,我们可以试一下 用户名anony…

【总结】华为、H3C、锐捷三家交换机配置命令详解

一直以来&#xff0c;对于华为、H3C、锐捷交换机的命令配置&#xff0c;不断的有朋友留言&#xff0c;三家交换机的配置命令容易弄混&#xff0c;经常在实际项目配置中出错&#xff0c;因此&#xff0c;本期我们将来介绍这三家交换机的基础配置命令&#xff0c;大家可以分别来看…

动手学区块链学习笔记(一):加密算法介绍

引言 本文根据实验楼以及自己查询到的一些资料&#xff08;文末给出&#xff09;&#xff0c;模拟了一下区块链从诞生到交易的整个过程&#xff0c;也算是弥补了一下之前区块链的一些缺失知识。 哈希加密原理介绍 什么是比特币&#xff1f; 比特币是一种加密货币&#xff0c…

【Python百日进阶-数据分析】Day223 - plotly瀑布图go.Waterfall()

文章目录一、语法二、参数三、返回值四、实例4.1 简单瀑布图4.2 多类别瀑布图4.3 设置标记大小和颜色4.4 水平瀑布图4.5 Dash中的应用一、语法 绘制瀑布轨迹&#xff0c;这是一种有用的图表&#xff0c;可以在条形图中显示各种元素&#xff08;正或负&#xff09;的贡献。y如果…

一文读懂mybatis连接池原理

本文需要配合代码demo一起观看更佳&#xff0c;源码地址。 本源码中对 mybatis代码做了详尽的注释。对mybatis源码进行了详尽的注释&#xff0c;且可以对项目进行install&#xff0c;然后在ron-man-mybatis1项目中 src/main/java/iron/man/lyf/ironmanmybatis1/run_test/Mybat…

亚马逊云科技启示录:创新作帆,云计算的征途是汪洋大海

开篇&#xff1a;创新是亚马逊云科技发展的最持久驱动力云计算&#xff0c;新世纪以来最伟大的技术进步之一&#xff0c;从2006年 Amazon Web Service 初创时的小试牛刀&#xff0c;到如今成长为一个巨大的行业和生态&#xff0c;已经走过16年的风雨历程。Java之父詹姆斯高斯林…

关于子查询

1、什么是子查询&#xff1a; select语句中嵌套select语句&#xff0c;被嵌套的select语句称为子查询。 2、子查询都可以出现在什么地方&#xff1a; select ..(select) #子查询可以出在select后面 from ..(select) #子查询可以出在from后面 where ..(select) …