深入了解C语言中的结构体类型与内存对齐

引言:

在C语言中,结构体是一种自定义的数据类型它允许我们将不同类型的数据组合在一起,形成一个新的数据类型。结构体的使用为我们解决了一些复杂数据的表示和处理问题,不仅限于单单的整型或者字符。本文将深入探讨结构体类型、结构体变量的创建和初始化,并详细介绍结构体中的内存对齐规则。

1.结构体类型

结构体类型是由不同类型的数据成员组成的集合,其中每个数据成员可以是任意类型的数据,包括基本数据类型、数组、指针、其他结构体等。结构体类型的定义使用关键字"struct"。

1.1 结构体的声明

形式如下:

struct tag
{
	member-list; // 成员列表
};

举例:描述一个学生

struct student
{
	char name[20]; // 名字 
 	int age; // 年龄 
 	char sex[5]; // 性别
 	char id[20]; // 学号
}; // 注意分号不要遗忘

1.2 结构体的创建和初始化

  • 结构体可以在声明的同时创建
struct student
{
	char name[20]; // 名字 
 	int age; // 年龄 
 	char sex[5]; // 性别
 	char id[20]; // 学号
}stu; // 这样就在声明的同时创建了一个struct student类型的变量stu
  • 结构体也可以声明完后再创建
struct student
{
	char name[20]; // 名字 
 	int age; // 年龄 
 	char sex[5]; // 性别
 	char id[20]; // 学号
};

int main()
{
	struct student stu; // 这样也是创建了一个struct student类型的变量stu
	return 0;
}
  • 结构体的初始化
struct Stu
{
	char name[20];//名字 
	int age;//年龄 
	char sex[5];//性别 
	char id[20];//学号 
};

int main()
{
	//按照结构体成员的顺序初始化 
	struct Stu s = { "张三", 20, "男", "20230818001" };
	printf("name: %s\n", s.name);
	printf("age : %d\n", s.age);
	printf("sex : %s\n", s.sex);
	printf("id : %s\n", s.id);
	
	//按照指定的顺序初始化 
	struct Stu s2 = { .age = 18, .name = "lisi", .id = "20230818002", .sex = "女" };
	printf("name: %s\n", s2.name);
	printf("age : %d\n", s2.age);
	printf("sex : %s\n", s2.sex);
	printf("id : %s\n", s2.id);
	return 0;
}
  • 输出结果:
    在这里插入图片描述

1.3 访问结构体成员

  • 结构体指针变量成员的访问:
struct student
{
	char name[20]; // 名字 
	int age; // 年龄 
	char sex[5]; // 性别
	char id[20]; // 学号
};

int main()
{
	struct student* stu = (struct student*)malloc(sizeof(struct student));
	stu->age = 18;
	printf("age : %d\n", stu->age); // 只有指针能用箭头访问成员
	(*stu).age = 20;
	printf("age : %d\n", (*stu).age); // 必须要加(),因为.的优先级高于*
	return 0;
}
  • 输出结果:
    在这里插入图片描述
    只有指针能用箭头->访问成员

  • 非指针的结构体成员的访问
struct student
{
	char name[20]; // 名字 
	int age; // 年龄 
	char sex[5]; // 性别
	char id[20]; // 学号
};

int main()
{
	struct student stu;
	stu.age = 18;
	printf("age : %d\n", stu.age);
	return 0;
}
  • 输出结果:
    在这里插入图片描述
    非指针的结构体只能通过.访问成员

1.4 小技巧

当我们需要多次使用结构体时,我们可以通过重命名结构体变量来减少代码量

  • 可以在声明结构体类型的时候重命名:
typedef struct student
{
	char name[20]; // 名字 
 	int age; // 年龄 
 	char sex[5]; // 性别
 	char id[20]; // 学号
}student;

int main()
{
	student stu; // 这样创建的变量是和struct student一个类型的,但是可以一定程度减少代码量
	return 0;
}
  • 也可以声明后重命名
struct student
{
	char name[20]; // 名字 
 	int age; // 年龄 
 	char sex[5]; // 性别
 	char id[20]; // 学号
};

typedef struct student student; // 将struct student重命名为student

int main()
{
	student stu; // 这样创建的变量是和struct student一个类型的,但是可以一定程度减少代码量
	return 0;
}

1.5 结构的特殊声明

  • 在声明结构的时候,可以不完全的声明。
//匿名结构体类型 
struct
{
	int a;
	char b;
	float c;
}x;

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

上⾯的两个结构在声明的时候省略掉了结构体标签(tag)。

那么此时有一个问题,如下的代码合法吗?

p = &x;
  • 警告:
    • 编译器会把上⾯的两个声明当成完全不同的两个类型,所以是⾮法的。
    • 匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使⽤⼀次。

1.6 结构体的自引用

在链表和二叉树中常常用到这样一种结构:

typedef struct Node
{
	struct Node* next; // 结构体指针可以指向下一个节点
	int data;
}Node;

当我们对结构体指针next赋值时,我们就可以通过next找到它指向的下一个结构体变量节点

  • 注意:
    • 以下形式是非法的:
typedef struct Node
{
	Node* next; // 此时struct Node 还没有重命名为Node ,所以不能使用Node自引用
	int data;
}Node;

2.结构体内存对齐

我们已经掌握了结构体的基本使⽤了。
现在我们深⼊讨论⼀个问题:计算结构体的⼤⼩
这也是⼀个特别热⻔的考点: 结构体内存对⻬

2.1 对齐规则

⾸先得掌握结构体的对⻬规则:

1.结构体的第⼀个成员对⻬到和结构体变量起始位置偏移量为 0 的地址处

2.其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。

  • 对⻬数 = 编译器默认的⼀个对⻬数与该成员变量⼤⼩的较⼩值。
  • VS中对齐数默认的值为 8
  • Linux中gcc没有默认对⻬数,对⻬数就是成员⾃⾝的⼤⼩

3.结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的整数倍。

4.如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。

2.1.1 内存对齐例题

下面例题均在VS环境下

题目1

struct S1 // 计算该结构体大小
{
	char c1;
	int i;
	char c2;
};

解析:


在这里插入图片描述


题目2

struct S2 // 计算该结构体大小
{
	char c1;
	char c2;
	int i;
};

解析:


在这里插入图片描述


题目3

struct S3 // 计算该结构体大小
{
	double d;
	char c;
	int i;
};

解析:


在这里插入图片描述


题目4

struct S4 // 计算该结构体大小
{
	char c1;
	struct S3 s3; // 题目3中的结构体
	double d;
};

解析:


在这里插入图片描述


2.2 为什么存在内存对⻬?

大部分的参考资料都是这样说的:

  1. 平台原因(移植原因):
    不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

  2. 性能原因:
    数据结构(尤其是栈)应该尽可能地在⾃然边界上对⻬。原因在于,为了访问未对⻬的内存,处理器需要作两次内存访问;⽽对⻬的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取 8 个字节,则地址必须是 8 的倍数。如果我们能保证将所有的double类型的数据的地址都对⻬成 8 的倍数,那么就可以⽤⼀个内存操作来读或者写值了。否则,我们可能需要执⾏两次内存访问,因为对象可能被分放在两个 8 字节内存块中。

  • 总体来说:结构体的内存对⻬是拿空间来换取时间的做法

  • 所以我们在设计结构体的时候,尽量让占⽤空间⼩的成员尽量集中在⼀起,这样既可以满⾜对⻬,⼜可以节省空间。

例如:

struct S1
{
	char c1;
	int i;
	char c2;
}; // 12 byte

struct S2
{
	char c1;
	char c2;
	int i;
}; // 8 byte

S1 和 S2 类型的成员⼀模⼀样,但是 S2 占的空间比 S1 要小。


2.3 修改默认对⻬数

结构体在对⻬⽅式不合适的时候,我们可以⾃⼰更改默认对⻬数。

#pragma 这个预处理指令,可以改变编译器的默认对⻬数。

#include <stdio.h>
#pragma pack(1)//设置默认对⻬数为1 

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

#pragma pack()//取消设置的对⻬数,还原为默认 

int main()
{
	//输出的结果是什么? 
	printf("%d\n", sizeof(struct S));
	return 0;
}
  • 输出结果:
    在这里插入图片描述
    如果不修改默认对齐数,则结果为 12 。

3.结构体传参

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 函数。

原因:

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

结论:结构体传参的时候,要传结构体的地址

结语:

通过本文的介绍,我们了解到了C语言中结构体类型的定义和使用方法,以及结构体变量的创建和初始化。同时,我们还详细讲解了结构体中存在的内存对齐规则,帮助我们更好地理解结构体在内存中的存储方式。结构体在C语言中的应用非常广泛,掌握了结构体的基本用法及内存对齐规则,有助于我们写出更加高效、灵活的程序代码。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

金三银四上岸!2024最新Java高频面试题总结(附答案PDF)!

《Java面试全解析》1000道 面试题大全详解&#xff08;针对于应届生、三到五年经验、六到十年经验&#xff01;&#xff09; 本人是 2009 年参加编程工作的&#xff0c;一路上在技术公司摸爬滚打&#xff0c;前几年一直在上海&#xff0c;待过的公司有 360 和游久游戏&#xf…

Mysql数据库getshell方法

今天摸鱼时候&#xff0c;突然有人问我不同的数据库getshell的方式&#xff0c;一时间我想到了mysql还有redis未授权访问到getshell的方式&#xff0c;但是仅仅第一时间只想到了这两种&#xff0c;我有查了查资料&#xff0c;找到了上面两种数据库getshell的补充&#xff0c;以…

python实现泊松回归

1 什么是基于计数的数据&#xff1f; 基于计数的数据包含以特定速率发生的事件。发生率可能会随着时间的推移或从一次观察到下一次观察而发生变化。以下是基于计数的数据的一些示例&#xff1a; 每小时穿过十字路口的车辆数量每月去看医生的人数每月发现的类地行星数量 计数数…

Mysql 常用SQL语句

1、查看mysql中所有的数据库&#xff0c; show databases; 2、创建库 create database 库名;&#xff08;也可以用 create database if not exists 库名; 表示如果库不存在再创建&#xff09; 例&#xff1a;create database if not exists ecology; 3、删除库 …

Linux安全加固

账号和权限 系统用户 超级管理员&#xff1a;UID0 系统默认用户&#xff1a;系统程序使用&#xff0c;从不登录 新增普通用户&#xff1a;UID大于500 用户管理 添加用户&#xff1a;useradd <用户名> 删除用户&#xff1a;userdel [-r] [-f] <用户名> 锁定/解…

electron 打包生成的latest.yml文件名字变成xxx.yml文件名

正常情况是electron每次打包会生成一个latest.yml文件和一个xxx.exe文件&#xff0c;但是当version的名字修改成 这样 后面添加了-beta &#xff0c;然后生成的文件名字就变成了 beta.yml 更改方法&#xff1a; 在build配置底下添加 "detectUpdateChannel": false…

我的C++奇迹之旅:值和引用的本质效率与性能比较

文章目录 &#x1f4dd;引用&#x1f320;引用概念&#x1f309;引用特性 &#x1f320;使用场景&#x1f309;做参数&#xff08;传值与传地址&#xff09;&#x1f309;传值、传引用效率比较 &#x1f320;引用做返回值&#x1f309;引用和指针的区别 &#x1f320;常引用&am…

坚持刷题|分发饼干

文章目录 题目思路代码实现实现总结主要步骤时间复杂度 扩展问题 Hello&#xff0c;大家好&#xff0c;我是阿月。坚持刷题&#xff0c;老年痴呆追不上我&#xff0c;今天刷第一个贪心算法&#xff1a;分发饼干 题目 455.分发饼干 思路 要解决这个问题&#xff0c;可以使用…

深度学习实战73-基于多模态CLIP模型的实战项目,CLIP模型的架构介绍与代码实现

大家好,我是微学AI,今天给大家介绍一下深度学习实战73-基于多模态CLIP模型的实战项目,CLIP模型的架构介绍与代码实现。多模态CLIP(Contrastive Language-Image Pre-training)模型是一种深度学习模型,其核心设计理念是通过大规模的对比学习训练,实现图像与文本之间的跨模…

Linux——进程管理

目录 作业和进程的概念 程序与进程的关系 查看进程信息——ps&#xff0c;top ps命令 top命令 设置进程的优先级——nice&#xff0c;renice nice命令 renice命令 查看进程信息——pgrep&#xff0c;pstree pgrep命令 pstree命令 切换进程——jobs&#xff0c;bg&a…

【linux】基础IO(一)

文件只有站在系统层面才能彻底理解 简单回顾一下文件&#xff1a; 首先我们要明确一点&#xff0c;我们说的打开文件不是写下fopen就打开文件&#xff0c;而是当我们的进程运行起来&#xff0c;进程打开的文件。 我们在C语言一般都会使用过如下的代码进行向文件中写入 但是除…

Oracle客户端如何连接远程数据库?

Oracle是一种常用的数据库管理系统&#xff0c;它具有高效、稳定的特性&#xff0c;广泛应用于各行各业。为了实现远程数据库的连接&#xff0c;我们可以使用Oracle客户端工具。本文将介绍如何使用Oracle客户端连接远程数据库&#xff0c;并讨论其使用场景。 Oracle客户端工具 …

sql中如何添加数据?

添加 在 SQL 中添加数据通常使用INSERT语句。INSERT语句用于将新的数据行插入到数据库表中。 基本的INSERT语句语法如下&#xff1a; INSERT INTO table_name (column1, column2,...) VALUES (value1, value2,...);其中&#xff1a; INSERT INTO&#xff1a;指定要插入数据…

利用Flutter的特性最大程度提升iOS应用的用户体验

本文探讨了使用Flutter开发的iOS应用能否上架&#xff0c;以及上架的具体流程。苹果提供了App Store作为正式上架渠道&#xff0c;同时也有TestFlight供开发者进行内测。合规并通过审核后&#xff0c;Flutter应用可以顺利上架。但上架过程可能存在一些挑战&#xff0c;因此可能…

SSM框架学习——MVC模式与三层架构

MVC模式与三层架构 什么是MVC模式 MVC模式代表Model-View-Controller&#xff08;模型-视图-控制器&#xff09;模式。这种应用模式用于应用程序的分层开发。 Model代表存取数据的对象&#xff0c;它自身可带有逻辑&#xff0c;数据变化时更新Controller。View代表Model包含…

最牛的音乐大模型-suno 音乐界的ChatGPT

一、前言 之前一直对音乐是无感的&#xff0c;但随着 suno.ai 大火&#xff0c;开始喜欢上了音乐&#xff0c;喜欢上了音乐创作的 二、suno介绍 2.1 基本介绍 基于 Suno 任何人都可以创作美妙音乐。无论您是歌手还是艺术家抑或是对音乐一无所知的人&#xff0c;suno都会打破…

STM32(1):系统架构地址映射

STM32&#xff08;1&#xff09;&#xff1a;系统架构&地址映射 前提摘要 个人说明&#xff1a; 限于时间紧迫以及作者水平有限&#xff0c;本文错误、疏漏之处恐不在少数&#xff0c;恳请读者批评指正。意见请留言或者发送邮件至&#xff1a;“Email:noahpanzzzgmail.com…

基因组Survey分析

流程图&#xff1a; 图片来源&#xff1a;https://www.jianshu.com/p/94da86093843 一、Fastp质控 二、NT比对 一般选择第六个输出格式 结果示例&#xff1a; 三、k-mer分析 软件&#xff1a;GCE/genomescope 分析目的&#xff1a;预估基因组大小&#xff0c;重复序列比…

团聚金刚石研磨液为高性能研磨抛光材料 中机新材是国内领先供应商

团聚金刚石研磨液为高性能研磨抛光材料 中机新材是国内领先供应商 团聚金刚石研磨液是一种高性能研磨抛光材料&#xff0c;是以团聚金刚石微粉为原料采用特殊配方配制而成。团聚金刚石微粉是由金刚石微粉、粘结剂经特殊工艺处理集合而成的球形磨料。金刚石微粉制备方式多样&am…

4年经验来面试20K的测试岗,一问三不知,我还真不如去招应届生。

公司前段缺人&#xff0c;也面了不少测试&#xff0c;结果竟然没有一个合适的。一开始瞄准的就是中级的水准&#xff0c;也没指望来大牛&#xff0c;提供的薪资在10-20k&#xff0c;面试的人很多&#xff0c;但平均水平很让人失望。看简历很多都是4年工作经验&#xff0c;但面试…