【C语言 | 预处理】C语言预处理详解(三)——内存对齐、手把手带你计算结构体大小

news2024/11/18 22:39:36

😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀
🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭
🤣本文内容🤣:🍭介绍内存对齐、手把手教你计算结构体大小🍭
😎金句分享😎:🍭🍭

本文未经允许,不得转发!!!

目录

  • 🎄一、内存对齐是什么?
  • 🎄二、为什么需要内存对齐?
  • 🎄三、计算结构体大小
    • ✨3.1 对齐参数
    • ✨3.2 计算结构体大小的步骤和例子
  • 🎄四、#pragma pack 的使用方法
    • ✨4.1 用法一:#pragma pack (n)、#pragma pack ()
    • ✨4.2 用法二:#pragma pack(push)、#pragma pack(n)、#pragma pack(pop)
  • 🎄五、总结


在这里插入图片描述

🎄一、内存对齐是什么?

内存对齐(Memory Alignment) 是指将数据存储在内存中时,是按照特定的规则将数据放置在地址为其大小倍数的位置上。具体而言,内存对齐要求变量的起始地址是它对齐参数的整数倍

看下面这两个结构体,看看按照内存对齐的要求,是怎么存储的:

struct Test1
{
	char c1;
	short s;
	char c2;
	int i;
};

struct Test2
{
	char c1;
	char c2;
	short s;
	int i;
};

两个结构体虽然结构体成员一样,但他们所占用的内存大小却不一样,Test1占用12个字节,Test2占用8个字节,他们在内存中的存储大致如下图:
看看struct Test1按照内存对齐要求是怎样安排的,假设结构体首地址为0:

  • 成员 c1 自身大小为 1 个字节,是结构体第一成员,所以直接在地址0
  • 成员 s 自身大小为 2 个字节,按照要求,其起始地址必须是 2 的整数倍,所以不能放在地址1 的位置,起始地址为地址2
  • 成员 c1 自身大小为 1 个字节,起始地址需要为 1 的整数倍,直接安排在地址4
  • 成员 i 自身大小为 4 个字节,起始地址需要为 4 的整数倍,从地址5 开始往后找,地址8是4的整数倍。
    在这里插入图片描述

在这里插入图片描述

🎄二、为什么需要内存对齐?

计算机处理器为了访问未对齐的内存,处理器需要作两次内存访问;然而,对齐的内存访问仅需要一次访问。

所以,内存对齐的主要目的是提高访问数据的效率。当数据按照规定的对齐方式存放在内存中时,处理器可以更快地读取和存储数据,而不需要执行额外的内存操作。未对齐的数据可能导致性能下降,甚至在某些架构中导致程序崩溃。

所以,默认情况下,编译器都会将结构、栈中的成员数据进行内存对齐。

在这里插入图片描述

🎄三、计算结构体大小

按照内存对齐的要求,结构体在内存中是怎么存储的?这小节介绍复杂结构体各个成员在内存中怎么存储?

这里的复杂结构体是指包含了结构体和数组的结构体。先看看下面结构体ST_2,你能清楚它各个成员的在结构体中的偏移量吗?

typedef struct st
{
	char c;		// 起始地址 0; 结束地址 1
	int  i;		// 起始地址 4; 结束地址 8
	double d;	// 起始地址 8; 结束地址 16
}ST; // 结构体大小 16 字节

typedef struct st2
{
	char c;         
	int  i;        
	ST   st;       
	double d;       
	char c2;       
	ST   st_arr[3];
	long l;     
	int  i_arr[9]; 
}ST2;

✨3.1 对齐参数

在计算结构体大小之前,先了解几个概念:

  • 对齐参数:编译器进行内存对齐时,会涉及到一个对齐参数,不同的对齐参数,计算出来的结构体大小会不一样。可以通过下面代码查看编译器的默认对齐参数,(并非"32位系统就是4,64位系统为8");
    // default_align.c
    // gcc default_align.c -std=c11
    #include <stdio.h>
    #include <stddef.h>
    int main(void)
    {
    	printf("Default alignment: %zu\n", __alignof__(max_align_t));
    	printf("Biggest alignment: %d\n", __BIGGEST_ALIGNMENT__);
    	return 0;
    }
    
  • 基础数据类型的对齐方式:对齐参数就是类型大小;
  • 数组的对齐方式:对齐参数就是单个数组元素的大小。比如:char a[3];它的对齐方式和分别写 3 个 char 是一样的;也就是说它还是按 1 个字节对齐。
  • 结构体的对齐方式:对齐参数就是成员中的最大对齐参数。比如:上面的结构体 ST ,对齐参数就是成员d的对齐参数 8。

✨3.2 计算结构体大小的步骤和例子

计算结构体大小的步骤(计算各个成员的地址):

  • 1、确定成员对齐参数:成员的对齐参数是 自身对齐参数系统对齐参数中较小的一个。
  • 2、确定成员起始地址:起始地址为成员对齐参数的整数倍;
  • 3、确定结构体大小:最终结构体大小必须是最大对齐参数的整数倍。

在这里插入图片描述
按照步骤计算上面 ST2 结构体大小,假设结构体起始地址为0,系统对齐参数为 4,系统是32位系统:

  • 第一个成员 c:位于结构体第一个,起始地址为 0,结束地址为 1;

  • 第二个成员 i:自身对齐参数为 4 (sizeof(int)=4),等于系统对齐参数 4,所以成员对齐参数为 4
    起始地址为:从上个成员结束地址1开始数,找到4的整数倍,结果为 4,所以起始地址为 4,结束地址为 8;

  • 第三个成员 st:自身对齐参数为 8 (ST结构体最大对齐参数为8),大于系统对齐参数 4,所以成员对齐参数为 4
    起始地址为:从上个成员结束地址8开始数,找到4的整数倍,结果为 8,所以起始地址为 4,结束地址为 24;

  • 第四个成员 d:自身对齐参数为 8 (sizeof(double)=8),大于系统对齐参数 4,所以成员对齐参数为 4
    起始地址为:从上个成员结束地址24开始数,找到4的整数倍,结果为 24,所以起始地址为 24,结束地址为 32;

  • 第五个成员 c2:自身对齐参数为 1 (sizeof(char)=1),小于系统对齐参数 4,所以成员对齐参数为 1
    起始地址为:从上个成员结束地址32开始数,找到1的整数倍,结果为 32,所以起始地址为 32,结束地址为 33;

  • 第六个成员 st_arr:是一个结构体数组,对齐参数就是单个数组元素的对齐参数,数组元素又是一个结构体ST,按照该结构体最大对齐参数8作为自身对齐参数,大于系统对齐参数 4,所以成员对齐参数为 4
    起始地址为:从上个成员结束地址33开始数,找到4的整数倍,结果为 36,所以起始地址为 36,结束地址为 84;

  • 第七个成员 l:自身对齐参数为 4 (sizeof(double)=4),等于系统对齐参数 4,所以成员对齐参数为 4
    起始地址为:从上个成员结束地址84开始数,找到4的整数倍,结果为 84,所以起始地址为 84,结束地址为 88;

  • 第八个成员 i_arr:是一个结构体int型数组,数据元素是int类型的,所以自身对齐参数为 4 (sizeof(int)=4),等于系统对齐参数 4,所以成员对齐参数为 4
    起始地址为:从上个成员结束地址88开始数,找到4的整数倍,结果为 88,所以起始地址为 88,结束地址为 124;

计算完成员地址后,找出最大对齐参数,这个例子是 4,目前内存存储到124个字节,是4的整数倍,所以结构体ST2的大小是 124。

可以使用下面的代码验证是否计算正确:

// memAlign.c
// gcc memAlign.c -o memAlign
#include <stdio.h>
#include <stddef.h>

#pragma pack (4)
typedef struct st
{
	char c;		// 起始地址 0; 结束地址 1
	int  i;		// 起始地址 4; 结束地址 8
	double d;	// 起始地址 8; 结束地址 16
}ST; // 结构体大小 16 字节

typedef struct st2
{
	char c;         
	int  i;        
	ST   st;       
	double d;       
	char c2;       
	ST   st_arr[3];
	long l;     
	int  i_arr[9]; 
}ST2;
#pragma pack ()

int main(void)
{
	printf("c:%zu i:%zu st:%zu d:%zu c2:%zu st_arr:%zu l:%zu i_arr:%zu\n", 
		offsetof(ST2, c), offsetof(ST2, i), offsetof(ST2, st), 
		offsetof(ST2, d), offsetof(ST2, c2), offsetof(ST2, st_arr), 
		offsetof(ST2, l), offsetof(ST2, i_arr));
	return 0;
}

在这里插入图片描述

🎄四、#pragma pack 的使用方法

#pragma pack 可以用来改变编译器的默认对齐方式,也就是改变上文提到的系统对齐参数;

#pragma pack(n) 的n只能是2的次方幂,目前测试了,n的值可以为1、2、3、8、16,当设置32时会报错。

✨4.1 用法一:#pragma pack (n)、#pragma pack ()

使用指令#pragma pack (n),编译器将按照 n 个字节对齐。
使用指令#pragma pack (),编译器将取消自定义字节对齐方式。
用法可以参考下面代码,表示从#pragma pack (4)开始到#pragma pack ()之间的代码的系统对齐参数是4:

#pragma pack (4)
typedef struct st
{
	char c;		// 起始地址 0; 结束地址 1
	int  i;		// 起始地址 4; 结束地址 8
	double d;	// 起始地址 8; 结束地址 16
}ST; // 结构体大小 16 字节

typedef struct st2
{
	char c;         
	int  i;        
	ST   st;       
	double d;       
	char c2;       
	ST   st_arr[3];
	long l;     
	int  i_arr[9]; 
}ST2;
#pragma pack ()

✨4.2 用法二:#pragma pack(push)、#pragma pack(n)、#pragma pack(pop)

#pragma pack(push):保存当前对其方式到 packing stack;
#pragma pack(n):设置编译器按照 n 个字节对齐;
#pragma pack(pop):packing stack 出栈,并设置为对齐参数;
用法参考下面代码:

#pragma pack (push)	// 保存现在的对齐参数 
#pragma pack (4)	// 将对齐参数改为 4
typedef struct st
{
	char c;		// 起始地址 0; 结束地址 1
	int  i;		// 起始地址 4; 结束地址 8
	double d;	// 起始地址 8; 结束地址 16
}ST; // 结构体大小 16 字节

typedef struct st2
{
	char c;         
	int  i;        
	ST   st;       
	double d;       
	char c2;       
	ST   st_arr[3];
	long l;     
	int  i_arr[9]; 
}ST2;
#pragma pack (pop)	// 恢复之前保存的对齐参数

在这里插入图片描述

🎄五、总结

本文介绍内存对齐,也解释了为什么需要内存对齐,最后演示了一个结构体是怎样按照计算占用内存大小的。

在这里插入图片描述
如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁

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

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

相关文章

2023年加氢工艺证考试题库及加氢工艺试题解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2023年加氢工艺证考试题库及加氢工艺试题解析是安全生产模拟考试一点通结合&#xff08;安监局&#xff09;特种作业人员操作证考试大纲和&#xff08;质检局&#xff09;特种设备作业人员上岗证考试大纲随机出的加氢…

DDU框架学习之路

目录 MVVM对比 DDU 数据消费者UI 数据的转换者&#xff1a;Domain Layer 数据图生产者/提供者 DataLayer 遵循原理&#xff1a; 单一数据流&#xff1a; Android官方推荐架构&#xff1a;DDU MVVM对比 M&#xff1a;Model 网络层 用于获取远端数据 VM:ViewModel 中间转…

【Shell脚本9】Shell test 命令

Shell test 命令 Shell中的 test 命令用于检查某个条件是否成立&#xff0c;它可以进行数值、字符和文件三个方面的测试。 数值测试 num1100 num2100 if test $[num1] -eq $[num2] thenecho 两个数相等&#xff01; elseecho 两个数不相等&#xff01; fi输出结果&#xff1a…

关于Android Studio 同步Gradle失败的解决方案

&#xff08;1&#xff09;打开Android Studio的Settings找到Gradle的目录 &#xff08;2&#xff09;打开本地文件目录&#xff0c;找到对应的gradle版本&#xff0c;可以通过Index of /gradle/ 下载gradle压缩包。把目录中gradle-7.0.2-bin\一堆字符\ 下 的.lck 和.part文…

Oracle(16)Managing Privileges

目录 一、基础知识 1、Managing Privileges管理权限 2、System Privileges 系统特权 3、System Privileges : Example系统权限&#xff1a;示例 4、Who Can Grant or Revoke? 谁可以授予或撤销权限&#xff1f; 5、The PUBLIC 6、SYSDBA and SYSOPER 7、Revoke with A…

KT6368A蓝牙芯片的出现部分芯片距离短换芯片就好是什么问题呢

一、简介 KT6368A蓝牙芯片的出现部分芯片距离短&#xff0c;换一个芯片距离就好了&#xff0c;是什么问题呢&#xff1f;生产2K的样子 详细说明 按照我们出货客户的跟踪情况&#xff0c;这种问题&#xff0c;可能性极低因为芯片本身的不良率&#xff0c;目前是控制在千分之三…

js 求数组中的对象某个属性和

可以直接看下效果 代码&#xff1a; <script>let list [{num: 1,price: 10,},{num: 2,price: 10,},{num: 3,price: 10,},{num: 4,price: 10,},]// for循环 求总数和 num的和let num 0for (let i 0; i < list.length; i) {num list[i].num}console.log(第一种&am…

深度学习 python opencv 火焰检测识别 计算机竞赛

文章目录 0 前言1 基于YOLO的火焰检测与识别2 课题背景3 卷积神经网络3.1 卷积层3.2 池化层3.3 激活函数&#xff1a;3.4 全连接层3.5 使用tensorflow中keras模块实现卷积神经网络 4 YOLOV54.1 网络架构图4.2 输入端4.3 基准网络4.4 Neck网络4.5 Head输出层 5 数据集准备5.1 数…

《红蓝攻防对抗实战》九.内网穿透之利用GRE协议进行隧道穿透

​ 前文推荐&#xff1a; 《红蓝攻防对抗实战》一. 隧道穿透技术详解 《红蓝攻防对抗实战》二.内网探测协议出网之TCP/UDP协议探测出网 《红蓝攻防对抗实战》三.内网探测协议出网之HTTP/HTTPS协议探测出网 《红蓝攻防对抗实战》四.内网探测协议出网之ICMP协议探测出网 《红蓝…

AI:86-基于深度学习的人体姿态估计与运动分析

🚀 本文选自专栏:人工智能领域200例教程专栏 从基础到实践,深入学习。无论你是初学者还是经验丰富的老手,对于本专栏案例和项目实践都有参考学习意义。 ✨✨✨ 每一个案例都附带有在本地跑过的代码,详细讲解供大家学习,希望可以帮到大家。欢迎订阅支持,正在不断更新中,…

数据结构线性表——带头双向循环链表

前言&#xff1a;小伙伴们好久不见啦&#xff0c;上篇文章我们一起学习了数据结构线性表其一的单链表&#xff0c;了解了单链表的不少好处&#xff0c;但是不可能有完美的数据结构&#xff0c;就算是单链表&#xff0c;也会有很多缺点。 那么今天这篇文章&#xff0c;我们就来…

全网最细,Apipost接口自动化测试-关联配置,老鸟带你上高速...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 在接口自动化测试…

Arduino到底适不适合做产品

文章目录 一、Arduino性能很低&#xff0c;不如树莓派等开发板&#xff0c;所以不要用Arduino做开发二、Arduino程序效率很低&#xff0c;所以不要用Arduino做开发三、Arduino只能开发玩具&#xff0c;不能做产品四、Arduino开发板成本太高&#xff0c;不适合做产品总结个人见解…

iPhone或在2024开放第三方应用商店。

iPhone或开放第三方应用商店&#xff0c;可以说这是一个老生常谈的话题。对于像是iOS这样封闭的系统来说&#xff0c;此前传出苹果可能开放侧载消息的时候&#xff0c;又有谁能信&#xff0c;谁会信&#xff1f; 如果是按照苹果自身的意愿&#xff0c;这种事情自然是不可能发生…

【LeetCode笔试题】88.合并两个有序数组

问题描述 给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2&#xff0c;另有两个整数 m 和 n &#xff0c;分别表示 nums1 和 nums2 中的元素数目。 请你 合并 nums2 到 nums1 中&#xff0c;使合并后的数组同样按 非递减顺序 排列。 注意&#xff1a;最终&#xff0c;合…

王学岗visibility改变后调用onLayout()

自定义控件的时候发现了一个bug。 Button位移动画执行结束后我设置了一个不相关的TextView的可见性由gone变为visible.令人郁闷的是&#xff0c;只要我注释的地方放开。动画执行结束后button都会重新绘制在位移动画开始的位置。注释掉这段代码就正常。 经过分析后得知 View的Vi…

python OrderedDict类(有序字典)

嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! python更多源码/资料/解答/教程等 点击此处跳转文末名片免费获取 创建有序字典 import collectionsdic collections.OrderedDict() dic[k1] v1 dic[k2] v2 dic[k3] v3 print(dic)#输出&#xff1a;OrderedDict([(k1, v1), (…

Vatee万腾科技决策力的未来展望:开创数字化创新的新高度

随着科技不断演进&#xff0c;Vatee万腾的科技决策力在数字化创新领域展现出了强大的潜力和前瞻性。 Vatee万腾的科技决策力被视为数字化创新的引擎&#xff0c;为未来创新注入了新的动力。通过深刻的市场洞察和科学决策&#xff0c;Vatee万腾致力于推动数字化创新走向新的高度…

图论11-欧拉回路与欧拉路径+Hierholzer算法实现

文章目录 1 欧拉回路的概念2 欧拉回路的算法实现3 Hierholzer算法详解4 Hierholzer算法实现4.1 修改Graph&#xff0c;增加API4.2 Graph.java4.3 联通分量类4.4 欧拉回路类 1 欧拉回路的概念 2 欧拉回路的算法实现 private boolean hasEulerLoop(){CC cc new CC(G);if(cc.cou…

可视化 | 3D文字球状标签云

文章目录 &#x1f4da;改编点&#x1f4da;final 改编自echarts 3d词云&#xff08;指向滑动、拖动、缩放、点击、自转 &#xff09; &#x1f4da;改编点 背景透明&#xff1a;background:rgb(0,0,0,0);不用链接&#xff0c;用span&#xff0c;重点span标class"star&q…