Cyuyan中的自定义类型——结构体

news2024/12/24 2:38:05

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 一、结构体基础知识
    • (一)、结构体类型的声明、变量的创建与初始化
    • (二)、结构成员访问操作符
    • (三)、结构体的自引用
    • (四)、结构体传参
  • 二、结构体内存对齐!!
    • (一)、对齐规则
    • (二)、分析结构体大小的详细过程
    • (三)、为什么存在内存对齐
    • (四)、默认对齐数的可修改性
  • 三、结构体实现位段!
    • (一)、位段的声明
    • (二)、位段的内存分配
    • (三)、位段的跨平台问题
    • (四)、位段的应用
    • (五)、位段的使用注意事项
  • 总结


前言

提示:这里可以添加本文要记录的大概内容:

在C语言中共有三种自定义类型——结构体、联合体、枚举。本文主要介绍第一种结构体,后面文章中会介绍联合体与枚举。本文主要围绕以下几个方面对结构体进行介绍——结构体的基础知识、结构体的内存对齐、结构体实现位段。结构体内存对齐,和实现位段是我们比较陌生的知识,需要努力掌握一下。


提示:以下是本篇文章正文内容,下面案例可供参考

一、结构体基础知识

结构是一些值的几何,这些值称为成员变量。值得一提的是结构的每个成员可以是不同类型的变量。
结构体的关键字为struct

(一)、结构体类型的声明、变量的创建与初始化

  • 我们以描述一个学生为例:
#include<stdio.h>
struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
};//注意分号不能丢

我们列举了学生应该有的一些特征,利用不同的数据类型给定义变量值作为结构体的成员,将这些成员组合在一起就构成了一个描述学生的结构体。这就是它的声明.

  • 对于结构体的变量创建以及初始化,我们还是以描述一个学生为例:
struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
};//注意分号不能丢
int main()
{
struct Stu s={"张三"20,“男”,“20230818001}//这里我们就创建了结构体变量s,并对其进行了初始化的操作
}

同时我们也可以在声明部分直接定义结构体变量

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

这里我们直接在声明中大括号后面,分号之前直接定义了结构体变量s。

  • 结构体的特殊声明
    在声明结构体的时候,可以不完全声明,即将结构体的名字给抹掉。这样的话存在两个个弊端:
    第一个就是就是我们进行定义结构体变量的时候,如果没有对结构体进行typedef重命名的话,基本上只能在声明后面定义变量,且只能使用一次。
    第二个就是尽管两个结构体里面成员完全一致,但会将这两个结构的声明当成完全不同的类型。
//匿名结构体声明:
struct 
{
int a;
char b;
float c;
}x;//如果没有重定义的话只能在声明部分,定义变量
struct 
{
int a;
char b;
float c;
}a[20],*p;//如果没有重定义的话只能在声明部分,定义变量

根据第二个弊端可以得出:

p=&x;

这句代码是完全错误的,因为编译器会将上面两个匿名结构体声明当成两种不同的类型。

(二)、结构成员访问操作符

成员访问操作符一个是: . ;另外一个是:-> ;
这两个一个针对非指针结构体变量,一个针对指针型结构体变量。

#include<stdio.h>
struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}a,*b;//注意分号不能丢
int main()
{
//我们要访问stu结构体中的成员两种方式:
//1.非指针型
a.age=20;
printf("%d\n",a.age);
//2.指针型
printf("%s",b->name);
}

在这里插入图片描述

(三)、结构体的自引用

  • 结构体的自引用就是结构体的成员中有类型为结构体本身。
  • 例如定义一个单链表的节点
struct Node
{
int data;
struct Node next;
};

其实上面的代码是有一些问题的,我们考虑一下啊,一个结构体中再包含一个同类型的结构体变量,这样结构体变量的大小就会无穷大,这样是不合理的,正确的引用应该用指针型:

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

这样的话指针在X86环境下只占4个字节,就可以避免了结构体变量大小为无穷大的现象。

  • 对于不完全声明的匿名结构体类型是不能进行自引用的(尽管进行了重定义)
typedef struct
{
int data;
Node* next;
}Node;

上面这段代码是错误的,因为自定义类型Node在后面声明的,不能提前使用。
故而定义结构体(自引用)不要使用匿名结构体。

(四)、结构体传参

  • 传值调用
struct S
{
int data[1000];
int num;
};
void print1(struct S s)
{
printf("%d",s.num);
}
int main()
{
struct S s={{1,2,3,4},4};
print1(s);//传参传的是结构体
}
  • 传址调用
struct S
{
int data[1000];
int num;
};
void print2(struct S* p)
{
printf("%d",p->num);
}
int main()
{
struct S s={{1,2,3,4},4};
print2(&s);//传参传的是结构体的地址
}

上面两端代码打印的结果是相同的,分别是结构体的传值调用,以及传址调用,但是我们首先选择的是传址调用,因为函数传参的时候,参数是需要压栈的,会有时间和空间上的系统开销,如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,所以会导致性能的下降。而传址传的是指针,在X86环境下只有4个字节,比较小。故而结构体传参的时候,我们优先选择传结构体的地址。

二、结构体内存对齐!!

(一)、对齐规则

  • 结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处。
    宏 offsetof(type,member)(头文件 stddef.h)是计算结构体成员相较于结构体变量起始位置的偏移量。
  • 其他成员要对齐到某个数字(对齐数)的整数倍的地址处
    在这里对齐数=编译器默认的一个对齐数 与该成员变量大小的较小值。
    VS中默认对齐数为8;Linux gcc中没有默认对齐数,对齐数就是成员自身的大小
  • 结构体总的大小为最大对齐数(结构体中每一个成员变量都有一个对齐数,所以对齐数中最大的)的整数倍
  • 对于镶嵌了结构体的情况,镶嵌的结构体成员对齐到自己成员中最大对齐数的整数倍处。故而结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍

(二)、分析结构体大小的详细过程

  • 练习1:
#include<stdio.h>
struct S2
{
char c1;
int i;
char c2;
};
int main()
{
printf("%zd",sizeof(struct S2));

}

运行结果如下:
在这里插入图片描述
根据结构体对齐规则:对齐如下图:
在这里插入图片描述
结构体第一个成员为c1,它位于结构体变量起始位置偏移量为0的地址处,占用一个字节;然后第二个成员为n,它的内存大小为4个字节,VS默认对齐数为8,所以取较小值,故而它的对齐数为4,要从对齐数的整数倍开始,故而n变量的地址起始位置应该在偏移量为4的位置,顺至到偏移量为7的位置(共4个字节);最后一个成员为c2,它的内存大小为1个字节,VS默认对齐数为8,所以它的对齐数为1.故而偏移量8即为它的起始地址。结构体成员的最大对齐数为4,所以结构体变量的大小应该是最大对齐数的整数倍,即4的倍数,现在已经是9个字节了,所以还应该浪费3个字节,变成12个字节故而此结构体变量大小为12个字节。

  • 练习2:
#include<stdio.h>
struct S1
{
char c1;
char c2;
int i;
};
int main()
{
printf("%zd",sizeof(struct S1));

}

运行结果如下:
在这里插入图片描述
根据结构体对齐规则:对齐如下图:
在这里插入图片描述
结构体第一个成员为c1,它位于结构体变量起始位置偏移量为0的地址处,占用一个字节;然后第二个成员为c2,它的内存大小为1个字节,VS默认对齐数为8,所以取较小值,故而它的对齐数为1,顺利的占据偏移量为1的地址处;最后第三个成员为int型,它的内存大小为4,VS默认对齐数为8,所以它的对齐数为4,要从对齐数的整数倍开始,故而n变量的地址起始位置应该在偏移量为4的位置,顺至到偏移量为7的位置(共4个字节)。它的所有成员的最大对齐数为4,根据对齐规则,此结构体变量的大小应该是最大对齐数的整数倍,所以占据8个字节。

  • 练习3:
#include<stdio.h>
struct S3
{
double d;
char c;
int i;
};
int main()
{
printf("%zd",sizeof(struct S3));

}

运行结果如下:
在这里插入图片描述

根据结构体对齐规则:对齐如下图:
在这里插入图片描述
结构体第一个成员为d,它位于结构体变量起始位置偏移量为0的地址处,占用8个字节;第二个成员为c,它的内存大小为1个字节,VS默认对齐数为8,所以它的对齐数为1,故而它的起始位置是偏移量为8的位置,共占据一个字节;它的第三个成员为i,它的内存大小为4个字节,VS默认对齐数为8,所以它的对齐数为4,因为对齐数位置要是4的倍数,所以它的起始位置的偏移量为12,共占据4个字节,此时偏移量来到15。此结构体变量成员中最大对齐数为8,所以该结构体变量所占字节大小为8的倍数,此时正好为16个字节,正好满足为8的倍数,所以不用额外扩充,浪费字节。

  • 练习4:结构体嵌套问题
#include<stdio.h>
struct S3
{
double d;
char c;
int i;
};
struct S4
{
char c1;
struct S3 s3;
double d;
};
int main()
{
printf("%zd",sizeof(struct S4));

}

运行结果如下:
在这里插入图片描述
根据结构体对齐规则,对齐如下图:
在这里插入图片描述
结构体第一个成员为c1,它位于结构体变量起始位置偏移量为0的地址处,占用一个字节;紧接着,第二个成员为s3,它的内存大小为16(上面以求),VS默认对齐数为8,所以它的对齐数为8,由于它的起始 位置要是8的倍数,所以它的起始位置偏移量为8,共计16个字节,所以来到了偏移量为23的位置;该结构体的第三个成员为d,它的内存大小为8个字节,VS默认对齐数为8,所以它的对齐数为8,由于它的起始 位置要是8的倍数,所以它的起始位置偏移量为24,共计8个字节,来到了偏移量为31的位置。该结构体成员的最大对齐数为8,所以该结构体变量的大小为8的倍数,此时正好为32个字节,正好满足为8的倍数,所以不用额外扩充,浪费字节。

(三)、为什么存在内存对齐

  • 平台原因(移植原因):
    不是所有的硬件平台都能访问任意地址上的任意数据的;某些平台只能在某些地址处取某些特定类型的数据,否则会抛出硬件异常。故而应该对齐指定位置。
  • 性能原因:
    数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐地内存,处理器需要作两次内存访问;而对齐地内存访问仅需要一次访问。假设一个处理器总是从内存中取4个字节,则地址必须是4的倍数。如果我们能保证所有的int类型的数据的地址都能对齐成4的倍数,那么就可以用一个内存操作来读或者写值了。否则,我们可能需要执行两次内存访问,因为对象可能被分放在两个8字节的内存块中。
    以下面例子来解释:
    在这里插入图片描述
    我们一次要访问4个字节,如果没对齐,我们要完整的访问n变量需要两次访问,而对齐后我们只需要一次访问即可,这节省了时间。

总体来说:结构体的内存对齐实质上是拿空间来换取时间的做法。

而我们肯定会想,设计结构体的时候,如何做到又能对齐,又能节省空间。我们的做法是应该让占用空间小的成员尽量集中在一起!!

#include<stdio.h>
struct S1
{
char c1;
char c2;
int i;
};
struct S2
{
char c1;
int i;
char c2;
};
int main()
{
printf("%zd\n",sizeof(struct S1));
printf("%zd\n",sizeof(struct S2));
}

运行结果如下:
在这里插入图片描述
S1和S2结构体的成员一样,但是S1中让占用空间小的成员c1,c2集中在一起,那么它占用的内存就小

(四)、默认对齐数的可修改性

  • 我们用#pragma 这个预处理指令, 可以改变编译器的默认对齐数
#pragma pack(1)
struct S
{
	char c1;
	int n;
	char c2;

};

int main()
{

	printf("%zd\n", sizeof(struct S));


}

运行结果如下:
此时VS的默认对齐数为1.
在这里插入图片描述

三、结构体实现位段!

(一)、位段的声明

  • 位段的声明与结构体的声明类似,但需要注意以下几点:
    1.位段的成员必须是 int、unsigned int、signed int、c类型,但在C99中也可以选择其他类型;
    2.位段的成员名后边有一个冒号和一个数字。
struct A
{
int _a:2;
int _b:5;
int _c:10;
int _d:30;
};

在这里A就是一个位段类型,这里里面成员_a冒号后的数字代表所给变量定义所占的二进制位数。
那么这个结构体的大小会是多少呢,要解决这个问题,我们得先了解位段的内存分配规则

(二)、位段的内存分配

  • 位段成员可以是int、unsigned int、signed int或者是char等类型

  • 位段的空间是按照需要以4个字节(int)或者1个字节(char)的方式来开辟的。

  • 位段涉及很多不确定因素,位段是不跨平台的,要注意可移植的程序应该避免使用位段

  • 分析下面位段的大小:

#include<stdio.h>
struct S
{
char a:3;
char b:4;
char c:5;
char d:4;

};
struct S s={0};
int main()
{
printf("%zd",sizeof(struct S));
}

运行结果如下:
在这里插入图片描述
咱们假设给定空间后,在空间内部都是从右向左使用;当剩下空间不足以放下一个成员的时候,空间就浪费掉;且位段的哦那关键是按照需要以1个字节的方式来开辟。
在这里插入图片描述
这样,在开辟第一个字节时候,a从右向左存储三个bit位,紧接着b存储4个比特位,这个字节就浪费了一个bit位,紧接着开辟下一个字节,这是c从右向左占据5个比特位,剩下3个bit位不足以存放d变量,所以又重新开辟一个字节。故而这个位段的大小为3个字节。

(三)、位段的跨平台问题

  • int 位段被当作有符号数还是无符号数是不确定的。
  • 位段中最大位的数目不能确定。(例如16位机器最大为16,而32位机器最大是32。故而写成27,在16位机器中会出错)
  • 位段中的成员在内存中从左向右分配,还是从右向左分配,标准尚未定义
  • 当一个结构体包含两个位段,第二个位段成员比较大,无法容纳第一个位段剩余的位时候,是舍弃剩余的位还是将剩余位利用,这个是不确定哒。

总而言之:与结构相比,位段可以达到同样的效果,并且可以很好的节省空间,但是位段存在跨平台问题。

(四)、位段的应用

  • 在网络协议中,IP数据报的格式,很多属性只需要几个bit位就可以描述,在这里我们可以使用位段,来达到理想的效果,充分节省空间,这样下来,在网络传输的时候,数据报的大小也会较小一些,这对网络的畅通是有帮助的。
    在这里插入图片描述

(五)、位段的使用注意事项

  • 位段的几个成员共用一个字节,这样有些成员的起始位置并不是某个字节的起始位置,故而这些位置处是没有地址的。因为在内存中每个字节分配一个地址,一个字节内部的bit位是没有地址的。故而我们在对没有地址的位段成员不能使用取地址操作符(&),所以在用scanf函数的时候,我们应该采用间接赋值。
#include<stdio.h>
struct A
{
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 30;
};
int main()
{
	struct A sa = { 0 };
	int d = 0;
	scanf("%d", &d);
	sa._d = d;
	printf("%d", sa._d);
}

为了避免位段中的成员可能不能进行取地址操作,我们引入中间变量d,来对位段成员间接赋值。

总结

本文介绍了C语言自定义类型中的第一种结构体的相关知识,着重介绍了结构体内存对齐以及位段的相关知识,如有错误,请批评指正。

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

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

相关文章

近红外光谱脑功能成像(fNIRS):1.光学原理、变量选取与预处理

一、朗伯-比尔定律与修正的朗伯-比尔定律 朗伯-比尔定律 是一个描述光通过溶液时被吸收的规律。想象你有一杯有色液体&#xff0c;比如一杯红茶。当你用一束光照射这杯液体时&#xff0c;光的一部分会被液体吸收&#xff0c;导致透过液体的光变弱。朗伯-比尔定律告诉我们&#…

如何在主动动态安全中使用人工智能驱动的威胁分类提高防御精准度

面对当今世界不断演变的网络威胁&#xff0c;人工智能和网络安全将会发挥重要的防护作用。在数据泄露和网络攻击日益突出的时代&#xff0c;人工智能和网络安全之间的合作成为数字安全战场上的强大盟友。 本文将深入研究这两个领域的融合&#xff0c;揭示它们在彻底改变威胁检测…

未来的钥匙在于过去:学历史的真正意义,震惊!历史竟然是偶然的?从历史中寻找未来的方向!

我们自幼接受的教育是&#xff0c;学历史是为了相信历史是必然的。中国人民必然战胜日寇的侵略&#xff0c;解放思想和改革开放必定会发生&#xff0c;和平和发展必定是世界的主题&#xff0c;中国经济必定是高速增长…… 然而&#xff0c;在真正的历史学家眼中&#xff0c;历史…

什么是 Socks5 代理?了解和使用 SOCKS5 代理的终极指南

SOCKS5是什么以及它如何工作&#xff1f; 在网络和互联网协议领域&#xff0c;有多种工具和技术在确保安全高效的通信方面发挥着至关重要的作用。 SOCKS5 就是这样一个工具&#xff0c;它代表套接字安全版本 5。 在这篇博文中&#xff0c;我们将深入探讨 SOCKS5 的细节&…

实战项目——用Java实现图书管理系统

前言 首先既然是管理系统&#xff0c;那咱们就要实现以下这几个功能了--> 分析 1.首先是用户分为两种&#xff0c;一个是管理员&#xff0c;另一个是普通用户&#xff0c;既如此&#xff0c;可以定义一个用户类&#xff08;user&#xff09;&#xff0c;在定义管理员类&am…

【后端面试题】【中间件】【NoSQL】MongoDB提高可用性的方案(主从结构、仲裁节点、分片、写入语义)

主从结构 MongoDB的高可用和别的中间件的高可用方案基本类似。比如在MySQL里&#xff0c;接触了分库分表和主从同步&#xff1b;在Redis里&#xff0c;Redis也有主从结构&#xff1b;在Kafka里&#xff0c;分区也是有主从结构的。 所以先介绍启用了主从同步 我们的系统有一个关…

【AIGC】DiffuToon:稳定的视频卡通化技术方案

论文&#xff1a;https://arxiv.org/pdf/2401.16224 github&#xff1a;https://github.com/modelscope/DiffSynth-Studio/tree/main/examples/Diffutoon 网络结构 两个主要分支&#xff1a;主要的卡通化管线和编辑分支 重要技术 使用了fastblend(无需模型的视频帧之间的平滑…

巴图自动化Profinet协议转Modbus协议模块接称重模块与PLC通讯

巴图自动化Profinet协议转Modbus协议模块&#xff08;BT-MDPN10&#xff09;是一种能够实现Modbus协议和Profinet协议之间转换的设备。Profinet协议转Modbus协议模块可提供单个或多个RS485接口&#xff0c;使得不同设备之间可以顺利进行通信&#xff0c;进一步提升了工业自动化…

IPFoxy Tips:为什么要选择动态住宅代理IP?

在大数据时代的背景下&#xff0c;代理IP成为了很多企业顺利开展的重要工具。代理IP地址可以分为住宅代理IP地址和数据中心代理IP地址。选择住宅代理IP的好处是可以实现真正的高匿名性&#xff0c;而使用数据中心代理IP可能会暴露自己使用代理的情况。 住宅代理IP是指互联网服务…

花两天手撸海纳嗨数数据分析系统,实现数据分析自给自足

我发现一款超好用的数据分析营销系统&#xff0c;且支持免费私有化部署。 机器准备 机器角色配置10.0.21.85主8核&#xff0c;12G&#xff0c;100G&#xff0c;Centos7.910.0.221.51辅8核&#xff0c;12G&#xff0c;100G&#xff0c;Centos7.910.0.221.55辅8核&#xff0c;12…

x86芯片定制,Ethercat芯片定制,适用于运动控制,工业总线等软硬一体机

x86芯片定制&#xff0c;Ethercat芯片定制 X86平台 我们的研发工程师已经积累了非常丰富的主板、整机设计经验&#xff0c;对接您的产品规格场景需求&#xff0c;快速交付样机&#xff0c;包含主板、BOX整机、平板电脑、CPCI等形态产品。降本、长生命周期、快速交付、及时响应…

电影院售票管理系统(小白)大佬求解

最近在写一个关于电影院售票管理系统的sm项目&#xff0c;但是在买票的环节出现了问题及点击选座购票&#xff0c;没有数据渲染出来&#xff0c;我不知道什么情况&#xff0c;所以问问。有没有大佬可以帮我解决这个问题&#xff1f;下面是我的。控制层&#xff0c;服务层&#…

systemctl命令使用

systemctl 作用&#xff1a;可以控制软件&#xff08;服务&#xff09;的启动、关闭、开机自启动 系统内置服务均可被systemctl控制第三方软件&#xff0c;如果自动注册了可以被systemctl控制第三方软件&#xff0c;如果没有自动注册&#xff0c;可以手动注册 语法 systemct…

《数据结构与算法基础 by王卓老师》学习笔记——2.5线性表的链式表示与实现1

1.链式表示 2.链表举例 3.链式存储的相关术语 4.三个讨论题

生成式人工智能与虚拟资产成为BGOV 2024的焦点议题

香港&#xff0c;2024年7月2日 — 一年一度的创新科技盛事BUSINESS GOVirtual (BGOV) 科技博览及会议将于2024年7月11日至12日在香港会议展览中心隆重举行。 展览及会议焦点两大科技趋势&#xff1a;生成式人工智能 (Generative AI) 和虚拟资产 生成式人工智能( Generative A…

【CT】LeetCode手撕—199. 二叉树的右视图

目录 题目1- 思路2- 实现⭐199. 二叉树的右视图——题解思路 3- ACM 实现 题目 原题连接&#xff1a;199. 二叉树的右视图 1- 思路 使用二叉树的层序遍历 2- 实现 ⭐199. 二叉树的右视图——题解思路 class Solution {public List<Integer> rightSideView(TreeNode ro…

【高级篇】第9章 Elasticsearch 监控与故障排查

9.1 引言 在现代数据驱动的应用架构中,Elasticsearch不仅是海量数据索引和搜索的核心,其稳定性和性能直接影响到整个业务链路的健康度。因此,建立有效的监控体系和掌握故障排查技能是每一位Elasticsearch高级专家的必备能力。 9.2 监控工具:洞察与优化的利器 在Elastics…

AzureDataFactory Dataverse connector自动处理了分页问题(单次查询上限5000条的限制)

众所周知&#xff0c;在用fetch执行D365的查询时&#xff0c;单次的查询是5000条&#xff0c;如果超过5000条则需要自己处理分页&#xff0c;添加额外的处理逻辑&#xff0c;但在ADF中&#xff0c;Dataverse connector已经自动处理了分页&#xff0c;我们可以很简单的做个POC. …

用随机森林算法进行的一次故障预测

本案例将带大家使用一份开源的S.M.A.R.T.数据集和机器学习中的随机森林算法&#xff0c;来训练一个硬盘故障预测模型&#xff0c;并测试效果。 实验目标 掌握使用机器学习方法训练模型的基本流程&#xff1b;掌握使用pandas做数据分析的基本方法&#xff1b;掌握使用scikit-l…

软件测试与质量保证 | 云班课选择题库

目录 第1章课后习题 第2章课后习题 第3章课后习题 第4章课后习题 第5章课后习题 第6章课后习题 第7章课后习题 第8章课后习题 第9章课后习题 第10章课后习题 第11章课后习题 第12章课后习题 第13章 测试相关未分类习题 第1章课后习题 1. 与质量相关的概念包括 &a…