南昌网站建设方案详细版,wordpress自适应手机顶部没有链接,个人开公司需要多少注册资金,西安市住房和城乡建设局官方网站#xff11;#xff1a;内存对齐定义#xff1a; 现在使用的计算机中内存空间都是按照字节划分的#xff0c;从理论上讲似乎对任何类型的变量的访问可以从任何地址开始#xff0c;但是实际上计算机系统对于基本数据类型在内存 中的存放位置都有限制#xff0c;要求这…内存对齐定义 现在使用的计算机中内存空间都是按照字节划分的从理论上讲似乎对任何类型的变量的访问可以从任何地址开始但是实际上计算机系统对于基本数据类型在内存 中的存放位置都有限制要求这些数据存储首地址是某个数K的倍数这样各种基本数据类型在内存冲就是按照一定的规则排列的而不是一个紧挨着一个排放这 就是内存对齐。 对齐模数 内存对齐中指定的对齐数值K成为对齐模数(Alignment Modulus)。当一种类型S的对齐模数与另一种类型T的对齐模数的比值是大于1的整数我们就称类型S的对齐要求比T强(严格)而称T比S弱(宽松)。 内存对齐的好处 内存对齐作为一种强制的要求第一简化了处理器与内存之间传输系统的设计第二可以提升读取数据的速度。各个硬件平台对存储空间的处理上有很大的不同。一 些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须 保证字节对齐.其他平台可能没有这种情况但是最常见的是如果不按照适合其平台要求对数据存放进行对齐会在存取效率上带来损失。比如有些平台每次读都是 从偶地址开始如果一个int型假设为32位系统如果存放在偶地址开始的地方那么一个读周期就可以读出这32bit而如果存放在奇地址开始的地 方就需要2个读周期并对两次读出的结果的高低字节进行拼凑才能得到该32bit数据。显然在读取效率上下降很多。 Intel的IA32架构的处理器则不管数据是否对齐都能正确工作。但是如果想提升性能应该注意内存对齐方式。 ANSI C标准中并没有规定相邻声明的变量在内存中一定要相邻。为了程序的高效性内存对齐问题由编译器自行灵活处理这样导致相邻的变量之间可能会有一些填充字 节。对于基本数据类型(int char等)他们占用的内存空间在一个确定硬件系统下有确定的值。ANSI C规定一种结构类型的大小是它所有字段的大小以及字段之间或字段尾部的填充区大小之和。 内存对齐策略 微软C编译器(cl.exe for 80×86)的对齐策略 第一 结构体变量的首地址能够被其最宽基本类型成员的大小所整除 备注编译器在给结构体开辟空间时首先找到结构体中最宽的基本数据类型然后寻找内存地址能被该基本数据类型所整除的位置作为结构体的首地址。将这个最宽的基本数据类型的大小作为上面介绍的对齐模数。 第二 结构体每个成员相对于结构体首地址的偏移量offset都是成员大小的整数倍如有需要编译器会在成员之间加上填充字节internal adding 备注:为结构体的一个成员开辟空间之前编译器首先检查预开辟空间的首地址相对于结构体首地址的偏移是否是本成员的整数倍若是则存放本成员反之则在本成员和上一个成员之间填充一定的字节以达到整数倍的要求也就是将预开辟空间的首地址后移几个字节。 第三 结构体的总大小为结构体最宽基本类型成员大小的整数倍如有需要编译器会在最末一个成员之后加上填充字节trailing padding。 备注结构体总大小是包括填充字节最后一个成员满足上面两条以外还必须满足第三条否则就必须在最后填充几个字节以达到本条要求。 填充字节就是为了使结构体字段满足内存对齐要求而额外分配给结构体的空间。对于结构体本身也存在着对齐要求ANSI C标准规定结构体类型的对齐要求不能比它所有字段中要求最严格的那个宽松但是可以更严格(但此非强制要求VC7.1就仅仅是让它们一样严格)。C标准 保证任何类型(包括自定义结构类型)的数组所占空间的大小一定等于一个单独的该类型数据的大小乘以数组元素的个数。换句话说数组各元素之间不会有空 隙。 总结规则如下 0 结构体变量的首地址能够被其最宽基本类型成员的大小所整除 1 VC6和VC71默认的内存对齐方式是 #pragam pack(8) 2 结构体中每个成员按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数中较小的一个对齐. 3: 结构体每个成员相对于结构体首地址的偏移量都是成员大小的整数倍. 4: 结构体本身也存在着对齐要求规则不能比它所有字段中要求最严格的那个宽松. 5 结构体的总大小为结构体最宽基本类型成员大小的整数倍,且应尽量节省内存。 6 在GCC中对齐模数的准则是对齐模数最大只能是 4也就是说即使结构体中有double类型对齐模数还是4所以对齐模数只能是124。 而且在上述的规则中第3条里offset必须是成员大小的整数倍 (1): 如果这个成员大小小于等于4则按照上述准则是可行的 (2): 如果成员的大小大于4则结构体每个成员相对于结构体首地址的偏移量只能按照是4的整数倍来进行判断是否添加填充。 typedef struct ms1 {char a;int b;
} MS1;typedef struct ms2 {int a;char b;
} MS2; MS1中有最强对齐要求的是b字段(int类型)字段a相对于首地址偏移量为0(1的倍数),直接存放此时如果直接存放字段b则字段b相对于结构体 变量首地址的偏移量为1(不是4的倍数)填充3字节b由偏移地址为4开始存放。也就是遵循了第2条与第3条规则而对于结构体变量本身根据规则4 对齐参数至少应该为4。根据规则5,sizeof(MS1) 8; 同样MS2分析得到的结果也是如此。 typedef struct ms3 {char a;short b;double c;
} MS3;typedef struct ms4 {char a;MS3 b;
} MS4; MS3中内存要求最严格的字段是c(8字节)MS3的对齐参数也是8字节; 那么MS4类型数据的对齐模数就与MS3中的double一致(为8)a字段后面应填充7个字节.sizeof(MS3) 16; sizeof(MS4) 24; 注意规则5中是说结构体的总大小为结构体最宽基本类型成员大小的整数倍。注意是基本类型这里的MS3不是基本类型。 对齐模数的选择只能是根据基本数据类型所以对于结构体中嵌套结构体只能考虑其拆分的基本数据类型。 例子3(GCC)
struct T {char ch;double d ;
}; 在GCC下sizeof(T)应该等于12个字节。VC8下为16字节。 ch为1字节没有问题的之后d的大小大于4对于d的对齐模数只能是4相对于结构体变量的首地址偏移量也只能是4而不能使8的整数倍由偏移量4开始存放结构体共占12字节。 这里并没有执行第5条规则。 位域情况 C99规定int、unsigned int和bool可以作为位域类型。但编译器几乎都对此作了扩展允许其它类型类型的存在。 如果结构体中含有位域(bit-field)总结规则如下 1) 如果相邻位域字段的类型相同且其位宽之和小于类型的sizeof大小则后面的字段将紧邻前一个字段存储直到不能容纳为止 2) 如果相邻位域字段的类型相同但其位宽之和大于类型的sizeof大小则后面的字段将从新的存储单元开始其偏移量为其类型大小的整数倍 3) 如果相邻的位域字段的类型不同则各编译器的具体实现有差异VC6采取不压缩方式不同位域字段存放在不同的位域类型字节中Dev-C和GCC都采取压缩方式 4如果位域字段之间穿插着非位域字段则不进行压缩 5) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,且应尽量节省内存。 备注当两字段类型不一样的时候对于不压缩方式例如 struct N {char c:2;int i:4;
}; 依然要满足不含位域结构体内存对齐准则第3条i成员相对于结构体首地址的偏移应该是4的整数倍所以c成员后要填充3个字节然后再开辟4个字节的空间作为int型其中4位用来存放i所以上面结构体在VC中所占空间为8个字节 而对于采用压缩方式的编译器来说遵循不含位域结构体内存对齐准则第3条不同的是如果填充的3个字节能容纳后面成员的位则压缩到填充字节中不能容纳则要单独开辟空间所以上面结构体N在GCC或者Dev- C中所占空间应该是4个字节。 例子4
typedef struct {char c:2;double i;int c2:4;
}N3; 按照含位域规则4在GCC下占据的空间为16字节在VC下占据的空间是24个字节。 结论 -------- 定义结构体的时候成员最好能从大到小来定义那样能相对的省空间。例如如下定义 struct A {double d;int i;char c;
}; 那么无论是windows下的vc系列编译器还是linux下的gcc都是16字节。 例子5
typedef union student{char name[10];long sno;char sex;float score [4];
} STU;
STU aa[5];
coutsizeof(aa)endl; union是可变的以其成员中最大的成员作为该union的大小16*5580 例子6
typedef struct student{char name[10];long sno;char sex;float score [4];
} STU;
STU aa[5];
coutsizeof(aa)endl; STU占空间为:10字节(char)空2字节4字节(long)1字节(char)空3字节16字节(float)36字节36*5180字节 例子7(VC8.0)
typedef struct bitstruct {int b1:5;int b2:2;int b3:3;
}bitstruct;int _tmain(int argc, _TCHAR* argv[]) {bitstruct b;memcpy(b,EM,sizeof(b));coutsizeof(b)endl;coutb.b1endlb.b2endlb.b3;return 0;
} 对于bitstruct是含有位域的结构体,sizeof(int)为4字节按照规则1、2首先b1占起始的5个字节, 根据含位域规则1, b2紧跟存放,b3也是紧跟存放的。 根据规则5,得到sizeof(bitstruct) 4。 现在主流的CPU,intel系列的是采用的little endian的格式存放数据,motorola系列的CPU采用的是big endian. 以主流的little endian分析 在进行内存分配的时候首先分配bitstruct的第一个成员类型int(4字节)这四个字节的存放按照低字节存储在低地址中的原则。 int共4个字节 第4个字节 - 第3个字节 - 第2个字节 - 第1个字节, 在内存中的存放方式如下所示。 而后为b1分配5位,这里优先分配的应该是低5位也就是第一个字节的低5位。 继而分配b2的2个字节也就是第1个字节中紧接着的2位。 最后分配b3的3位按照规则1、2b3还是紧接着存放的b3的最低位是第一个字节的最高位高两位为第2个字节的低两位。 内存分配图如下所示 字符E二进制为0100 0101,字符M的二进制为0100 1101在内存中存放如下所示 memcpy为按位拷贝的所以两片内存区可以直接对应上得到 b1的二进制形式为00101 高位为0正数5 b2的二进制形式为10 高位为1负数取反加1添加符号-2 b3的二进制形式为b3的最低一位是0,高位为01拼接后为010,正数2 内存分配情况感觉蛮奇怪的按如下修改例7b1应该为5b2为-2b3为-6VC8.0下验证正确。 typedef struct bitstruct {int b1:5;int b2:2;int b3:4;
}bitstruct;int _tmain(int argc, _TCHAR* argv[]) {bitstruct b;memcpy(b,EM,sizeof(b));coutsizeof(b)endl;coutb.b1endlb.b2endlb.b3;return 0;
} : 定义数组时的内存布局及内存字节对齐 int b10; int a[3]{1,2,3}; int c11; int b0x01020304; char cha; 对于一个数0x01020304; 对于一个数0x1122 使用Little Endian方式时低地址存放低字节由低地址向高地址存放为:4-3-2-1 而使用Big Endian方式时, 低地址存放高字节由低地址向高地址存放为:1-2-3-4 而在Little Endian模式中b的地址所指的就是 : 低地址(存放的是最低的字节) 1 void __cdecl func_cdcel(int i, char *szTest) {2 3 cout szTest在栈中的地址: szTest endl;4 5 cout szTest本身的值(指向的地址): (void*)szTest endlendl;6 7 8 9 cout i在堆栈中地址: i endl;
10
11 cout i的地址: i endl;
12
13
14
15 int k,k2;
16
17 cout 局部变量k的地址: k endl;
18
19 cout 局部变量k2的地址: k2 endl;
20
21 cout ------------------------------------------------------- endl;
22
23 }
24
25
26
27 void __stdcall func_stdcall(int i, char *szTest){
28
29 cout szTest在栈中的地址: szTest endl;
30
31 cout szTest本身的值(指向的地址): (void*)szTest endlendl;
32
33
34
35 cout i在堆栈中地址: i endl;
36
37 cout i的地址: i endl;
38
39
40
41 int k,k2;
42
43 cout 局部变量k的地址: k endl;
44
45 cout 局部变量k2的地址: k2 endl;
46
47 cout ------------------------------------------------------- endl;
48
49 }
50
51
52
53 int main(){
54
55 int a[4];
56
57 cout a[0]地址: a[0] endl;
58
59 cout a[1]地址: a[1] endl;
60
61 cout a[2]地址: a[2] endl;
62
63 cout a[3]地址: a[3] endl;
64
65
66
67 int i 0x22;
68
69 int j 8;
70
71 char szTest[4] {a,b, c, d};
72
73 cout i的地址:i endl;
74
75 cout szTest的地址:(void*)szTest endl;
76
77 func_cdcel(i, szTest);
78
79 func_stdcall(i, szTest);
80
81 } View Code 输出为 a[0]地址:0012FF54 a[1]地址:0012FF58 a[2]地址:0012FF5C a[3]地址:0012FF60 — 可见存储方式如上图所示a[3]在高地址先入栈而数组地址a为a[0]的地址(低地址) i的地址:0012FF48 — 这里进行了内存对齐i的起始地址必定是i所占内存大小的倍数 szTest的地址:0012FF30 szTest在栈中的地址:0012FE5C szTest本身的值(指向的地址):0012FF30 i在堆栈中地址:0012FE58 — i在堆栈中的地址低于szTest,也就是说szTest是先入栈的 i的地址:0012FE58 局部变量k的地址:0012FE48 局部变量k2的地址:0012FE3C ------------------------------------------------------- szTest在栈中的地址:0012FE5C szTest本身的值(指向的地址):0012FF30 i在堆栈中地址:0012FE58 i的地址:0012FE58 局部变量k的地址:0012FE48 局部变量k2的地址:0012FE3C 转载http://www.cnblogs.com/alex-tech/archive/2011/03/24/1993856.html 转载于:https://www.cnblogs.com/chaozhu/p/5600496.html