在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
这个问题困扰许久,查了很多资料,网上众说纷纭。整理例如以下: 一.内存对齐的初步解说 内存对齐能够用一句话来概括: “数据项仅仅能存储在地址是数据项大小的整数倍的内存位置上(分别为偶地址、被4整除的地址、被8整除的地址 比如int类型占用4个字节,地址仅仅能在0,4,8等位置上。 例1: #include <stdio.h> int main() return 0; 运行结果例如以下: &a = ffbff5ec 会发现b与a之间空出了3个字节,也就是说在b之后的0xffbff5e9,0xffbff5ea,0xffbff5eb空了出来,a直接存储在了0xffbff5ec, 由于a的大小是4,仅仅能存储在4个整数倍的位置上。打印xx的大小会发现,是16,有些人可能要问。b之后空出了3个字节,那也应该是13啊?其余的3个 呢?这个往后阅读本文会理解的更深入一点,这里简单说一下就是d后边的3个字节,也会浪费掉。也就是说。这3个字节也被这个结构体占用了. 能够简单的改动结构体的结构,来减少内存的使用。比如能够将结构体定义为: 这样打印这个结构体的大小就是12。省了非常多空间,能够看出。在定义结构体的时候,一定要考虑要内存对齐的影响,这样能使我们的程序占用更小的内存。 二.操作系统的默认对齐系数 在默认情况下。为了方便对结构体和类中数据元素的訪问和管理,当结构体或类内的数据元素的长度都小于机器字长时,就以结构体或类中最长的数据元素为对齐单位。也就是说,结构体或类的长度一定是最长数据元素的整数倍。假设结构体或类内存在长度大于机器字长的数据元素,那么就以机器的字长为对齐单位。 结构体或类中类型同样的连续元素将在连续的空间。和数组保持一致。 比如:(机器字长为32位) 同一时候VC为了确保结构的大小为结构的字节边界数(即该结构中占用最大空间的类型所占用的字节数)的倍数,所以在为最后一个成员变量申请空间后。还会依据须要自己主动填充空缺的字节。 每 个操作系统都有自己的默认内存对齐系数,假设是新版本号的操作系统,默认对齐系数一般都是8,由于操作系统定义的最大类型存储单元就是8个字节,比如 long long(为什么一定要这样。在第三节会解说)。不存在超过8个字节的类型(比如int是4,char是1。long在32位编译时是4,64位编译时是 8)。当操作系统的默认对齐系数与第一节所讲的内存对齐的理论产生冲突时,以操作系统的对齐系数为基准。 比如: 如果操作系统的默认对齐系数是4,那么对与long long这个类型的变量就不满足第一节所说的,也就是说long long这样的结构,能够存储在被4整除的位置上,也能够存储在被8整除的位置上。 能够通过#pragma pack()语句改动操作系统的默认对齐系数,编敲代码的时候不建议改动默认对齐系数,在第三节会解说原因 例2: #include <stdio.h> int main() return 0; &a = ffbff5e4 发现占用8个字节的a,存储在了不能被8整除的位置上。存储在了被4整除的位置上,採取了操作系统的默认对齐系数。 三.内存对齐产生的原因 内存对齐是操作系统为了高速訪问内存而採取的一种策略,简单来说,就是为了放置变量的二次訪问。操作系统在訪问内存 时,每次读取一定的长度(这个长度就是操作系统的默认对齐系数,或者是默认对齐系数的整数倍)。假设没有内存对齐时,为了读取一个变量是。会产生总线的二 次訪问。 比如如果没有内存对齐。结构体xx的变量位置会出现例如以下情况: struct xx{ 操作系统先读取0xffbff5e8-0xffbff5ef的内存,然后在读取0xffbff5f0-0xffbff5f8的内存,为了获得值c。就须要将两组内存合并,进行整合,这样严重减少了内存的訪问效率。 (这就涉及到了老生常谈的问题,空间和效率哪个更重要?这里不做讨论)。 这样大家就能理解为什么结构体的第一个变量,无论类型怎样。都是能被8整除的吧(由于訪问内存是从8的整数倍開始的,为了添加读取的效率)!
内存对齐的问题主要存在于理解struct等复合结构在内存中的分布。 首先要明确内存对齐的概念。
这个k在不同的cpu平台下。不同的编译器下表现也有所不同。比方32位字长的计算机与16位字长的计算机。这个离我们有些远了。 我们的开发主要涉及两大平台,windows和linux(unix)。涉及的编译器也主要是microsoft编译器(如cl),和gcc。 内存对齐的目的是使各个基本数据类型的首地址为相应k的倍数,这是理解内存对齐方式的终极法宝。另外还要区分编译器的分别。明确了这两点基本上就能搞定全部内存对齐方面的问题。 不同编译器中的k:
明确了以上的说明对struct等复合结构的内存分布就应该非常清楚了。 以下看一下最简单的一个类型:struct中成员都为基本数据类型,比如: 在windows平台。microsoft编译器下: 如果从0地址開始。首先a的k值为1。它的首地址能够使任何位置。所以a占用第一个字节,即地址0。然后b的k值为2,他的首地址必须是2的倍数,不能是1,所以地址1那个字节被填充,b首地址为地址2,占用地址2,3。然后到c,c的k值为4,他的首地址为4的倍数,所以首地址为4,占用地址4。5。6,7;再然后到d。d的k值也为4,所以他的首地址为8。占用地址8,9,10,11。最后到e,他的k值为8,首地址为8的倍数,所以地址12,13,14,15被填充,他的首地址应为16。占用地址16-23。显然其大小为24。 这就是 test1在内存中的分布情况。我们建立一个test1类型的变量,a、b、c、d、e分别赋值2、4、8、16、32。 然后从低地址依次打印出内存中每一个字节相应的16进制数为: 验证: 在linux平台。gcc编译器下: 验证: 显然判断也是正确的。 接下来,看一看几类特殊的情况,为了避免麻烦,不再描写叙述内存分布,仅仅计算结构大小。 第一种:嵌套的结构 在windows平台,microsoft编译器下: 这样的情况下假设把test2的第二个成员拆开来,研究内存分布。那么能够知道。test2的成员f占用地址0。g.a占用地址1,以后的内存分布不变。仍然满足全部基本数据成员的首地址都为其相应k的倍数这一原则。那么test2的大小就还是24了。可是实际上test2的大小为32,这是由于:不能由于test2的结构而改变test1的内存分布情况,所以为了使test1种各个成员仍然满足对齐的要求,f成员后面须要填充一定数量的字节,不难发现,这个数量应为7个。才干保证test1的对齐。所以test2相对于test1来说添加了8个字节,所以test2的大小为32。 在linux平台,gcc编译器下: 相同。这样的情况下假设把test2的第二个成员拆开来,研究内存分布,那么能够知道,test2的成员f占用地址0,g.a占用地址1,以后的内存分布不变,仍然满足全部基本数据成员的首地址都为其相应k的倍数这一原则,那么test2的大小就还是20了。可是实际上test2的大小为24。相同这是由于:不能由于test2的结构而改变test1的内存分布情况,所以为了使test1种各个成员仍然满足对齐的要求。f成员后面须要填充一定数量的字节。不难发现。这个数量应为3个,才干保证test1的对齐。所以test2相对于test1来说添加了4个字节,所以test2的大小为24。 另外一种:位段对齐 struct test3 在windows平台。microsoft编译器下: 相邻的多个同类型的数(带符号的与不带符号的,仅仅要基本类型同样,也为同样的数),假设他们占用的位数不超过基本类型的大小,那么他们可作为一个总体来看待。不同类型的数要遵循各自的对齐方式。 而且a与b的值在内存中从低位開始依次排列,位于4字节区域中的前0-3位和4-7位 假设test4位下面格式 如过test5是下面形式 那么因为int和char不同类型。他们分别以各自的方式对齐。所以test5的大小应为8字节,a与b的值分别位于第一个4字节的前4位和第5个字节的前4位。 在linux平台。gcc编译器下: struct test3
a。b的值在内存中依次排列分别为第一个四字节中的0-3和4-7位。 假设test4位下面格式 那么test5的大小应为4字节,a。b的值为0-9位和10-13位。c存放在后两个字节中。假设a的大小变成了20 struct test6 此时,test6的a、b共占用0,1。2共3字节,c的k值为2,事实上能够4位首位置。可是在结构外。a要以int的方式对齐。也就是说连续两个test6对象在内存中存放的话,a的首位置要保证为4的倍数,那么c后面必须多填充2位。所以test6的大小为8个字节。 内存对齐 - 小 楼 一 夜 听 春 雨 - 博客园 结构体和类的内存分配-点滴积累-搜狐博客 C语言之内存对齐小析 - WinLin - 博客频道 - CSDN.NET
|
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论