结构体、类等内存字节对齐
2015-06-02 16:47
218 查看
先看简单一点的
结构体内存对齐,主要是为了CPU能更快速的提取数据
所谓的对齐,指结构体及其成员的内存起始地址address应为要对齐大小X的倍数,
即address = kX
假设 n 为编译器设定的对齐大小, item为结构体中成员类型,那么实际对齐的大小值,
计算如下:
X = min(n, sizeof(item))
-------------------------------------------------------
举例:
例1:
sizeof(struct Item)等于多少?
分析如下:
1.结构体模式模式按成员中最大长度类型进行内存对齐,
struct Item中最大长度类型为int,长度为4
2.假设第一成员的起始地址为0,那么:
ItemChar1 的类型长度为1,根据X = min(n, sizeof(ItemChar1)),即X = min(4, 1) = 1,按1对齐
所以,内存地址空间范围为 0~29, 占30个Byte;
ItemEnum 的类型长度为4,根据X = min(n, sizeof(ItemEnum)),即X = min(4, 4) = 4,按4对齐
由于ItemChar1的结尾内存地址为29,所以下一个成员应该从30开始,
但是30不是4的倍数,往后偏移到32才是4的倍数,
所以ItemEnum的起始内存地址应该是32,
所以,内存地址空间范围为 32~35, 占4个Byte;
ItemChar2 的类型长度为1, X = min(4, 1) = 1
所以,内存地址空间范围为 36~85, 占50个Byte;
ItemInt 的类型长度为4,X = min(4, 4) = 4
起始地址应该是86,但是86非4的倍数,最靠近的88才是4的倍数,
所以起始地址偏移到88的位置
所以,内存地址空间范围为 88~91, 占4个Byte;
3. 由上一步计算得到结构体的内存空间范围为0~91,即大小为92,
而92已经是4的倍数,即所谓的已经对齐
所以该结构体的大小: sizeof(struct Item)等于92
-------------------------------------------------------
例2:
分析如下:
1.结构体A的最大长度类型int,大小为4
2.假设起始地址为0
i 的类型int, 大小为4, min(4, 4) = 4, 按4对齐,内存地址空间为 0~3
j 的类型int, 大小为4, min(4, 4) = 4, 按4对齐,内存地址空间为 4~7 (4已是4的倍数)
s 的类型short,大小为2,min(4, 2) = 2, 按2对齐,内存地址空间为 8~9 (8已是2的倍数)
c 的类型char, 大小为1, min(4, 1) = 1, 按1对齐,内存地址空间为 10 (10已是1的倍数)
3. 结构体A的地址空间范围为0~10, 大小为11,
但由于要按4对齐,11非4的倍数,最靠近的12才是
所以结构体A的大小应该是12
-------------------------------------------------------
例3:
1. 结构体最大长度类型double,长度为8
2. 假设结构体起始地址为0
a 的类型char大小为1,1小于8,按1对齐,地址空间范围为 0
b 的类型int 大小为4,4小于8,按4对齐,
由于接下来的起始地址1不是4的倍数,所以取4做起始地址
即b的地址空间范围应该是4~7
c 的类型double 大小为8,8等于8,按8对齐,
接着的起始地址应该是8,已经是8的倍数,
所以c的地址空间范围应该是 8~15
d 的类型char大小为1,1小于8,按1对齐,地址空间范围16
3. 结构体地址空间范围0~16, 大小为17,
17不是8的倍数,最靠近的为8的倍数的数应该是24,
所以该结构体大小为24
-------------------------------------------------------
测试:
test.c
gcc -o t test.c
./t
输出:
以上内容出自http://blog.csdn.net/zhaori/article/details/7659268
再来看看原理:
不光结构体存在内存对齐一说,类(对象)也如此,甚至于所有变量在内存中的存储也有对齐一说(只是这些对程序员是透明的,不需要关心)。实际上,这种对齐是为了在空间与复杂度上达到平衡的一种技术手段,简单的讲,是为了在可接受的空间浪费的前提下,尽可能的提高对相同运算过程的最少(快)处理。先举个例子:
假设机器字长是32位的(即4字节,下面示例均按此字长),也就是说处理任何内存中的数据,其实都是按32位的单位进行的。现在有2个变量:
假设这2个变量是从内存地址0开始分配的,如果不考虑对齐,应该是这样存储的(见下图,以intel上的little endian为例,为了形象,每16个字节分做一行,后同):
![](http://img1.51cto.com/attachment/201109/234241430.png)
因为计算机的字长是4字节的,所以在处理变量A与B时的过程可能大致为:
A:将0x00-0x03共32位读入寄存器,再通过左移24位再右移24位运算得到a的值(或与0x000000FF做与运算)
B:将0x00-0x03这32位读入寄存器,通过位运算得到低24位的值;再将0x04-0x07这32位读入寄存器,通过位运算得到高8位的值;再与最先得到的24位做位运算,才可得到整个32位的值。
上面叙述可知,对a的处理是最简处理,可对b的处理,本身是个32位数,处理的时候却得折成2部分,之后再合并,效率上就有些低了。
想解决这个问题,就需要付出几个字节浪费的代价,改为下图的分配方式:
![](http://img1.51cto.com/attachment/201109/234031687.png)
按上面的分配方式,A的处理过程不变;B却简单得多了:只需将0x04-0x07这32位读入寄存器就OK了。
我们可以具体谈结构体或类成员的对齐了:
结构体在编译成机器代码后,其实就没有本身的集合概念了,而类,实际上是个加强版的结构体,类的对象在实例化时,内存中申请的就是一些变量的空间集合(类似于结构体,同时也不包含函数指针)。这些集合中的每个变量,在使用中,都需要涉及上述的加工原则,自然也就需要在效率与空间之间做出权衡。
为了便捷加工连续多个相同类型原始变量,同时简化原始变量寻址,再汇总上述最少处理原则,通常可以将原始变量的长度做为针对此变量的分配单位,比如内存可用64个单元,如果某原始变量长度为8字节,即使机器字长为4字节,分配的时候也以8字节对齐(看似IO次数是相同的),这样,寻址、分配时,均可以按每8字节为单位进行,简化了操作,也可以更高效。
系统默认的对齐规则,追求的至少两点:1、变量的最高效加工 2、达到目的1的最少空间
举个例子,一个结构体如下:
假设定义了一个结构体变量C,在内存中分配到了0x00的位置,显然:
对于成员C.c 无论如何,也是一次寄存器读入,所以先占一个字节。
对于成员C.d 是个64位的变量,如果紧跟着C.c存储,则读入寄存器至少需要3次,为了实现最少的2次读入,至少需要以4字节对齐;同时对于8字节的原始变量,为了在寻址单位上统一,则需要按8字节对齐,所以,应该分配到0x08-0xF的位置。
对于成员C.e 是个32位的变量,自然只需满足分配起始为整数个32位即可,所以分配至0x10-0x13。
对于成员C.f 是个16位的变量,直接分配在0x14-0x16上,这样,反正只需一次读入寄存器后加工,边界也与16位对齐。
对于成员C.g 是个8位的变量,本身也得一次读入寄存器后加工,同时对于1个字节的变量,存储在任何字节开始都是对齐,所以,分配到0x17的位置。
对于成员C.h 是个16位的变量,为了保证与16位边界对齐,所以,分配到0x18-0x1A的位置。
分配图如下(还不正确,耐心读下去):
![](http://img1.51cto.com/attachment/201109/004003168.png)
结构体C的占用空间到h结束就可以了吗?我们找个示例:如果定义一个结构体数组 CA[2],按变量分配的原则,这2个结构体应该是在内存中连续存储的,分配应该如下图:
![](http://img1.51cto.com/attachment/201109/004624736.png)
分析一下上图,明显可知,CA[1]的很多成员都不再对齐了,究其原因,是结构体的开始边界不对齐。
那结构体的开始偏移满足什么条件才可以使其成员全部对齐呢。想一想就明白了:很简单,保证结构体长度是原始成员最长分配的整数倍即可。
上述结构体应该按最长的.d成员对齐,即与8字节对齐,这样正确的分配图如下:
![](http://img1.51cto.com/attachment/201109/005212595.png)
当然结构体T的长度:sizeof(T)==0x20;
再举个例子,看看在默认对齐规则下,各结构体成员的对齐规则:
具体的分配图如下:
![](http://img1.51cto.com/attachment/201109/012057204.png)
上述全部测试代码如下:
运行时的内存情况如下图:
![](http://img1.51cto.com/attachment/201109/013014504.png)
最后,简单加工一下转载过来的内存对齐正式原则:
先介绍四个概念:
1)数据类型自身的对齐值:基本数据类型的自身对齐值,等于sizeof(基本数据类型)。
2)指定对齐值:#pragma pack (value)时的指定对齐值value。
3)结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。
4)数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中较小的那个值。
有效对齐值N是最终用来决定数据存放地址方式的值,最重要。有效对齐N,就是表示“对齐在N上”,也就是说该数据的"存放起始地址%N=0".而数据结构中的数据变量都是按定义的先后顺序来排放的。第一个数据变量的起始地址就是 数据结构的起始地址。结构体的成员变量要对齐排放,结构体本身也要根据自身的有效对齐值圆整(就是结构体成员变量占用总长度需要是对结构体有效对齐值的整 数倍)
#pragma pack (value)来告诉编译器,使用我们指定的对齐值来取代缺省的。
如#pragma pack (1) /*指定按2字节对齐*/
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
本文出处
结构体内存对齐,主要是为了CPU能更快速的提取数据
所谓的对齐,指结构体及其成员的内存起始地址address应为要对齐大小X的倍数,
即address = kX
假设 n 为编译器设定的对齐大小, item为结构体中成员类型,那么实际对齐的大小值,
计算如下:
X = min(n, sizeof(item))
-------------------------------------------------------
举例:
例1:
enum ENUM_DATA{IntData, CharData, LongData}; struct Item { char ItemChar1[30]; ENUM_DATA ItemEnum; char ItemChar2[50]; int ItemInt; };
sizeof(struct Item)等于多少?
分析如下:
1.结构体模式模式按成员中最大长度类型进行内存对齐,
struct Item中最大长度类型为int,长度为4
2.假设第一成员的起始地址为0,那么:
ItemChar1 的类型长度为1,根据X = min(n, sizeof(ItemChar1)),即X = min(4, 1) = 1,按1对齐
所以,内存地址空间范围为 0~29, 占30个Byte;
ItemEnum 的类型长度为4,根据X = min(n, sizeof(ItemEnum)),即X = min(4, 4) = 4,按4对齐
由于ItemChar1的结尾内存地址为29,所以下一个成员应该从30开始,
但是30不是4的倍数,往后偏移到32才是4的倍数,
所以ItemEnum的起始内存地址应该是32,
所以,内存地址空间范围为 32~35, 占4个Byte;
ItemChar2 的类型长度为1, X = min(4, 1) = 1
所以,内存地址空间范围为 36~85, 占50个Byte;
ItemInt 的类型长度为4,X = min(4, 4) = 4
起始地址应该是86,但是86非4的倍数,最靠近的88才是4的倍数,
所以起始地址偏移到88的位置
所以,内存地址空间范围为 88~91, 占4个Byte;
3. 由上一步计算得到结构体的内存空间范围为0~91,即大小为92,
而92已经是4的倍数,即所谓的已经对齐
所以该结构体的大小: sizeof(struct Item)等于92
-------------------------------------------------------
例2:
struct A{ int i; int j; short s; char c; };
分析如下:
1.结构体A的最大长度类型int,大小为4
2.假设起始地址为0
i 的类型int, 大小为4, min(4, 4) = 4, 按4对齐,内存地址空间为 0~3
j 的类型int, 大小为4, min(4, 4) = 4, 按4对齐,内存地址空间为 4~7 (4已是4的倍数)
s 的类型short,大小为2,min(4, 2) = 2, 按2对齐,内存地址空间为 8~9 (8已是2的倍数)
c 的类型char, 大小为1, min(4, 1) = 1, 按1对齐,内存地址空间为 10 (10已是1的倍数)
3. 结构体A的地址空间范围为0~10, 大小为11,
但由于要按4对齐,11非4的倍数,最靠近的12才是
所以结构体A的大小应该是12
-------------------------------------------------------
例3:
struct A { char a; int b; double c; char d; };
1. 结构体最大长度类型double,长度为8
2. 假设结构体起始地址为0
a 的类型char大小为1,1小于8,按1对齐,地址空间范围为 0
b 的类型int 大小为4,4小于8,按4对齐,
由于接下来的起始地址1不是4的倍数,所以取4做起始地址
即b的地址空间范围应该是4~7
c 的类型double 大小为8,8等于8,按8对齐,
接着的起始地址应该是8,已经是8的倍数,
所以c的地址空间范围应该是 8~15
d 的类型char大小为1,1小于8,按1对齐,地址空间范围16
3. 结构体地址空间范围0~16, 大小为17,
17不是8的倍数,最靠近的为8的倍数的数应该是24,
所以该结构体大小为24
-------------------------------------------------------
测试:
test.c
#include <stdio.h> struct st_a { int a; char b; char c; }; struct st_b { char b; short c; int a; }; struct st_c { char b; int a; short c; }; int main(int argc, char* argv[]) { struct st_a a; struct st_b b; struct st_c c; printf("st_a size:%d\n", sizeof(a)); printf(" &a:%ld\n", &a.a); printf(" &b:%ld\n", &a.b); printf(" &c:%ld\n", &a.c); printf("st_b size:%d\n", sizeof(b)); printf(" &b:%ld\n", &b.b); printf(" &c:%ld\n", &b.c); printf(" &a:%ld\n", &b.a); printf("st_c size:%d\n", sizeof(c)); printf(" &b:%ld\n", &c.b); printf(" &a:%ld\n", &c.a); printf(" &c:%ld\n", &c.c); return 0; }
gcc -o t test.c
./t
输出:
st_a size:8 &a:140733530861264 &b:140733530861268 &c:140733530861269 st_b size:8 &b:140733530861248 &c:140733530861250 &a:140733530861252 st_c size:12 &b:140733530861232 &a:140733530861236 &c:140733530861240
以上内容出自http://blog.csdn.net/zhaori/article/details/7659268
再来看看原理:
不光结构体存在内存对齐一说,类(对象)也如此,甚至于所有变量在内存中的存储也有对齐一说(只是这些对程序员是透明的,不需要关心)。实际上,这种对齐是为了在空间与复杂度上达到平衡的一种技术手段,简单的讲,是为了在可接受的空间浪费的前提下,尽可能的提高对相同运算过程的最少(快)处理。先举个例子:
假设机器字长是32位的(即4字节,下面示例均按此字长),也就是说处理任何内存中的数据,其实都是按32位的单位进行的。现在有2个变量:
char A; int B;
假设这2个变量是从内存地址0开始分配的,如果不考虑对齐,应该是这样存储的(见下图,以intel上的little endian为例,为了形象,每16个字节分做一行,后同):
![](http://img1.51cto.com/attachment/201109/234241430.png)
因为计算机的字长是4字节的,所以在处理变量A与B时的过程可能大致为:
A:将0x00-0x03共32位读入寄存器,再通过左移24位再右移24位运算得到a的值(或与0x000000FF做与运算)
B:将0x00-0x03这32位读入寄存器,通过位运算得到低24位的值;再将0x04-0x07这32位读入寄存器,通过位运算得到高8位的值;再与最先得到的24位做位运算,才可得到整个32位的值。
上面叙述可知,对a的处理是最简处理,可对b的处理,本身是个32位数,处理的时候却得折成2部分,之后再合并,效率上就有些低了。
想解决这个问题,就需要付出几个字节浪费的代价,改为下图的分配方式:
![](http://img1.51cto.com/attachment/201109/234031687.png)
按上面的分配方式,A的处理过程不变;B却简单得多了:只需将0x04-0x07这32位读入寄存器就OK了。
我们可以具体谈结构体或类成员的对齐了:
结构体在编译成机器代码后,其实就没有本身的集合概念了,而类,实际上是个加强版的结构体,类的对象在实例化时,内存中申请的就是一些变量的空间集合(类似于结构体,同时也不包含函数指针)。这些集合中的每个变量,在使用中,都需要涉及上述的加工原则,自然也就需要在效率与空间之间做出权衡。
为了便捷加工连续多个相同类型原始变量,同时简化原始变量寻址,再汇总上述最少处理原则,通常可以将原始变量的长度做为针对此变量的分配单位,比如内存可用64个单元,如果某原始变量长度为8字节,即使机器字长为4字节,分配的时候也以8字节对齐(看似IO次数是相同的),这样,寻址、分配时,均可以按每8字节为单位进行,简化了操作,也可以更高效。
系统默认的对齐规则,追求的至少两点:1、变量的最高效加工 2、达到目的1的最少空间
举个例子,一个结构体如下:
//by www.datahf.net zhangyu typedef struct T { char c; //本身长度1字节 __int64 d; //本身长度8字节 int e; //本身长度4字节 short f; //本身长度2字节 char g; //本身长度1字节 short h; //本身长度2字节 };
对于成员C.c 无论如何,也是一次寄存器读入,所以先占一个字节。
对于成员C.d 是个64位的变量,如果紧跟着C.c存储,则读入寄存器至少需要3次,为了实现最少的2次读入,至少需要以4字节对齐;同时对于8字节的原始变量,为了在寻址单位上统一,则需要按8字节对齐,所以,应该分配到0x08-0xF的位置。
对于成员C.e 是个32位的变量,自然只需满足分配起始为整数个32位即可,所以分配至0x10-0x13。
对于成员C.f 是个16位的变量,直接分配在0x14-0x16上,这样,反正只需一次读入寄存器后加工,边界也与16位对齐。
对于成员C.g 是个8位的变量,本身也得一次读入寄存器后加工,同时对于1个字节的变量,存储在任何字节开始都是对齐,所以,分配到0x17的位置。
对于成员C.h 是个16位的变量,为了保证与16位边界对齐,所以,分配到0x18-0x1A的位置。
分配图如下(还不正确,耐心读下去):
![](http://img1.51cto.com/attachment/201109/004003168.png)
结构体C的占用空间到h结束就可以了吗?我们找个示例:如果定义一个结构体数组 CA[2],按变量分配的原则,这2个结构体应该是在内存中连续存储的,分配应该如下图:
![](http://img1.51cto.com/attachment/201109/004624736.png)
分析一下上图,明显可知,CA[1]的很多成员都不再对齐了,究其原因,是结构体的开始边界不对齐。
那结构体的开始偏移满足什么条件才可以使其成员全部对齐呢。想一想就明白了:很简单,保证结构体长度是原始成员最长分配的整数倍即可。
上述结构体应该按最长的.d成员对齐,即与8字节对齐,这样正确的分配图如下:
![](http://img1.51cto.com/attachment/201109/005212595.png)
当然结构体T的长度:sizeof(T)==0x20;
再举个例子,看看在默认对齐规则下,各结构体成员的对齐规则:
//by www.datahf.net zhangyu typedef struct A { char c; //1个字节 int d; //4个字节,要与4字节对齐,所以分配至第4个字节处 short e; //2个字节, 上述两个成员过后,本身就是与2对齐的,所以之前无填充 }; //整个结构体,最长的成员为4个字节,需要总长度与4字节对齐,所以, sizeof(A)==12 typedef struct B { char c; //1个字节 __int64 d; //8个字节,位置要与8字节对齐,所以分配到第8个字节处 int e; //4个字节,成员d结束于15字节,紧跟的16字节对齐于4字节,所以分配到16-19 short f; //2个字节,成员e结束于19字节,紧跟的20字节对齐于2字节,所以分配到20-21 A g; //结构体长为12字节,最长成员为4字节,需按4字节对齐,所以前面跳过2个字节, //到24-35字节处 char h; //1个字节,分配到36字节处 int i; //4个字节,要对齐4字节,跳过3字节,分配到40-43 字节 }; //整个结构体的最大分配成员为8字节,所以结构体后面加5字节填充,被到48字节。故: //sizeof(B)==48;
具体的分配图如下:
![](http://img1.51cto.com/attachment/201109/012057204.png)
上述全部测试代码如下:
//by www.datahf.net zhangyu #include "stdio.h" typedef struct A { char c; int d; short e; }; typedef struct B { char c; __int64 d; int e; short f; A g; char h; int i; }; typedef struct C { char c; __int64 d; int e; short f; char g; short h; }; typedef struct D { char a; short b; char c; }; int main() { B *b=new B; void *s[32]; s[0]=b; s[1]=&b->c; s[2]=&b->d; s[3]=&b->e; s[4]=&b->f; s[5]=&b->g; s[6]=&b->h; s[7]=&b->g.c; s[8]=&b->g.d; s[9]=&b->g.e; s[10]=&b->i; b->c= 0x11; b->d= 0x2222222222222222; b->e= 0x33333333; b->f=0x4444; b->g.c=0x50; b->g.d=0x51515151; b->g.e=0x5252; b->h=0x66; int i1=sizeof(A); int i2=sizeof(B); int i3=sizeof(C); int i4=sizeof(D); printf("i1:%d\ni2:%d\ni3:%d\ni4:%d\n",i1,i2,i3,i4);//12 48 32 6 }
运行时的内存情况如下图:
![](http://img1.51cto.com/attachment/201109/013014504.png)
最后,简单加工一下转载过来的内存对齐正式原则:
先介绍四个概念:
1)数据类型自身的对齐值:基本数据类型的自身对齐值,等于sizeof(基本数据类型)。
2)指定对齐值:#pragma pack (value)时的指定对齐值value。
3)结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。
4)数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中较小的那个值。
有效对齐值N是最终用来决定数据存放地址方式的值,最重要。有效对齐N,就是表示“对齐在N上”,也就是说该数据的"存放起始地址%N=0".而数据结构中的数据变量都是按定义的先后顺序来排放的。第一个数据变量的起始地址就是 数据结构的起始地址。结构体的成员变量要对齐排放,结构体本身也要根据自身的有效对齐值圆整(就是结构体成员变量占用总长度需要是对结构体有效对齐值的整 数倍)
#pragma pack (value)来告诉编译器,使用我们指定的对齐值来取代缺省的。
如#pragma pack (1) /*指定按2字节对齐*/
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
本文出处
相关文章推荐
- Linked List Cycle -- leetcode
- Twister—迭代MapReduce
- 第十周项目 3 日期时间类
- Eclipse导入Android签名
- ELF relocatable object file && ELF Executable object file && Run-time Memory Image 的结构
- nginx整合多个tomcat
- Window8 ComboBox 可编辑
- 升级python环境
- setContentView(R.layout.activity_main) Error解决方法
- ArcGIS For Android 定位模块的使用介绍
- 预处理器简介
- [ERROR] Failed to execute goal org.apache.maven.plugins:maven-install-plugin:2.3.1:
- CDH5.3.2中配置运行Spark SQL的Thrift Server
- 如何用简单易懂的例子解释隐马尔可夫模型?
- B-tree/B+tree/B*tree [转]
- 实例构造器和类型构造器
- python执行子进程实现进程间通信的方法
- jQuery中$.fn的用法示例介绍
- 数值属性的相异性:闵可夫斯基距离
- iOS 调试