内存对齐问题的完美解释
- 格式:doc
- 大小:38.50 KB
- 文档页数:8
c++内存中字节对齐问题详解一、什么是字节对齐,为什么要对齐?现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
对齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同。
一些平台对某些特定类型的数据只能从某些特定地址开始存取。
比如有些架构的CPU 在访问一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失。
比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数据。
显然在读取效率上下降很多。
二、请看下面的结构:struct MyStruct{double dda1;char dda;int type};对结构MyStruct采用sizeof会出现什么结果呢?sizeof(MyStruct)为多少呢?也许你会这样求:sizeof(MyStruct)=sizeof(double)+sizeof(char)+sizeof(int)=13但是当在VC中测试上面结构的大小时,你会发现sizeof(MyStruct)为16。
你知道为什么在VC中会得出这样一个结果吗?其实,这是VC对变量存储的一个特殊处理。
为了提高CPU的存储速度,VC对一些变量的起始地址做了“对齐”处理。
在默认情况下,VC规定各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数。
下面列出常用类型的对齐方式(vc6.0,32位系统)。
内存字节对齐方式与实例在计算机中,内存字节对齐是指数据在内存中存储时按照一定规则对齐到内存地址的一种方式。
内存字节对齐的原理是为了提高数据在存储和访问时的效率,因为现代计算机的内存通常是按字节寻址的,即每个内存单元都有一个唯一的地址。
内存字节对齐方式主要有两种:强制对齐和自然对齐。
强制对齐是指数据在存储时必须按照某个特定的字节倍数对齐,比如4字节或8字节对齐。
而自然对齐是指数据在存储时采用自然的对齐方式,即数据按照自身的大小进行对齐。
在内存字节对齐时,有些数据类型需要按照特定的字节对齐方式存储,以确保数据在内存中的正确存储和访问。
比如,在32位系统中,整型数据通常要求按4字节对齐,双精度浮点数要求按8字节对齐。
下面以一个实例来说明内存字节对齐的具体应用:假设我们有一个结构体如下所示:```Cstruct Student {int id;char name[20];float score;};```其中,id是学生的学号,name是学生的姓名,score是学生的分数。
我们可以看到,结构体中包含了不同类型的数据。
当我们创建一个Student对象时,编译器会根据结构体内各个变量的大小和对齐要求,决定这个对象占用内存的大小。
在这个例子中,id是一个int类型,占用4个字节;name是一个char数组,占用20个字节;score是一个float类型,占用4个字节。
根据内存字节对齐的规则,编译器通常会按照结构体内最大类型的大小来进行对齐。
在这个例子中,最大的类型是float,占用4个字节,因此整个结构体的大小应为4的倍数。
因此,该结构体占用的内存大小为4 + 20 + 4 = 28个字节。
如果我们按照强制对齐规则,将结构体的对齐方式设置为4字节对齐,那么结构体的大小将是32个字节,其中,id占4个字节,name占20个字节,score占8个字节(在4字节对齐下,score由4个字节扩展为8个字节)。
通过这个例子,我们可以看到在数据结构中,合理的内存字节对齐方式不仅可以减小内存的浪费,提高内存的利用率,还可以提高数据的读写效率。
通过内存对齐提高程序性能在编写程序时,我们不仅要考虑代码的逻辑正确性,还需要关注程序的性能。
优化程序性能的一种方法是通过内存对齐来提升程序的运行效率。
本文将介绍什么是内存对齐,以及如何利用内存对齐来提高程序的性能。
一、什么是内存对齐内存对齐是指数据在内存中的存储位置与其大小的关系。
在计算机的内存中,数据存储是按照字节的方式进行的,只有进行了对齐的数据,才能够被高效地读取和写入。
如果数据没有进行对齐,计算机需要进行额外的处理,这就会降低程序的性能。
二、内存对齐的原理内存对齐的原理很简单。
计算机处理数据时,通常以“字”为单位进行操作。
一个字的大小是与计算机的体系结构有关的,例如在32位系统中,一个字的大小为4字节;而在64位系统中,一个字的大小为8字节。
对于一个变量来说,如果其大小小于一个字,那么它在内存中的存储位置应该与它的大小一致。
例如,一个整型变量的大小为4字节,在内存中的存储位置应该是4的倍数。
如果一个变量的大小大于一个字,那么它的存储位置需要满足多个字的对齐要求。
三、如何进行内存对齐在C/C++等编程语言中,可以通过特定的语法来控制变量的对齐方式。
在结构体和类中,通过使用#pragma pack指令或者__attribute__((aligned(n)))来指定对齐方式。
例如,可以使用#pragma pack(4)来指定结构体或类的成员按照4字节对齐,或者使用__attribute__((aligned(8)))来指定成员按照8字节对齐。
在编写程序时,需要特别关注结构体的对齐方式。
结构体在内存中的存储位置是根据其成员变量的大小和对齐方式来确定的。
如果结构体的成员变量没有进行合适的对齐,就会导致冗余空间的产生,从而降低了内存的利用率。
四、内存对齐的优势内存对齐对于提高程序性能具有以下几个优势:1. 提高访问效率:内存对齐可以保证数据在内存中的存储位置与其大小的对应关系,这样就可以减少计算机进行访问和操作的步骤,从而提高程序的运行效率。
内存对齐是一种优化程序性能的重要手段。
它可以使得数据在内存中的存储更加紧凑和高效,减少内存访问的次数和开销,从而提高程序的运行速度。
本文将从什么是内存对齐、为何需要内存对齐以及如何通过内存对齐提高程序性能等方面展开论述。
一、什么是内存对齐内存对齐是指内存中的数据在存储时按照一定的规则对齐,如按字节对齐、按字对齐等。
在现代计算机中,数据访问通常以字节为单位进行,而内存对齐能够使得数据的存储地址整除数据类型的大小。
例如,一个int类型的变量通常占用4个字节,内存对齐能够保证它存储的地址是4的倍数,而不是随机的地址。
二、为何需要内存对齐内存对齐的主要目的是提高数据存取的效率。
当数据按照字节对齐存储时,CPU在访问内存时无需额外的计算和操作,可以直接通过内存地址来获取数据,加快访问速度。
相反,如果数据没有对齐存储,CPU就需要进行额外的位移和掩码操作,这会造成额外的时间和开销。
三、内存对齐的原则1. 基本类型的变量,如int、float,通常按照其本身的大小进行对齐。
例如,一个int类型的变量通常按照4字节对齐存储。
2. 结构体的对齐规则通常是按照最大成员的大小进行对齐。
例如,一个结构体中最大的成员是8字节的double类型变量,那么结构体就按照8字节对齐存储。
3. 编译器一般会对结构体进行填充,以满足对齐的要求。
这样可以使得结构体的大小是对齐大小的整数倍,从而提高内存访问的效率。
4. 对于特殊情况和对齐要求更高的场景,可以使用编译器提供的对齐指令来自定义对齐规则。
四、如何通过内存对齐提高程序性能1. 减少内存访问次数:由于内存对齐可以使得数据在内存中的存储更加紧凑,减少了数据的分散存储,从而可以减少内存访问的次数。
对于大型数据结构或数组,内存对齐能够显著提升对内存的访问效率,加快程序的运行速度。
2. 提高缓存命中率:CPU的高速缓存是一个重要的性能瓶颈,内存对齐可以提高缓存命中率。
当数据按照对齐规则存储时,缓存可以更好地预取和预存储数据,减少了对主存的访问次数,从而提高程序的运行效率。
内存对齐详解什么是内存对齐CPU读取内存是⼀块⼀块读取的,并不会以⼀个字节⼀个字节去读取和写⼊内存。
块的⼤⼩可以为2、4、6、8、16等字节⼤⼩,块的⼤⼩称为内存访问粒度。
为什么要进⾏内存对齐平台(移植性)原因:不是所有的硬件平台都能够访问任意地址上的任意数据。
例如:特点的硬件平台只允许在特定地址获取特定类型的数据,否则会导致异常情况。
性能原因:如果不进⾏内存对齐,会导致CPU两次内存访问,并且要花费额外的时钟周期来处理对齐及运算。
如果本⾝是内存对齐的,则只需要访问⼀次即可完成读取操作。
没有内存对齐情况下CPU处理流程如图:我们需要获取的数据存储在1-4字节CPU先读取未对齐地址的第⼀块内存,0-3字节,然后移除不需要的0字节CPU再次读取未对齐地址的第⼆块内存,4-7字节,然后移除不需要的5、6、7合并1-4字节数据合并后放⼊寄存器从上述流程可以看出,如果不做内存对齐需要进⾏两次读取,还需要增加其他许多耗时动作,如果进⾏内存对齐,从0地址开始读取4个字节,则只需要读取⼀次即可,显然会⾼效很多,相当于空间换时间。
默认系数不同平台上编译器都有⾃⼰默认的“对齐系数”,可以通过预编译命令#pragma pack(n)进⾏变更,n就是“对齐系数”。
常⽤平台系数:32位对齐系数为 4 , 64位对齐系数为 8对齐流程成员对齐:在Go中可以调⽤unsafe.Alignof返回相应类型的对齐系数,对齐系数都是2的n次⽅,最⼤不会超过8,因为64编译器默认的对齐系数为8。
结构体第⼀个成员变量偏移量为0,往后每个成员变量的对齐值必须为编译器默认对齐长度或当前成员变量类型的长度,取最⼩值作为当前类型的对齐值,偏移量必须为对齐值的整数倍。
整体对齐:所有成员对齐后,最终结构体也需要进⾏内存对齐对齐值必须为编译器默认对齐长度或结构体所有成员变量类型中的最⼤长度,取最⼤数的最⼩整数倍最为对齐值“内存对齐”实例分析// 内存对齐测试// 类型相应字节⼤⼩:// bool 1字节 int32 4字节 int8 1字节 int64 8字节 byte 1字节func TestAlignment(t *testing.T) {// ⾸先进⾏成员对齐:// 1. a 1 字节偏移量 0 占⽤ 1 内存情况:a// 2. b 4 字节偏移量 4 占⽤ 4 内存情况:axxxbbbb// 3. c 1 字节偏移量 8 占⽤ 1 内存情况:axxxbbbbc// 4. d 8 字节偏移量 16 占⽤ 8 内存情况:axxxbbbbcxxxxxxxdddddddd// 5. e 1 字节偏移量 24 占⽤ 1 内存情况:axxxbbbbcxxxxxxxdddddddde// 成员对齐以后进⾏整体对齐:// 当前成员共占⽤25字节,需要保持对齐系数8的倍数,所以需要对齐到32// 最终内存对齐情况:axxxbbbbcxxxxxxxddddddddexxxxxxxpart1 := struct {a boolb int32c int8d int64e byte}{}// ⾸先进⾏成员对齐:// 1. e 1 字节偏移量 0 占⽤ 1 内存情况:e// 2. c 1 字节偏移量 1 占⽤ 1 内存情况:ec// 3. a 1 字节偏移量 2 占⽤ 1 内存情况:eca// 4. b 4 字节偏移量 4 占⽤ 4 内存情况:ecaxbbbb// 5. d 8 字节偏移量 8 占⽤ 1 内存情况:ecaxbbbbdddddddd// 成员对齐以后进⾏整体对齐:// 当前成员共占⽤16字节,刚好为内存对齐系数8的倍数// 所以最终内存对齐情况:ecaxbbbbddddddddpart2 := struct {e bytec int8a boolb int32d int64}{}fmt.Printf("part1 size: %d, align: %d\n", unsafe.Sizeof(part1), unsafe.Alignof(part1))fmt.Printf("part2 size: %d, align: %d\n", unsafe.Sizeof(part2), unsafe.Alignof(part2))// 成员对齐:Age 占⽤ 4 字节// 整体对齐:64位内存系数位8 所以最终内存占⽤ 8 字节part3 := struct {Age int}{}fmt.Printf("part3 size: %d, align: %d\n", unsafe.Sizeof(part3), unsafe.Alignof(part3))}参考博⽂。
内存对齐规则内存对齐是计算机系统中的一个重要概念,它指的是在内存中存储数据时,数据在内存中的起始地址必须是特定值的倍数。
这个特定值称为对齐单位。
内存对齐的存在是为了提高计算机系统的性能和效率。
本文将介绍内存对齐的规则和作用,并探讨其在计算机系统中的重要性。
一、内存对齐的规则在计算机系统中,内存对齐遵循以下规则:1. 基本对齐规则:数据的起始地址必须是其数据类型的整数倍。
例如,一个整型变量的起始地址必须是4的倍数,一个双精度浮点型变量的起始地址必须是8的倍数。
2. 结构体对齐规则:结构体中的成员变量按照其数据类型的对齐方式进行对齐。
结构体的起始地址必须是其成员变量中对齐要求最高的数据类型的整数倍。
3. 数组对齐规则:数组的起始地址必须是数组元素类型的对齐要求最高的数据类型的整数倍。
4. 结构体嵌套对齐规则:结构体嵌套时,内层结构体的起始地址必须是外层结构体中对齐要求最高的数据类型的整数倍。
二、内存对齐的作用内存对齐的主要作用是提高计算机系统的性能和效率。
具体而言,内存对齐可以带来以下好处:1. 提高访问速度:对齐的数据可以直接从内存中读取,而不需要进行额外的对齐操作。
这样可以减少内存访问的时间,提高程序的执行效率。
2. 节省内存空间:内存对齐可以使数据在内存中的布局更加紧凑,减少内存碎片的产生。
这样可以节省内存空间,提高内存的利用率。
3. 硬件兼容性:不同的硬件平台对内存对齐的要求可能不同。
遵循内存对齐规则可以增加程序在不同硬件平台上的兼容性,减少因为内存对齐问题而导致的程序错误。
三、内存对齐的重要性内存对齐在计算机系统中具有重要的意义。
首先,内存对齐可以提高程序的执行效率,减少内存访问的时间,提高计算机系统的性能。
其次,内存对齐可以减少内存碎片的产生,节省内存空间,提高内存的利用率。
此外,遵循内存对齐规则可以增加程序在不同硬件平台上的兼容性,提高程序的可移植性。
总结起来,内存对齐是计算机系统中的一个重要概念,它可以提高计算机系统的性能和效率。
结构体内存对齐解析为什么要内存对齐虽然所有的变量最后都会保存到特定的地址内存中去,但是相应的内存空间必须满足内存对齐的要求,主要基于存在以下两个原因:•硬件平台原因:并不是所有的平台都能够访问任意地址上的任意数据,某些硬件平台只能够访问对齐的地址,否则就会出现硬件异常错误。
•性能原因:如果数据存放在未对齐的内存空间中,则处理器在访问变量时要做两次次内存访问,而对齐的内存访问只需要一次。
上述两个原因,第一个原因从字面意思上就能够理解,那第二个原因是什么意思呢?假定现在有一个 32 位微处理器,那这个处理器访问内存都是按照 32 位进行的,也就是说一次性读取或写入都是四字节。
假设现在有一个处理器要读取一个大小为4 字节的变量,在内存对齐的情况下,处理器是这样进行读取的:那如果数据存储没有按照内存对齐的方式进行的话,处理器就会这样进行读取:对比内存对齐和内存没有对齐两种情况我们可以明显地看到:在内存对齐的情况下,只需要两个个步骤就可以将数据读出来,首先处理器找到要读出变量所在的地址,然后将数据读出来。
在内存没有对齐的情况下,却需要以下四个步骤才能够将数据取出来:•处理器找到要读取变量所在的地址,也就是图中红色方块所在位置。
•由于此时内存未对齐,处理器是32 位的,一次性读取或者写入都是 4 字节,所以需要将 0-3 地址内的数据和 4-7 地址里的数据都取出来。
•由于 0 - 3 地址范围的 0 地址里的数据不属于我们要读取的数据,因此将这一小块的数据进行移位,把0 地址里的数据移出去;同理,4 - 7 地址范围里的数据也要进行移位,保留 4 地址里的数据•合并移位之后的数据,得出结果通过上述的分析,我们可以知道内存对齐能够提升性能,这也是我们要进行内存对齐的原因之一。
结构体内存对齐对齐原则在明白了为何要进行内存对齐之后,我们来分析结构体内的内存对齐,在进行具体的实例分析前,需要给出结构体内存对齐的两条基本原则。
内存字节对齐原则1. 说起内存字节对齐,这可是计算机里的一门"整理艺术"!就像咱们收拾房间一样,东西不能乱放,得讲究摆放的位置,让拿取更方便。
2. 想象一下,内存就是一个超大的储物柜,每个格子都是一个字节。
为了存取效率,咱们得把数据像叠积木一样整整齐齐地放进去,这就是对齐的妙处。
3. 对齐的基本规则可有意思了!就拿四字节数据来说,它就像个"矜持"的大爷,非得住在能被4整除的门牌号上。
要是住在不合适的地方,那可不行,非得浪费几个小格子不可。
4. 为啥要这么讲究呢?这就跟咱们买菜一样。
一次性买够一袋子的菜,跑一趟就够了;要是零零散散地买,就得多跑好几趟,多费劲啊!5. 来个实在的例子:假如咱们有个结构体,里面放了一个字节的小数据,后面跟着四字节的大数据。
按理说只需要5个格子,但实际上可能占用8个格子!那多出来的空格子就像是大数据的"专属停车位",必须得留着。
6. 有的小伙伴可能要问了:这不是浪费空间吗?诶,这就像是超市购物,散装商品非得按整袋买,虽然可能用不完,但买起来更便宜更快捷啊!7. 不同的处理器还有不同的小脾气。
有的处理器要求严格,数据必须严丝合缝地对齐,就像军训时候站队一样;有的处理器比较随意,不对齐也能工作,就是速度慢点。
8. 编译器在处理这事儿的时候可聪明了,它会自动帮咱们在需要的地方加入"填充字节"。
这些填充字节就像是结构体里的"弹簧垫",把该撑开的地方撑开,保证后面的数据能对齐。
9. 要是想节省空间,咱们还可以玩个小花招:把大小相近的成员放一起!就像收拾行李箱,把大件放一起,小件放一起,这样就能省出不少空间。
10. 有意思的是,有时候看起来更小的结构体,实际占用的空间可能更大。
这就跟收拾房间似的,摆放整齐的东西可能占地方更多,但找起来更方便!11. 在写代码的时候,要是特别在意内存使用,可以用特殊的指令告诉编译器:不用对齐了,能省则省。
内存对齐规则在计算机科学中,内存对齐是指将数据结构的起始地址设置为按照特定规则对齐的地址。
这个规则是为了优化内存的访问效率和提高计算机的性能。
下面将详细介绍内存对齐的规则以及它的作用。
1. 内存对齐的基本原则内存对齐的基本原则是将数据结构按照其大小进行对齐。
对齐的目的是为了保证数据结构的每个成员在内存中的地址都是对齐的,这样可以提高内存的读写效率。
通常情况下,数据结构的对齐方式与平台的硬件架构有关,如x86架构的对齐方式与ARM架构的对齐方式可能不同。
2. 内存对齐的规则内存对齐的规则是根据数据结构的大小来确定的。
以下是常见的内存对齐规则:- 字节对齐:数据结构的起始地址必须是其大小的整数倍。
例如,一个4字节大小的数据结构的起始地址必须是4的倍数。
- 短整型对齐:短整型数据结构的起始地址必须是2的倍数。
- 整型对齐:整型数据结构的起始地址必须是4的倍数。
- 长整型对齐:长整型数据结构的起始地址必须是8的倍数。
- 双精度浮点型对齐:双精度浮点型数据结构的起始地址必须是8的倍数。
3. 内存对齐的作用内存对齐可以提高计算机的性能和内存的访问效率。
首先,对齐的数据结构可以使计算机一次读取或写入多个连续的内存地址,减少了读写操作的次数,提高了内存访问的速度。
其次,对齐的数据结构可以减少内存碎片的产生,提高内存的利用率。
最后,对齐的数据结构可以避免由于内存对齐不当而引起的数据错误和性能下降。
4. 内存对齐的注意事项在进行内存对齐时,需要注意以下几点:- 结构体中的成员变量的声明顺序会影响内存的对齐方式。
通常情况下,将大小相同的成员变量放在一起可以减少内存的浪费。
- 在某些特殊情况下,可以使用特定的编译指令来控制内存对齐的方式,以满足特定的需求。
- 内存对齐可能会增加内存的消耗,特别是在数据结构中存在大量的填充字节的情况下。
因此,在设计数据结构时,需要权衡内存利用率和性能之间的关系。
总结起来,内存对齐是为了提高内存的读写效率和计算机的性能而进行的一种优化技术。
关于内存对齐详细解释什么是内存对齐?在⽤sizeof运算符求算某结构体所占空间时,并不是简单地将结构体中所有元素各⾃的空间相加,这⾥涉及到内存对齐的问题。
访问未对齐的内存,处理器需要访问两次(数据先读⾼位再读低位然后进⾏拼接),⽽访问对齐的内存,只需要⼀次。
为了提⾼效率,所以进⾏内存对齐。
windows的默认对齐数是8,linux中默认对齐数为4.为什么会产⽣内存对齐的原因?1.平台原因:某些平台只能在特定的地址处访问特定类型的数据。
不是所有的硬件平台都能访问任意地址上的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常2.性能原因:(1)数据结构(尤其是栈)应该尽可能在⾃然边界上对齐。
(2)为了访问未对齐的内存,处理器需要作两次内存访问;⽽对齐的内存仅需要访问⼀次。
(CPU在读取内存的时候是⼀块⼀块进⾏读取的,块的⼤⼩称为(memory granularity)内存读取粒度)编译器是怎样进⾏内存对齐的优化的?在内存中,编译器按照成员列表顺序分别为每个结构体变量成员分配内存,在存储过程中需要满⾜边界对齐要求时,编译器会在成员之间留下额外的内存空间。
结构体或联合体的数据成员、第⼀个成员放到0编译地⽅,以后每个数据成员放到⾃⾝对齐的整数倍偏移处。
(结构体⼤⼩必须是最⼤对齐数的整数倍)。
字节对齐可以程序控制,采⽤指令:#pragma pack(1)//1字节对齐#pragma pack(2)//2字节对齐#pragma pack(4)//4字节对齐#pragma pack(8)//8字节对齐举个栗⼦:struct A{char c;double d;short s;int i;}当系统以8字节对齐时程序验证⼀下:1.#pragma pack(8)2.#include<iostream>3.#include<cstdio>4.5.struct A6.{7.char c;8.double d;9.short s;10.int i;11.};12.int main()13.{14.struct A a;15.printf("A所占⼤⼩为%d字节\n",sizeof(struct A));16.17.return 0;18.}我们再来看看为什么内存不对齐会影响读取速度?假设CPU要读取⼀个4字节⼤⼩的数据到寄存器中(假设内存读取粒度是4),分两种情况讨论:1.数据从0字节开始2.数据从1字节开始解析:当数据从0字节开始的时候,直接将0-3四个字节完全读取到寄存器,结算完成了。
内存对齐详解内存地址对齐,是一种在计算机内存中排列数据(表现为变量的地址)、访问数据(表现为CPU读取数据)的一种方式,包含了两种相互独立又相互关联的部分:基本数据对齐和结构体数据对齐。
为什么需要内存对齐?对齐有什么好处?是我们程序员来手动做内存对齐呢?还是编译器在进行自动优化的时候完成这项工作?在现代计算机体系中,每次读写内存中数据,都是按字(word,4个字节,对于X86架构,系统是32位,数据总线和地址总线的宽度都是32位,所以最大的寻址空间为232 = 4GB(也许有人会问,我的32位XP用不了4GB内存,关于这个不在本篇博文讨论范围),按A[31,30…2,1,0]这样排列,但是请注意为了CPU每次读写 4个字节寻址,A[0]和A[1]两位是不参与寻址计算的。
)为一个块(chunks)来操作(而对于X64则是8个字节为一个快)。
注意,这里说的 CPU每次读取的规则,并不是变量在内存中地址对齐规则。
既然是这样的,如果变量在内存中存储的时候也按照这样的对齐规则,就可以加快CPU读写内存的速度,当然也就提高了整个程序的性能,并且性能提升是客观,虽然当今的CPU的处理数据速度(是指逻辑运算等,不包括取址)远比内存访问的速度快,程序的执行速度的瓶颈往往不是CPU 的处理速度不够,而是内存访问的延迟,虽然当今CPU中加入了高速缓存用来掩盖内存访问的延迟,但是如果高密集的内存访问,一种延迟是无可避免的,内存地址对齐会给程序带来了很大的性能提升。
内存地址对齐是计算机语言自动进行的,也即是编译器所做的工作。
但这不意味着我们程序员不需要做任何事情,因为如果我们能够遵循某些规则,可以让编译器做得更好,毕竟编译器不是万能的。
为了更好理解上面的意思,这里给出一个示例。
在32位系统中,假如一个int变量在内存中的地址是0x00ff42c3,因为int是占用4个字节,所以它的尾地址应该是0x00ff42c6,这个时候CPU为了读取这个int变量的值,就需要先后读取两个word大小的块,分别是0x00ff42c0~0x00ff42c3和0x00ff42c4~0x00ff42c7,然后通过移位等一系列的操作来得到,在这个计算的过程中还有可能引起一些总线数据错误的。
内存对齐详解由底层深⼊内存对齐原理剖析寻址花费的时间==》降低寻址次数,数据单元⼤>也就是内存和效率的互换内存对齐前的内存分体技术什么叫分体?分体是⼲嘛的?硬件层都是01的天下,我们的数据也是位存储,我们存储数据时按道理是不是也应该这样⼀位位⼆进制进⾏存储。
事实上,我们在⽇常操作中,常操作的是Byte,往往都是⼀个B⼀个B的进⾏存储。
这实际上就是为了简化我们的⽇常操作,毕竟谁愿意存⼀个数据,需要⾃⼰⼀个bit⼀个bit得进⾏存储。
于是这就形成了所谓的体,即8bit为⼀个(分)体。
每个体在访问时,⼀次默认就读出⼀个体的数据长度,现如今的64位机,也就是说⼀个访问地址单元的数据为64位,即64条数据线,⼀次可读4K。
那如果在64位机中,我存有⼀个8位数据(16-31),该怎么访问呢。
每次都要读出64位数据吗?事实上,设计者们在最开始已经想好了。
当需要时,可以通过控制电路,对数据线进⾏控制,也就是说,即使每个单元都链接有64根数据线,但可以只选中其中的16-31号地址线进⾏数据读取,从⽽达到逻辑上的8位位宽。
这也就解释了,为啥在固定64位机上,编译器等,可以调整对齐宽度,这些只是逻辑上的调整,最底层还是64位的对齐宽度。
接下来的内存对齐,都是基于逻辑上的内存对齐。
内存分配时,⼀个数据总是会尽量不被切开到多个地址单元中如图所⽰,紫⾊部分为浪费了的空间1.为什么不可以c直接接在后⾯进⾏存储呢?这涉及到内存寻址,当数据被切开后,那么就要求寻址两次,才能获取完整的数据,由于寻址是要耗费CPU的,为了寻找⼀部分的数据,要多寻找⼀次,操作系统设计者觉得效率低下,相对于内存来说,cpu是很昂贵的,⽽且降低了整体效率,所以他们决定以空间换效率,也就是说,像第⼀种⽅式,它最后数据c只会进⾏⼀次寻址,从⽽提⾼了效率。
2.那么有⼈会说,既然这些数据存放不了,为什么不可以把其他数据再填充进来呢?我想,如果还要把那些⼩碎⽚还得利⽤的话,那么是不是还需要⼀个表⽤以记录维护那些⼩碎⽚,这⽆疑降低了效率。
结构体对齐规则1、什么是内存对齐?我们都知道,定义的变量(元素)是要按照顺序一个一个放到内存中去的,它们也不一定就是紧密排列的,是要按照一定的规则就行排放的,这就是内存对齐。
对结构体来说,元素的存储从首地址开始,第一个元素的地址和整个结构体的首地址相同,其他的每个元素放置到内存中时,它都会认为内存是按照元素自己的大小来划分空间的,所以元素放置在内存中的位置一定会在元素自己宽度(字节数)的整数倍上开始,这就是所谓的结构体内存对齐问题。
特别有意思的是,C语言同意使用者自行确定内存对齐的设置,通过伪指令#pragma pack (n) 可以重新设定内存对齐的字节数。
这个后面会讲到!2、为什么要有内存对齐?这真是一个好问题!从网上了解到的几个原因:(1)考虑平台的原因。
实际的硬件平台跑代码是有所区别的,一些硬件平台可以对任意地址上的任意数据进行访问,而有一些硬件平台就不行,就是有限制,所以内存对齐是一种解决办法。
(2)考虑性能的原因。
CPU访问内存时,如果内存不对齐的话,为了访问到数据的话就需要几次访问,而对齐的内存只需要访问一次即可,提高了CPU访问内存的速度。
3、结构体的内存对齐规则是什么?每当有用到结构体的时候,总会考虑这个结构体实际应该要占用多少的内存,是否还有优化的空间。
特别是在面试时,结构体的内存对齐问题是很多面试会考到,也会经常被提及问起,属于高频考点了!话不多说,直接奉上结构体的内存对齐的判别方法,方便大家快速算出结构体所占的内存大小。
这里先规定一下:内存对齐值称为内存对齐有效值,这个值可以是1、2、4、8、16,所以先规定一下。
规则:规则1,结构体第一个成员一定是放在结构体内存地址里面的第1位。
规则2,成员对齐规则:除了第一个成员,之后的每个数据成员的对齐要按照成员自身的长度和内存对齐有效值进行比较,按两者中最小的那个进行对齐,即偏移的倍数。
规则3,结构体整体对齐规则:数据成员完成对齐之后,对整个结构体的大小进行对齐。
⼤端和⼩端--内存对齐问题什么是⼤端和⼩端Big-Endian和Little-Endian的定义如下:1) Little-Endian就是低位字节排放在内存的低地址端,⾼位字节排放在内存的⾼地址端。
2) Big-Endian就是⾼位字节排放在内存的低地址端,低位字节排放在内存的⾼地址端。
举⼀个例⼦,⽐如数字0x12 34 56 78在内存中的表⽰形式为:1)⼤端模式:低地址 -----------------> ⾼地址0x12 | 0x34 | 0x56 | 0x782)⼩端模式:低地址 ------------------> ⾼地址0x78 | 0x56 | 0x34 | 0x12可见,⼤端模式和字符串的存储模式类似。
3)下⾯是两个具体例⼦:16bit宽的数0x1234在Little-endian模式(以及Big-endian模式)CPU内存中的存放⽅式(假设从地址0x4000开始存放)为:内存地址⼩端模式存放内容⼤端模式存放内容0x40000x340x120x40010x120x3432bit宽的数0x12345678在Little-endian模式以及Big-endian模式)CPU内存中的存放⽅式(假设从地址0x4000开始存放)为:内存地址⼩端模式存放内容⼤端模式存放内容0x40000x780x120x40010x560x340x40020x340x560x40030x120x784)⼤端⼩端没有谁优谁劣,各⾃优势便是对⽅劣势:⼩端模式:强制转换数据不需要调整字节内容,1、2、4字节的存储⽅式⼀样。
⼤端模式:符号位的判定固定为第⼀个字节,容易判断正负。
数组在⼤端⼩端情况下的存储: 以unsigned int value = 0x12345678为例,分别看看在两种字节序下其存储情况,我们可以⽤unsigned char buf[4]来表⽰value: Big-Endian: 低地址存放⾼位,如下:⾼地址---------------buf[3] (0x78) -- 低位buf[2] (0x56)buf[1] (0x34)buf[0] (0x12) -- ⾼位---------------低地址Little-Endian: 低地址存放低位,如下:⾼地址---------------buf[3] (0x12) -- ⾼位buf[2] (0x34)buf[1] (0x56)buf[0] (0x78) -- 低位--------------低地址为什么会有⼤⼩端模式之分呢?这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着⼀个字节,⼀个字节为8bit。
⼀篇⽂章带你了解C语⾔内存对齐解决的问题⽬录⼀、内存对齐为4个字节的好处⼆、内存对齐的⽬的是以空间换取速度2.1、内存对齐为4的例⼦2.2、内存没有使⽤内存对齐的例⼦CPU读取数据的过程:三、掌握内存对齐的必要性总结⼀、内存对齐为4个字节的好处⾸先,了解⼀下CPU从内存⾥读取数据的流程:第⼀步,CPU通过地址总线,找到该数据的位置。
第⼆步,通过控制总线,发送读取数据的指令。
第三步,通过数据总线,从内存⾥获取该数据的内容。
内存对齐使⽤4个字节的原因有:1.STM32单⽚机的数据总线与地址总线都是32bit(4个字节)。
2.⽅便DMA的搬运,DMA搬运的最⼤内存是32bit(4个字节)。
⼆、内存对齐的⽬的是以空间换取速度2.1、内存对齐为4的例⼦/* 先来⼀个简单的结构体 */struct{char a;int b;}Test2;CPU读取内存⾥数据的过程:1.想找变量a:第⼀次读取就能找到。
2.想找变量b:第⼆次读取就能找到。
这⼀点很重要,变量a与变量b各⾃只需要1次寻址就能完成读取。
接下来看⼀看内存如果没有使⽤内存对齐的例⼦(当我不知道内存对齐时,我也是误以为内存⾥的数据是这样分布的!)2.2、内存没有使⽤内存对齐的例⼦如果内存没有使⽤内存对齐的话,构想的内存分布如下:CPU读取数据的过程:1.想找变量a:第⼀次读取就能找到。
2.想找变量b:先读取第⼀组内存的后三个字节,接着再读取第⼆组内存的第⼀个字节,最后将所有字节合并为4个字节。
如果内存没有使⽤内存对齐的话,CPU为了获取变量b花掉了两次地址寻址,接着还要将字节合并。
所以,内存对齐可以有效地提⾼CPU读写内存的速度,但是浪费⼀点空间。
三、掌握内存对齐的必要性了解内存对齐的作⽤后,就能弄懂为什么编译器要对某些内存做了填充。
⽐如本章节的例⼦,如果结构体⾥只有⼀个char与int变量,⽆论是char变量在前,还是int变量在前,都肯定会浪费3个字节被⽤于填充,凑够4个字节变成⼀组数据被CPU⼀次性读取。
目的:
1. 提高内存访问效率。
由于CPU在读取内存时,一次性会以固定长度(如4个字节、8个
字节等)读取数据,而不是一个一个的字节读取。
如果将多个变量连续分配在内存中而不
进行对齐处理的话,就会造成CPU在读取数据时出现“半包”情况。
此时CPU必须要做2
次内存访问才能得到所有的数据,显然效率低了很多。
2. 减少内存占用量。
当使用对齐方式来分布各个变量时,有些余留出来的空间是浪费的。
但是通过使用对其方式来分布各个变量也能避免上文中所说的“半包”情况出现从而大大
减少 CPU 读取数据所浪费的时间和功耗
原理:
1. 对齐原理是将物理地址向上舍入到最近的能被对齐因子(Alignment Factor) 整除的
倍数,例如 4 字(32bit) 要 4 字(32bit) 对齐, 8 字 (64bit) 要 8 字 (64bit) 对齐;
2. 由于 CPU 大部分都是 32 bit 或 64 bit 系统,因此通常要 4 byte 或 8 byte 相应
地对齐;
3. 有些 CPU 要求 16 byte 的对齐,例如 Intel Pentium Pro/II/III/IV ,AMD K6-2/K7 Athlon ;
4. 有些 CPU 要 32 byte 的对齐 ,例如 AMD Opteron / Athlon 64 .。
内存对齐的理解
内存对齐是一种优化技术,其目的是在存储单元大小为N的计算机上,使数据结构的首地址为N的倍数。
这样可以提高访问内存的效率,从而提高程序的性能。
在C/C++语言中,结构体和类的成员变量是按照定义的顺序依次存放在内存中的。
但是,由于计算机硬件的限制,存储单元的大小通常不是任意大小,而是固定的,如8字节、4字节、2字节等。
这时,如果结构体或类的成员变量大小不是存储单元大小的整数倍,就会出现内存对齐问题。
内存对齐的规则是,将结构体或类的成员变量按照从大到小的顺序排序,然后按照存储单元大小的整数倍进行对齐。
具体来说,如果某个成员变量的大小小于存储单元大小,则在其后面填充空白字节,使其占用的空间大小为存储单元大小的整数倍。
如果某个成员变量的大小等于存储单元大小,则不需要进行对齐。
如果某个成员变量的大小大于存储单元大小,则需要将其拆分成多个存储单元大小的部分进行对齐。
内存对齐的优点是可以提高程序的性能,因为CPU在处理内存时通常是以存储单元大小为单位进行读写的,如果数据结构的首地址不是存储单元大小的整数倍,就需要进行多次读写操作,这会浪费一定的时间和资源。
而进行内存对齐后,CPU可以一次读写整个存储单元,从而提高了程序的效率。
值得注意的是,内存对齐不仅仅是在结构体和类的成员变量中存
在,还可以在函数的调用过程中存在。
在函数调用时,参数的传递也需要进行内存对齐,以保证程序的正确性和性能。
解析C语言中的sizeof一、sizeof的概念sizeof是C语言的一种单目操作符,如C语言的其他操作符++、--等。
它并不是函数。
sizeof操作符以字节形式给出了其操作数的存储大小。
操作数可以是一个表达式或括在括号内的类型名。
操作数的存储大小由操作数的类型决定。
二、sizeof的使用方法1、用于数据类型sizeof使用形式:sizeof(type)数据类型必须用括号括住。
如sizeof(int)。
2、用于变量sizeof使用形式:sizeof(var_name)或sizeof var_name变量名可以不用括号括住。
如sizeof (var_name),sizeof var_name等都是正确形式。
带括号的用法更普遍,大多数程序员采用这种形式。
注意:sizeof操作符不能用于函数类型,不完全类型或位字段。
不完全类型指具有未知存储大小的数据类型,如未知存储大小的数组类型、未知内容的结构或联合类型、void类型等。
如sizeof(max)若此时变量max定义为int max(),sizeof(char_v) 若此时char_v定义为char char_v [MAX]且MAX未知,sizeof(void)都不是正确形式。
三、sizeof的结果sizeof操作符的结果类型是size_t,它在头文件中typedef为unsigned int类型。
该类型保证能容纳实现所建立的最大对象的字节大小。
1、若操作数具有类型char、unsigned char或signed char,其结果等于1。
ANSI C正式规定字符类型为1字节。
2、int、unsigned int 、short int、unsigned short 、long int 、unsigned long 、float、double、long double类型的sizeof 在ANSI C 中没有具体规定,大小依赖于实现,一般可能分别为2、2、2、2、4、4、4、8、10。
3、当操作数是指针时,sizeof依赖于编译器。
例如Microsoft C/C++7.0中,near类指针字节数为2,far、huge类指针字节数为4。
一般Unix的指针字节数为4。
4、当操作数具有数组类型时,其结果是数组的总字节数。
5、联合类型操作数的sizeof是其最大字节成员的字节数。
结构类型操作数的sizeof是这种类型对象的总字节数,包括任何垫补在内。
让我们看如下结构:struct {char b; double x;} a;在某些机器上sizeof(a)=12,而一般sizeof(char)+ sizeof(double)=9。
这是因为编译器在考虑对齐问题时,在结构中插入空位以控制各成员对象的地址对齐。
如double类型的结构成员x要放在被4整除的地址。
6、如果操作数是函数中的数组形参或函数类型的形参,sizeof给出其指针的大小。
四、sizeof与其他操作符的关系sizeof的优先级为2级,比/、%等3级运算符优先级高。
它可以与其他操作符一起组成表达式。
如i*sizeof(int);其中i为int类型变量。
五、sizeof的主要用途1、sizeof操作符的一个主要用途是与存储分配和I/O系统那样的例程进行通信。
例如:void *malloc(size_t size),size_t fread(void*ptr,size_t size,size_t nmemb,FILE* stream)。
2、sizeof的另一个的主要用途是计算数组中元素的个数。
例如:void * memset(void * s,int c,sizeof(s))。
六、建议由于操作数的字节数在实现时可能出现变化,建议在涉及到操作数字节大小时用sizeof来代替常量计算。
本文主要包括二个部分,第一部分重点介绍在VC中,怎么样采用sizeof来求结构的大小,以及容易出现的问题,并给出解决问题的方法,第二部分总结出VC 中sizeof的主要用法。
1、 sizeof应用在结构上的情况请看下面的结构:struct MyStruct{double dda1;char dda;int type};对结构MyStruct采用sizeof会出现什么结果呢?sizeof(MyStruct)为多少呢?也许你会这样求:sizeof(MyStruct)=sizeof(double)+sizeof(char)+sizeof(int)=13但是当在VC中测试上面结构的大小时,你会发现sizeof(MyStruct)为16。
你知道为什么在VC中会得出这样一个结果吗?其实,这是VC对变量存储的一个特殊处理。
为了提高CPU的存储速度,VC对一些变量的起始地址做了“对齐”处理。
在默认情况下,VC规定各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数。
下面列出常用类型的对齐方式(vc6.0,32位系统)。
类型对齐方式(变量存放的起始地址相对于结构的起始地址的偏移量)Char偏移量必须为sizeof(char)即1的倍数int偏移量必须为sizeof(int)即4的倍数float偏移量必须为sizeof(float)即4的倍数double偏移量必须为sizeof(double)即8的倍数Short偏移量必须为sizeof(short)即2的倍数各成员变量在存放的时候根据在结构中出现的顺序依次申请空间,同时按照上面的对齐方式调整位置,空缺的字节VC会自动填充。
同时VC为了确保结构的大小为结构的字节边界数(即该结构中占用最大空间的类型所占用的字节数)的倍数,所以在为最后一个成员变量申请空间后,还会根据需要自动填充空缺的字节。
下面用前面的例子来说明VC到底怎么样来存放结构的。
struct MyStruct{double dda1;char dda;int type};为上面的结构分配空间的时候,VC根据成员变量出现的顺序和对齐方式,先为第一个成员dda1分配空间,其起始地址跟结构的起始地址相同(刚好偏移量0刚好为sizeof(double)的倍数),该成员变量占用sizeof(double)=8个字节;接下来为第二个成员dda分配空间,这时下一个可以分配的地址对于结构的起始地址的偏移量为8,是sizeof(char)的倍数,所以把dda存放在偏移量为8的地方满足对齐方式,该成员变量占用sizeof(char)=1个字节;接下来为第三个成员type分配空间,这时下一个可以分配的地址对于结构的起始地址的偏移量为9,不是sizeof(int)=4的倍数,为了满足对齐方式对偏移量的约束问题,VC自动填充3个字节(这三个字节没有放什么东西),这时下一个可以分配的地址对于结构的起始地址的偏移量为12,刚好是sizeof(int)=4的倍数,所以把type 存放在偏移量为12的地方,该成员变量占用sizeof(int)=4个字节;这时整个结构的成员变量已经都分配了空间,总的占用的空间大小为:8+1+3+4=16,刚好为结构的字节边界数(即结构中占用最大空间的类型所占用的字节数sizeof(double)=8)的倍数,所以没有空缺的字节需要填充。
所以整个结构的大小为:sizeof(MyStruct)=8+1+3+4=16,其中有3个字节是VC自动填充的,没有放任何有意义的东西。
下面再举个例子,交换一下上面的MyStruct的成员变量的位置,使它变成下面的情况:struct MyStruct{char dda;double dda1;int type};这个结构占用的空间为多大呢?在VC6.0环境下,可以得到sizeof(MyStruc)为24。
结合上面提到的分配空间的一些原则,分析下VC怎么样为上面的结构分配空间的。
(简单说明)struct MyStruct{char dda;//偏移量为0,满足对齐方式,dda占用1个字节;double dda1;//下一个可用的地址的偏移量为1,不是sizeof(double)=8//的倍数,需要补足7个字节才能使偏移量变为8(满足对齐//方式),因此VC自动填充7个字节,dda1存放在偏移量为8//的地址上,它占用8个字节。
int type;//下一个可用的地址的偏移量为16,是sizeof(int)=4的倍//数,满足int的对齐方式,所以不需要VC自动填充,type存//放在偏移量为16的地址上,它占用4个字节。
};//所有成员变量都分配了空间,空间总的大小为1+7+8+4=20,不是结构//的节边界数(即结构中占用最大空间的类型所占用的字节数sizeof//(double)=8)的倍数,所以需要填充4个字节,以满足结构的大小为//sizeof(double)=8的倍数。
所以该结构总的大小为:sizeof(MyStruc)为1+7+8+4+4=24。
其中总的有7+4=11个字节是VC自动填充的,没有放任何有意义的东西。
VC对结构的存储的特殊处理确实提高CPU存储变量的速度,但是有时候也带来了一些麻烦,我们也屏蔽掉变量默认的对齐方式,自己可以设定变量的对齐方式。
VC中提供了#pragma pack(n)来设定变量以n字节对齐方式。
n字节对齐就是说变量存放的起始地址的偏移量有两种情况:第一、如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式,第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。
结构的总大小也有个约束条件,分下面两种情况:如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数;否则必须为n的倍数。
下面举例说明其用法。
#pragma pack(push) //保存对齐状态#pragma pack(4)//设定为4字节对齐struct test{char m1;double m4;int m3;};#pragma pack(pop)//恢复对齐状态以上结构的大小为16,下面分析其存储情况,首先为m1分配空间,其偏移量为0,满足我们自己设定的对齐方式(4字节对齐),m1占用1个字节。
接着开始为m4分配空间,这时其偏移量为1,需要补足3个字节,这样使偏移量满足为n=4的倍数(因为sizeof(double)大于n),m4占用8个字节。
接着为m3分配空间,这时其偏移量为12,满足为4的倍数,m3占用4个字节。
这时已经为所有成员变量分配了空间,共分配了16个字节,满足为n的倍数。
如果把上面的#pragma pack(4)改为#pragma pack(16),那么我们可以得到结构的大小为24。
(请读者自己分析)2、 sizeof用法总结在VC中,sizeof有着许多的用法,而且很容易引起一些错误。