c语言进阶部分详解(详细解析自定义类型——结构体,内存对齐,位段)

news2024/10/5 4:31:36

上篇文章介绍了一些常用的字符串函数,大家可以去我的主页进行浏览。

各种源码大家可以去我的github主页进行查找:Nerosts/just-a-try: 学习c语言的过程、真 (github.com)  

今天要介绍的是:结构体的相关内容


目录

一.结构体类型的声明

1.结构的基础知识

2.结构的声明 

3.特殊的声明 

4.结构的自引用

5.结构体变量的定义和初始化 

 6.结构体传参

 二.结构体内存对齐  

 1.对其规则

2.存在原因

3.减少浪费 

​编辑

4.修改默认对齐数

三.位段 

1.什么是位段

2.位段的内存分配

3.位段的跨平台问题 


一.结构体类型的声明

1.结构的基础知识

结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量

2.结构的声明 

 结构的声明的原型:

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

eg: 

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

3.特殊的声明 

匿名结构体: 

struct
{
 int a;
 char b;
 float c;
}x;


struct
{
 int a;
 char b;
 float c;
}a[20], *p;

4.结构的自引用

 我们也许回想在结构中包含一个类型为该结构本身的成员是否可以呢?像下面这样

struct Node
{
	struct Node a1;
	int date[2];
};

这种情况下,需要确保结构体类型的定义是在使用它的结构体类型之前。否则,编译器将无法确定结构体类型的大小。

所以是不行的,正确的自引用方式如下:

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

注意:

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

在定义指针变量next时,使用了Node类型。由于Node类型的定义在当前代码中尚未完成,编译器无法识别Node类型

正确的如下:

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

5.结构体变量的定义和初始化 

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};//单独结构体嵌套初始化

 6.结构体传参

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);
	printf("%d\n", s.data[0]);
	printf("%d\n", s.data[1]);
	printf("%d\n", s.data[2]);
}
//结构体地址传参
void print2(struct S* ps)
{
	printf("%d\n", ps->num);
	printf("%d\n", ps->data[0]);
	printf("%d\n", ps->data[1]);
	printf("%d\n", ps->data[2]);
}
int main()
{
	print1(s); //传结构体变量
	printf("_________");
	print2(&s); //传地址
	return 0;
}

两种调用,传参的结果都是一样的:

 但是,还是用printf2来传地址时更好:

函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
如果传递一个结构体对象的时候, 结构体过大,参数压栈的的系统开销比较大 ,所以会导致性能的下降

 二.结构体内存对齐  

struct S1
{
     char c1 ;
     int i ;
     char c2 ;
};     这个结构体有多大呢? 
第一反应大抵是:1+4+1=6吧

 但其实:

 

 1.对其规则

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

 按照对其规则,这是上述答案的分析:

接下来再看另外一个例子: 

eg:

struct S2
{
	char c1;
	char c2;
	int i;
};

int main()
{
	printf("%d", sizeof(struct S2));
	return 0;
}

答案便是:

2.存在原因

结构体的内存对齐是拿空间来换取时间的做法

一些资料也显示说:

  1. 访问效率:内存对齐可以使结构体的成员在内存中连续存储,减少内存访问次数,提高访问效率。如果结构体成员没有进行内存对齐,可能会导致成员之间存在空隙,需要多次访问内存才能获取到所有成员的值
  2. 数据对齐:某些硬件平台要求访问特定类型的数据必须按照特定字节对齐,否则可能会导致访问错误或性能下降。例如,某些处理器要求访问双字节数据(如short类型)必须按2字节对齐,访问四字节数据(如int类型)必须按4字节对齐。如果结构体成员没有进行内存对齐,可能会导致访问错误或性能下降

 

3.减少浪费 

那我们如何尽量减少内存对齐所产生的浪费呢?

让占用 空间小的成员尽量集中 在一起

所以上述S1的例子我们可以改造成:

struct S1
{
 char c1;
 int i;
 char c2;
};
struct S2
{
 char c1;
 char c2;
 int i;
};
int main()
{
	printf("%d\n", sizeof(struct S1));
	printf("%d", sizeof(struct S2));
	return 0;
}

结果也确实减小了内存使用:

4.修改默认对齐数

 这里我们使用#pragma,可以改变我们的默认对齐数

#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;
};



int main()
{
	printf("%d\n", sizeof(struct S1));
	printf("%d", sizeof(struct S2));
	return 0;
}

这次S2的大小便是我们最初认为的6了:


三.位段 

1.什么是位段

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

  1. 位段的成员必须是 intunsigned int signed int
  2. 位段的成员名后边有一个冒号和一个数字
struct A {
	int a : 2; //a占用2个bit位
	int b : 5; //b占用5个bit位
	int c : 10;
	int d : 30;
};

int main()
{
	struct A a;
	printf("%d", sizeof(a));
}

不使用位段的话是占16个字节,现在是:

 

 

2.位段的内存分配

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

 

struct S
{
	char a : 3;
	char b : 4;
	char c : 5;
	char d : 4;
};

int main()
{
	struct S s = { 0 };
	s.a = 10;
	s.b = 12;
	s.c = 3;
	s.d = 4;
	return 0;
}

3.位段的跨平台问题 

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

    这次关于结构体相关的内容就先到这里啦!                                                                           在下一篇文章中,我们将详细介绍枚举和联合体的内容。感谢大家的支持,加油!!!

 

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

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

相关文章

Jmeter之处理session、cookie以及如何做关联

session和cookie的概念 按照我的理解就是: cookie保持你访问的权限信息。 session限制你访问权限信息的有效时间,一旦过期就不能在访问了,比如说我们经常遇到了,很长一段时间网页没有去操作,就会自动退出登陆。你要…

ZooKeeper下载、安装、配置和使用

天行健,君子以自强不息;地势坤,君子以厚德载物。 每个人都有惰性,但不断学习是好好生活的根本,共勉! 文章均为学习整理笔记,分享记录为主,如有错误请指正,共同学习进步。…

node-red常用包分析

node-red-contrib-opcua Use OpcUa-Item to define variables. Use OpcUa-Client to read / write / subscribe / browse OPC UA server. 需要想通过OpcUa-Item节点来指定一个数据点。 触发器-->opcua_item----->opcua_client opcua_client的Action项解析: …

LVS负载均衡集群+NAT部署

集群的概念和目的 集群的定义 Cluster,集群(也称群集)由多台主机构成,但对外只表现为一一个整体,只提供一-个访问入口(域名或IP地址), 相当于一台大型计算机。 集群的作用 对于企业服务的的性能提升一般…

感觉真的要被淘汰了,工作3年,不懂自动化,看着公司的新员工都是自动化岗....

这两天和朋友谈到软件测试的发展:这一行的变化确实蛮大,从开始最基础的功能测试,到现在自动化、性能、安全乃至于以后可能出现的大数据测试、AI测试岗位需求逐渐增多。我也在软件测试这行摸爬滚打了十年了,正好有朋友问我&#xf…

最全面的msvcp140_atomic_wait.dll丢失的解决方法,教你怎么快速修复dll文件

在众多电脑使用者中,碰到过"msvcp140_atomic_wait.dll文件丢失"这样的问题的人并不鲜见。突如其来的错误提示常让人感到困扰,但无需过于焦虑,因为这类问题多数都有成熟的解决方案。在这篇文章中,我们将深入探讨如何针对…

Codeforces Round #905(Div.3)

A. Morning 题目 给定4位数字码,每位数字取值0-9。排列顺序如下: 初始光标指向1,每次可执行其中一个操作 1、输出光标所指数字 2、移动光标到相邻位置上。如3可移动到2或4,其中1只能移动到2,0只能移动到9。 问&…

离线电商数仓(一)

一、数据仓库概述 1. 数据仓库 数据仓库是一个为数据分析而设计的企业级数据管理系统。数据仓库可集中、整合多个数据源的大量数据,企业可以从数据仓库中获取宝贵数据进行决策。 数据分类:业务数据、日志数据 将这两种数据从业务系统采集到Hive中&…

(yum+内网)centos7两种方式安装jdk11

一、yum在线安装 需要提前配置yum源。 搜索可安装的版本,可以看到有1.6、1.7、1.8、11共4个版本 yum search openjdk 安装jdk11 yum -y install java-11-openjdk 验证 java -version 二、内网离线安装 需要提前下载安装包。 安装包下载地址 https://www.or…

爬虫一般采用什么代理IP,Python爬虫代理IP使用方法详解

在进行网络爬虫开发时,使用代理IP是一种常见的技术手段,可以帮助爬虫程序实现更高效、稳定和隐秘的数据抓取。本文将介绍爬虫一般采用的代理IP类型,并详细解释Python爬虫中使用代理IP的方法。 一般来说,爬虫采用以下几种代理IP类型…

类模板Array带二个模板参数

#include <ostream> #include <iostream> using namespace std; //Array.h template <typename T, int size> class Array{ public: Array(); // 也算是默认构造函数&#xff0c;因为不需要传进去参数 bool push(T elem); void display(); priv…

Nacos全面知识 ----微服务 SpringCloud

快速入门 分级存储模型 修改集群配置 Nacos设置负载均衡策略 集群优先 权重优先 Nacos热更新配置 Nacos添加配置信息 微服务配置拉取 热更新:推荐使用第二种方法进行热部署 ConfigurationProperties(prefix "pattern") 是 Spring Boot 中用于自动配置属性的注解。它…

用“价值”的视角来看安全:《构建新型网络形态下的网络空间安全体系》

文章目录 写在前面安全认知的硬核概念威胁的演化路径与发展趋势构建网络空间安全体系好书推荐 写作末尾 写在前面 网络空间安全体系是建立在先进技术、严密监控和综合策略之上的综合性框架&#xff0c;旨在保护网络免受恶意攻击、数据泄露和网络犯罪的威胁&#xff0c;其核心包…

【代码随想录第48天】动态规划7

代码随想录第48天| 动态规划7 322. 零钱兑换279.完全平方数 322. 零钱兑换 LeetCode题目&#xff1a; 322. 零钱兑换 代码随想录&#xff1a;322. 零钱兑换 给你一个整数数组 coins &#xff0c;表示不同面额的硬币&#xff1b;以及一个整数 amount &#xff0c;表示总金额。 计…

Failed to start The nginx HTTP and reverse proxy server.

本章教程主要分享一下&#xff0c;当nginx 启动时&#xff0c;遇到报这个错误时的一个解决问题思路。 目录 1、观察报错信息 2、尝试性解决 1、观察报错信息 根据日志的信息&#xff0c;我们至少可以知道2个比较信息。 1、操作用户执行命令是在非root权限下进行操作的。 2、Ad…

NPI加速器在烽火科技SMT车间的应用:贴片机程序制作效率的革新

烽火科技&#xff0c;一个在国内颇具知名度的高科技企业&#xff0c;坐落于武汉光谷的SMT车间中&#xff0c;机器嗡嗡作响&#xff0c;作业员们忙碌地进行着生产。工厂使用的是ASM的贴片机&#xff0c;使用Sipalce Pro作为其编程软件。然而&#xff0c;在高效的生产线背后&…

面试算法37:小行星碰撞

题目 输入一个表示小行星的数组&#xff0c;数组中每个数字的绝对值表示小行星的大小&#xff0c;数字的正负号表示小行星运动的方向&#xff0c;正号表示向右飞行&#xff0c;负号表示向左飞行。如果两颗小行星相撞&#xff0c;那么体积较小的小行星将会爆炸最终消失&#xf…

使用 git revert 回退代码

步骤一&#xff1a;查看提交历史 git log 这会列出你的提交历史&#xff0c;包括提交的哈希值、作者、日期和提交消息。 退出日志&#xff1a;按键盘上的 Q 即可。 步骤二&#xff1a;使用 git revert 回退代码 找到你需要回退的版本号&#xff0c;即提交的哈希值&#xff…

vue3中刷新当前页面的三种方法

<div click"kannoFn">kanno</div> 1.location.reload()&#xff0c;缺点&#xff1a;刷新时会页面闪白 function kannoFn(){location.reload() } 2.history.go(0)&#xff0c;缺点&#xff1a;刷新时会页面闪白 function kannoFn(){history.go(0) } …

法国橡木与美国橡木:酒桶如何影响葡萄酒?

木材赋予葡萄酒神奇的质地、香气和味道&#xff0c;这是其他任何方式都无法实现的。大多数世界上最好的葡萄酒都是在桶中陈酿的&#xff0c;法国和美国的橡木是最常用的木材&#xff0c;还有来自匈牙利、罗马尼亚和高加索山脉的橡木&#xff0c;其他木材&#xff0c;如金合欢&a…