理解内存对齐

2024-09-05 02:12
文章标签 内存 理解 对齐

本文主要是介绍理解内存对齐,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

序言

 相信大家已经看过不少关于如何计算内存对齐的文章了,但是大家大家有思考过吗,为什么需要内存对齐?为什么要做浪费内存资源的事?直接让数据挨在一起不就行了吗?本篇文章将简单介绍为什么需要内存对齐,以及理解了之后,我们再来看内存对齐。


1. 为什么需要内存对齐

 当我们在看一本英语书时,对于熟悉的单词,我们在大脑里面会迅速的反应,并将它翻译为对应的中文含义。但是,当一个单词出现如下情况:

diffe
rence

乍一看,我们会稍微反应一下,然后大脑会反应出,哦原来这是 difference 。当一本书中这种情况多了,就会大大降低我们的阅读效率!

 内存大家并不陌生,但是当内存和 CPU 进行数据交换时,一次交换的数据大小是一个字节吗?不是的,通常来说,是以一个字(32位下 4 字节,64位下 8 字节)来进行数据交换。再结合一段程序(本文都是 32位下)能更好的说明:

struct stu1
{char a; // 1int b; // 4
};int main()
{std::cout << sizeof(stu1) << std::endl;return 0;
}

如果不内存对齐的话,这个程序的输出结果是 5!内存上是这样的:
在这里插入图片描述

一个字长大小为 4 字节,如果 CPU 现在需要成员变量 b,需要读取两次,第一次读取获取高三个字节,最后一次获取一个字节。但是通过内存对齐后,是这样的:
在这里插入图片描述
这只需要读取一次就好啦!

 大家会认为内存读取数据的速度很快呀,读取两次和一次差不多吧?对 CPU 来说,简直是天差地别!在他眼里就一直等待… 所以为了加快 CPU 获取数据的速度,使用了内存对齐 — 加快访问速度,提高 CPU 执行效率!利用空间换取时间!


2. 内存对齐的规则

2.1 默认对齐数

 当每一次对齐时,我们需要将该数据的大小和默认对齐数进行比较,使用 较小的那一个作为对齐数! 默认对齐数设置方式也很简单,#pragma pack(n) 指令可以将默认的对齐数设置为 n,其中 n 可以是 1、2、4、8、16 等值,具体取决于编译器的支持。这意味着结构体中的成员将按照 n 字节对齐,而不是编译器默认的对齐方式。
 举个栗子:

#pragma pack(8) // 对齐数为 8struct stu1
{char a;int b;
};int main()
{std::cout << sizeof(stu1) << std::endl; // 输出结果 8return 0;
}
-------------------------------------手动分割线#pragma pack(1) // 对齐数为 1struct stu1
{char a;int b;
};int main()
{std::cout << sizeof(stu1) << std::endl; // 输出结果 5return 0;
}

 有些时候,虽然效率是加快了,但是浪费空间的缺点也不容小觑,对于对内存高度紧张的环境,我们就可以适当调节默认对齐数的大小。

2.2 只包含内置类型的场景

 在这个场景下结构体中只包含内置类型,默认对齐数为 4

struct stu1
{int a; // 4char b; // 1double c; // 8short d; // 2long long e; // 8
};int main()
{std::cout << sizeof(stu1) << std::endl;return 0;
}

具体怎么计算的呢,在这里我将介绍我自己使用的方法:

  1. a 开始,int 为 4 字节,和默认对齐数取小也是 4,从初始地址 1 开始占 4 个字节,现在地址更新到 4;
  2. bchar 为 1 字节,和默认对齐数取小是 1,不需要对齐,从地址 4 开始占 1 个字节,现在地址更新到 5;
  3. cdouble 为 8 字节,和默认对齐数取小是 4,需要对齐,到最近往后 4 的整数倍,从地址 8 开始占 8 个字节,现在地址更新到 16;
  4. dshort 为 2 字节,和默认对齐数取小是 2,不需要对齐,从地址 16 开始占 2 个字节,现在地址更新到 20;
  5. elong long 为 8 字节,和默认对齐数取小是 4,不需要对齐,从地址 20 开始占 8 个字节,现在地址更新到 28;
  6. 最后整个结构体的大小为 最大类型 和 默认对齐数 取小 的整数倍,最大类型为 long long 8 字节,默认对齐数取小是 4;28 已经是 4 的整数倍,所以结果是 28。

我们看看程序的输出:
在这里插入图片描述

2.3 内嵌结构体的场景

 对于内嵌结构体的场景,我们需要多一项关注 该结构体开始的位置需要是该结构体的最大类型(也要和默认对齐数取小)的整数倍!

#pragma pack(4)struct stu2
{int* ptr; // 4char ch; // 1
};struct stu1
{int a; // 4char b; // 1stu2 st; long long c; // 8
};int main()
{std::cout << sizeof(stu1) << std::endl;return 0;
}

在这里我们先计算 stu2 结构体的大小:

  1. ptr 开始,int* 为 4 字节,和默认对齐数取小也是 4,从初始地址 1 开始占 4 个字节,现在地址更新到 4;
  2. chchar 为 1 字节,和默认对齐数取小是 1,不需要对齐,从地址 4 开始占 1 个字节,现在地址更新到 5;
  3. 最后整个结构体的大小为 最大类型 和 默认对齐数 取小 的整数倍,最大类型为 int* 4 字节,默认对齐数取小是 4;5 不是 4 的整数倍,所以到最近往后 4 的整数倍 8

所以该结构体的大小就是 8 字节。

现在我们开始计算 stu1 结构体的大小:

  1. a 开始,int 为 4 字节,和默认对齐数取小也是 4,从初始地址 1 开始占 4 个字节,现在地址更新到 4;
  2. bchar 为 1 字节,和默认对齐数取小是 1,不需要对齐,从地址 4 开始占 1 个字节,现在地址更新到 5;
  3. ststu2 为 8 字节,该结构体最大类型和默认对齐数取小是 4,需要对齐,到最近往后 4 的整数倍,从地址 8 开始占 8 个字节,现在地址更新到 16;
  4. clong long 为 8 字节,和默认对齐数取小是 4,不需要对齐,从地址 16 开始占 8 个字节,现在地址更新到 24;
  5. 最后整个结构体的大小为 最大类型 和 默认对齐数 取小 的整数倍,最大类型为 long long 8 字节,默认对齐数取小是 4;24 已经是 4 的整数倍,所以结果是 24。

2.4 类中的其他成员

 我们还需要关注在类中的其他成员比如,静态成员函数,成员函数:

struct stu1
{char a;int b;static int c;void Print() {}
};int main()
{std::cout << sizeof(stu1) << std::endl;return 0;
}

在这里如果不存在后两者,stu1 的大小肯定是 8,加上两者呢?
在这里插入图片描述

怎么还是 8 呀?这是因为 成员函数和静态成员变量不独属于任何一个对象,他们属于这个类!sizeof 是计算该对象的大小,自然不算这两者!

2.5 包含虚函数的类

 请记住包含虚函数的类,还额外包含一个隐含的成员变量 — 虚函数表指针位于对象首部,他的对齐方式也很特殊 和类中的最大类型一致!在这里就不做演示了,大家可以自己尝试计算一下(依旧在 32 位下,但是默认对齐数产生了变化):

#pragma pack(8)struct stu1
{char a;long long b;virtual void Print() {}
};int main()
{std::cout << sizeof(stu1) << std::endl;return 0;
}

3. 总结

 在这里我们简单说了一下问什么需要内存对齐?以及内存对齐的相应场景,希望大家有所收获!

这篇关于理解内存对齐的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1137674

相关文章

NameNode内存生产配置

Hadoop2.x 系列,配置 NameNode 内存 NameNode 内存默认 2000m ,如果服务器内存 4G , NameNode 内存可以配置 3g 。在 hadoop-env.sh 文件中配置如下。 HADOOP_NAMENODE_OPTS=-Xmx3072m Hadoop3.x 系列,配置 Nam

认识、理解、分类——acm之搜索

普通搜索方法有两种:1、广度优先搜索;2、深度优先搜索; 更多搜索方法: 3、双向广度优先搜索; 4、启发式搜索(包括A*算法等); 搜索通常会用到的知识点:状态压缩(位压缩,利用hash思想压缩)。

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝

深入理解RxJava:响应式编程的现代方式

在当今的软件开发世界中,异步编程和事件驱动的架构变得越来越重要。RxJava,作为响应式编程(Reactive Programming)的一个流行库,为Java和Android开发者提供了一种强大的方式来处理异步任务和事件流。本文将深入探讨RxJava的核心概念、优势以及如何在实际项目中应用它。 文章目录 💯 什么是RxJava?💯 响应式编程的优势💯 RxJava的核心概念

如何通俗理解注意力机制?

1、注意力机制(Attention Mechanism)是机器学习和深度学习中一种模拟人类注意力的方法,用于提高模型在处理大量信息时的效率和效果。通俗地理解,它就像是在一堆信息中找到最重要的部分,把注意力集中在这些关键点上,从而更好地完成任务。以下是几个简单的比喻来帮助理解注意力机制: 2、寻找重点:想象一下,你在阅读一篇文章的时候,有些段落特别重要,你会特别注意这些段落,反复阅读,而对其他部分

深入理解数据库的 4NF:多值依赖与消除数据异常

在数据库设计中, "范式" 是一个常常被提到的重要概念。许多初学者在学习数据库设计时,经常听到第一范式(1NF)、第二范式(2NF)、第三范式(3NF)以及 BCNF(Boyce-Codd范式)。这些范式都旨在通过消除数据冗余和异常来优化数据库结构。然而,当我们谈到 4NF(第四范式)时,事情变得更加复杂。本文将带你深入了解 多值依赖 和 4NF,帮助你在数据库设计中消除更高级别的异常。 什么是

JVM内存调优原则及几种JVM内存调优方法

JVM内存调优原则及几种JVM内存调优方法 1、堆大小设置。 2、回收器选择。   1、在对JVM内存调优的时候不能只看操作系统级别Java进程所占用的内存,这个数值不能准确的反应堆内存的真实占用情况,因为GC过后这个值是不会变化的,因此内存调优的时候要更多地使用JDK提供的内存查看工具,比如JConsole和Java VisualVM。   2、对JVM内存的系统级的调优主要的目的是减少

JVM 常见异常及内存诊断

栈内存溢出 栈内存大小设置:-Xss size 默认除了window以外的所有操作系统默认情况大小为 1MB,window 的默认大小依赖于虚拟机内存。 栈帧过多导致栈内存溢出 下述示例代码,由于递归深度没有限制且没有设置出口,每次方法的调用都会产生一个栈帧导致了创建的栈帧过多,而导致内存溢出(StackOverflowError)。 示例代码: 运行结果: 栈帧过大导致栈内存

分布式系统的个人理解小结

分布式系统:分的微小服务,以小而独立的业务为单位,形成子系统。 然后分布式系统中需要有统一的调用,形成大的聚合服务。 同时,微服务群,需要有交流(通讯,注册中心,同步,异步),有管理(监控,调度)。 对外服务,需要有控制的对外开发,安全网关。