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

news/2024/5/19 0:27:54/文章来源:https://blog.csdn.net/2301_81988400/article/details/137242541

引言:

在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类型的变量stureturn 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重命名为studentint 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 bytestruct 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语言中的应用非常广泛,掌握了结构体的基本用法及内存对齐规则,有助于我们写出更加高效、灵活的程序代码。

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

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

相关文章

网络编程--高并发服务器(二)

这里写目录标题 线程池高并发服务器UDP服务器TCP与UDP机制的对比TCP与UDP优缺点比较UDP的C/S模型实现思路模型分析实现思路&#xff08;对照TCP的C/S模型&#xff09; recvfrom函数、sendto函数 一级目录二级目录二级目录二级目录 一级目录二级目录二级目录二级目录 一级目录二…

C++引用与指针比较

引子&#xff1a; 问题&#xff1a; 指针指向变量必须类型一致&#xff08;int对int*类型指针&#xff09;&#xff0c;这样计算&#xff0c;解引用才能得到正确的结果&#xff0c;那引用也是如此吗&#xff1f; 回答&#xff1a;&#xff08;常引用&#xff09; 从语法来说…

MyBatis主要的类层次结构(Mybatis工具类)

MyBatis主要的类层次结构 每一个MyBatis的应用程序都以一个SqlSessionFactory 对象的实例为核心 。 SqlSessionFactory对象的实例可以通过SqlSessionFactoryBuilder对象来获得 。 SqlSessionFactoryBuilder对象可以从 XML 配置文件中构建 SqlSessionFactory对象。 package…

RN实现全局数据共享(非Redux,使用原生内置的方法实现)

下面这个方法是在RN使用全局数据共享的,使用原生React的方式搞得,相对于Redux配置相对简单,适合小型项目 项目内创建MyContext.js // MyContext.jsimport React from react;const MyContext React.createContext();export default MyContext;App.js引入 // App.jsimport Rea…

【opencv】教程代码 —features2D(7)根据单应性矩阵估计相机坐标系下的物体位姿...

pose_from_homography.cpp从图像中找到棋盘角点并进行姿态估计 从图像中找到棋盘角点并显示 计算角点在世界坐标系中的位置 读取相机内参和畸变系数并校正图像中的角点 计算从3D点到2D点的单应性矩阵 通过奇异值分解(SVD)优化对旋转矩阵的估计 基于单应矩阵分解及其优化结果&am…

HarmonyOS 应用开发之LifecycleForm接口切换LifecycleApp接口切换 LifecycleApp接口切换

LifecycleForm接口切换 FA模型接口Stage模型接口对应d.ts文件Stage模型对应接口onCreate?(want: Want): formBindingData.FormBindingData;ohos.app.form.FormExtensionAbility.d.tsonAddForm(want: Want): formBindingData.FormBindingData;onCastToNormal?(formId: string…

Java复习第十一天学习笔记(IO流),附有道云笔记链接

【有道云笔记】十一 3.27 IO流 https://note.youdao.com/s/PeEdd3Zo 一、IO介绍以及分类 IO: Input Output 流是一组有顺序的&#xff0c;有起点和终点的字节集合&#xff0c;是对数据传输的总称或抽象。即数据在两设备间的传输称为流&#xff0c;流的本质是数据传输&#x…

无人机编队 | 基于自适应航迹评价函数权重的动态窗口法长机-僚机法实现多无人机路径规划附matlab代码

基本概述 实现基于自适应航迹评价函数权重的动态窗口法(Dynamic Window Approach, DWA)的长机-僚机(Leader-Follower)多无人机路径规划是一个复杂的任务,涉及到多个算法的组合与改进。这里我会简要介绍其原理,并提供一个基础的Matlab代码框架,但请注意,这只是一个起点…

如何恢复被.locked勒索病毒加密的服务器和数据库?

.locked勒索病毒有什么特点&#xff1f; .locked勒索病毒的特点主要包括以下几个方面&#xff1a; 文件加密&#xff1a;.locked勒索病毒会对受感染设备上的所有文件进行加密&#xff0c;包括图片、文档、视频和其他各种类型的重要文件。一旦文件被加密&#xff0c;文件的扩展…

Day14_学点CSS_高级选择器Demo

1 后代选择器s1 s2 <!--~ 适度编码益脑&#xff0c;沉迷编码伤身&#xff0c;合理安排时间&#xff0c;享受快乐生活。~ Copyright TangXJ~ Created by TangXJ~ Created&Used date: 2024/3/29 下午3:46 ~ 2024/3/29 下午3:47~ Modified date: 2024/3/29 下午3:47-->…

C语言 | Leetcode C语言题解之3题无重复字符的最长子串

题目&#xff1a; 题解&#xff1a; int lengthOfLongestSubstring(char * s) {//类似于hash的思想//滑动窗口维护int left 0;int right 0;int max 0;int i,j;int len strlen(s);int haveSameChar 0;for(i 0; i < len ; i ){if(left < right){ //检测是否出现重…

OpenHarmony实战开发-图案密码锁组件的使用

介绍 本示例展示了图案密码锁组件的使用&#xff0c;实现了密码设置、验证和重置功能。 图案密码锁组件&#xff1a;以宫格图案的方式输入密码&#xff0c;用于密码验证。手指触碰图案密码锁时开始进入输入状态&#xff0c;手指离开屏幕时结束输入状态并向应用返回输入的密码…

STM32学习和实践笔记(4): 分析和理解GPIO_InitTypeDef GPIO_InitStructure (b)

继续上篇博文&#xff1a;STM32学习和实践笔记&#xff08;4&#xff09;: 分析和理解GPIO_InitTypeDef GPIO_InitStructure (a)-CSDN博客 往下写&#xff0c; 为什么&#xff1a;当GPIO_InitStructure.GPIO_PinGPIO_Pin_0 ; 时&#xff0c;其实就是将对应的该引脚的寄存器地…

docker--部署 (超详版) (五)

环境准备&#xff1a;docker&#xff0c;mysql&#xff0c;redis&#xff0c;镜像&#xff0c;nginx 把虚拟机打开&#xff0c;连接xshell&#xff0c;参考博客&#xff1a; https://blog.csdn.net/m0_74229802/article/details/136965820?spm1001.2014.3001.5501 一&#x…

HCIP作业4

实验步骤&#xff1a; 第一步 给PC1和PC2和PC3配地址 第二步给R1到R5配置接口IP地址 [R1]int g0/0/0 [R1-GigabitEthernet0/0/0]ip ad 192.168.1.254 24 R1&#xff1a;[R1-GigabitEthernet0/0/0]int s4/0/0 [R1-Serial4/0/0]ip ad 15.1.1.1 24 [R1-Serial4/0/0]dis ip in…

Flutter 开发学习笔记(0):环境配置

文章目录 前言开发需求环境配置运行出现问题我运行也是解决了很久的问题镜像源设置为清华的镜像源&#xff08;不知道有没有影响&#xff09;使用JDK17&#xff0c;测试过JDK21和JDK11都不行手动下载flutter 对应的gradle添加阿里云代理安卓编译下载 运行成功&#xff01; 前言…

基于Springboot的一站式家装服务管理系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的一站式家装服务管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体…

2024年MathorCup数学建模思路A题B题C题D题思路分享

文章目录 1 赛题思路2 比赛日期和时间3 组织机构4 建模常见问题类型4.1 分类问题4.2 优化问题4.3 预测问题4.4 评价问题 5 建模资料 1 赛题思路 (赛题出来以后第一时间在CSDN分享) https://blog.csdn.net/dc_sinor?typeblog 2 比赛日期和时间 报名截止时间&#xff1a;2024…

css设置文字铺满盒子

<div>收货人</div>&#xff1a; <div>电话</div>&#xff1a; <div>省市区</div>&#xff1a; width: 100rpx;border: 1px solid rebeccapurple;display: inline-block;text-align-last: justify;

构建安全高效的用户登录系统:登录流程设计与Token验证详解

在当今数字化时代&#xff0c;用户登录系统是几乎所有在线服务的基础。然而&#xff0c;随着网络安全威胁的不断增加&#xff0c;设计一个安全可靠的登录系统变得至关重要。本文将深入探讨用户登录流程的设计原则以及Token验证的实现方式&#xff0c;带您了解如何构建安全高效的…