内存对齐分配策略(含位域模式)

1:内存对齐定义:
    现在使用的计算机中内存空间都是按照字节划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但是实际上计算机系统对于基本数据类型在内存 中的存放位置都有限制,要求这些数据存储首地址是某个数K的倍数,这样各种基本数据类型在内存冲就是按照一定的规则排列的,而不是一个紧挨着一个排放,这 就是内存对齐。

对齐模数:
    内存对齐中指定的对齐数值K成为对齐模数(Alignment Modulus)。当一种类型S的对齐模数与另一种类型T的对齐模数的比值是大于1的整数,我们就称类型S的对齐要求比T强(严格),而称T比S弱(宽松)。

2:内存对齐的好处:
    内存对齐作为一种强制的要求,第一简化了处理器与内存之间传输系统的设计,第二可以提升读取数据的速度。各个硬件平台对存储空间的处理上有很大的不同。一 些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须 保证字节对齐.其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是 从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地 方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数据。显然在读取效率上下降很多。
    Intel的IA32架构的处理器则不管数据是否对齐都能正确工作。但是如果想提升性能,应该注意内存对齐方式。
ANSI C标准中并没有规定相邻声明的变量在内存中一定要相邻。为了程序的高效性,内存对齐问题由编译器自行灵活处理,这样导致相邻的变量之间可能会有一些填充字 节。对于基本数据类型(int char等),他们占用的内存空间在一个确定硬件系统下有确定的值。ANSI C规定一种结构类型的大小是它所有字段的大小以及字段之间或字段尾部的填充区大小之和。

3:内存对齐策略:
微软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,所以对齐模数只能是1,2,4。
      而且在上述的规则中,第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];
cout<<sizeof(aa)<<endl;

union是可变的以其成员中最大的成员作为该union的大小16*5=5=80

例子6:
typedef struct student{char name[10];long sno;char sex;float score [4];
} STU;
STU aa[5];
cout<<sizeof(aa)<<endl; 

STU占空间为:10字节(char)+空2字节+4字节(long)+1字节(char)+空3字节+16字节(float)=36字节,36*5=180字节

例子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));cout<<sizeof(b)<<endl;cout<<b.b1<<endl<<b.b2<<endl<<b.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、2,b3还是紧接着存放的,b3的最低位是第一个字节的最高位,高两位为第2个字节的低两位。
内存分配图如下所示:

QQ截图未命名

字符E二进制为0100 0101,字符M的二进制为0100 1101,在内存中存放如下所示:

QQ截图未命名2
memcpy为按位拷贝的,所以两片内存区可以直接对应上,得到
b1的二进制形式为:00101 ,高位为0,正数,5
b2的二进制形式为:10 ,高位为1,负数,取反加1,添加符号,-2
b3的二进制形式为:b3的最低一位是0,高位为01,拼接后为010,正数,2

内存分配情况感觉蛮奇怪的,按如下修改例7,b1应该为5,b2为-2,b3为-6,VC8.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));cout<<sizeof(b)<<endl;cout<<b.b1<<endl<<b.b2<<endl<<b.b3;return 0;
}

4: 定义数组时的内存布局及内存字节对齐

int b=10;

int a[3]={1,2,3};

int c=11;

image

int b=0x01020304;

char ch='a';

对于一个数0x01020304; 对于一个数0x1122

使用Little Endian方式时,低地址存放低字节,由低地址向高地址存放为:4->3->2->1
而使用Big Endian方式时, 低地址存放高字节,由低地址向高地址存放为:1->2->3->4

 

而在Little Endian模式中,b的地址所指的就是 : 低地址(存放的是最低的字节)

 

image

 1 void __cdecl func_cdcel(int i, char *szTest) {
 2 
 3       cout << "szTest在栈中的地址:" << &szTest << endl;
 4 
 5       cout << "szTest本身的值(指向的地址):" << (void*)szTest << endl<<endl;
 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 << endl<<endl;
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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/490455.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

科技部发布新一批国家新一代人工智能开放创新平台

来源&#xff1a;科技部8月29日上午&#xff0c;在上海举办的2019世界人工智能大会开幕式上&#xff0c;科技部李萌副部长发布了新启动建设的十家国家新一代人工智能开放创新平台。分别是&#xff1a;依托上海依图网络科技有限公司建设视觉计算国家新一代人工智能开放创新平台&…

开发里程碑计划_里程碑——让你轻松控制项目进度

对于项目结果的最好控制就是控制项目开发的过程&#xff0c;也就是控制项目开发过程中的几个关键节点——项目的里程碑事件。通过对里程碑事件的控制用于评估项目各阶段工作进展的有效性&#xff0c;以及及时的明确开发过程中存在的风险过程。我们在做项目开发计划的时候&#…

SQL Server数据库大型应用解决方案总结【转】

【IT168 技术】随着互联网应用的广泛普及&#xff0c;海量数据的存储和访问成为了系统设计的瓶颈问题。对于一个大型的互联网应用&#xff0c;每天百万级甚至上亿的PV无疑对数据库造成了相当高的负载。对于系统的稳定性和扩展性造成了极大的问题。 一、负载均衡技术 负载均衡集…

Python正在吞噬世界

来源&#xff1a; AI前线AI 前线导读&#xff1a;2018 到 2019 年&#xff0c;所有编程语言的流行度都在下滑&#xff0c;除了 Python。Python 为什么会变得越来越火&#xff1f;本文梳理了 Python 的发展史&#xff0c;试图揭示背后的秘密。1994 年末&#xff0c;一群来自美国…

登录python自动化_Appium+Python实现自动化登录

#AppiumPython实现自动化测试 Appium简介 官方的概述为&#xff1a; Appium is an open source test automation framework for use with native, hybrid and mobile web apps. It drives iOS, Android, and Windows apps using the WebDriver protocol. Appium是一个开源的测试…

lvalue-xvalue-prvalue

• iM: has identity and cannot be moved from • im: has identity and can be moved from (e.g. the result of casting an lvalue to a rvalue reference) • Im: does not have identity and can be moved from 转载于:https://www.cnblogs.com/Searchor/p/5604736.html

城市仿真为何成为大势所趋?

来源&#xff1a;智慧城市联合实验室不知从何时起&#xff0c;人们开始从城市的角度构想&#xff0c;未来可以建设一个虚拟城市&#xff0c;来映射真实城市的运行&#xff0c;再进一步管理真实城市的运行。现在&#xff0c;这种构想已经初步实现……&#xff08;内附《城市环境…

linux网络配置_linux复制和网络配置的小注意事项

centos7&#xff0c;vbox复制出来的虚拟机无法ping通。其实这是vbox低版本没有解决的一个小问题&#xff0c;复制过去&#xff0c;主机的mac重新生成了(复制时候一定要选这个选项),但是配置文件的mac还是被复制的主机配置&#xff0c;网上搜索很多方案各种改&#xff0c;其实只…

实验室培养的迷你大脑,产生了脑电波...

十月龄如豌豆大小的类器官。图片来源&#xff1a;Muotri Lab来源&#xff1a;中国生物技术网近日&#xff0c;来自美国加州大学圣地亚哥分校的科学家用发育了功能性神经网络的干细胞创造了“迷你大脑”。尽管这些实验室发育的大脑比人脑小一百万倍&#xff0c;但它们是第一个被…

jupyternotebook运行python_jupyter notebook参数化运行python方式

Updates &#xff08;2019.8.14 19:53&#xff09;吃饭前用这个方法实战了一下&#xff0c;吃完回来一看好像不太行&#xff1a;跑完一组参数之后&#xff0c;到跑下一组参数时好像没有释放之占用的 GPU&#xff0c;于是 notebook 上的结果&#xff0c;后面好几条都报错说 cuda…

重磅|PPT讲解机器人产业发展现状与未来展望,重磅资料

来源&#xff1a;机器人大讲堂前段时间咱们分享过王喜文博士做的关于《5G》的PPT&#xff0c;大家反映做的很棒&#xff0c;最近王喜文博士在机械工业出版社又出了一本新书&#xff1a;智能&#xff1a;《新一代人工智能发展规划》解读&#xff0c;ISBN&#xff1a;978-7-111-6…

中国之光!中国最酷黑科技30强名单公布!

来源&#xff1a;智慧芽当前的中国正在迎来一个科技大爆发的时代&#xff0c;科技成果输出的速度远远快于经济成长的速度。中国现在每年产出科技成果&#xff0c;居世界第二&#xff0c;且正在高速接近美国。最好的一个证明就是含金量相对较高的PCT国际专利申请量&#xff0c;2…

python为什么没有指针_Python 没有指针,如何解算法题?

&#x1f446;“Python猫” &#xff0c;一个值得加星标的公众号 花下猫语&#xff1a; 今天一大早&#xff0c;读者群里又讨论了 Python 的“指针”问题。之前在公众号里发布过樱雨楼小姐姐的《对比 C 和 Python&#xff0c;谈谈指针与引用》&#xff0c;它从概念上有比较清晰…

深度 | 刘群:基于深度学习的自然语言处理,边界在哪里?

来源&#xff1a; AI科技评论 编辑 | Camel四大边界&#xff1a;数据边界、语义边界、符号边界和因果边界当前&#xff0c;深度学习之于自然语言处理&#xff0c;有其局限性。那么它所能起作用的边界在哪里呢&#xff1f;对此问题&#xff0c;我们应当深思。近日&#xff0c;在…

html border阴影效果_【开发小技巧】026—如何使用HTML和CSS创建浮动框阴影效果?...

英文 | https://www.geeksforgeeks.org/how-to-create-floating-box-effect-using-html-and-css/?refrp浮动框效果是自定义框阴影技术的经典示例。在这种技术中&#xff0c;我们无需使用CSS提供的box-shadow属性即可创建逼真的阴影效果。实现方法&#xff1a;在选择器之后使用…

NLP这两年:15个预训练模型对比分析与剖析

来源 | 知乎前言在之前写过的《NLP的游戏规则从此改写&#xff1f;从word2vec, ELMo到BERT》一文中&#xff0c;介绍了从word2vec到ELMo再到BERT的发展路径。而在BERT出现之后的这大半年的时间里&#xff0c;模型预训练的方法又被Google、Facebook、微软、百度、OpenAI等极少数…

多余的读写端口什么时候会对程序造成影响_程序员需要了解的硬核知识之控制硬件...

应用和硬件的关系我们作为程序员一般很少直接操控硬件&#xff0c;我们一般通过 C、Java 等高级语言编写的程序起到间接控制硬件的作用。所以大家很少直接接触到硬件的指令&#xff0c;硬件的控制是由 Windows 操作系统 全权负责的。你一定猜到我要说什么了&#xff0c;没错&am…

直击2019WAIC丨李德毅:人工智能是脱离意识的工具,需要约束的是人类自己

转自 上观新闻中国工程院院士、中国人工智能学会理事长李德毅在2019世界人工智能大会“科学前沿”主论坛上表示&#xff0c;人工智能就是人类智能的体外延伸&#xff0c;可以脱离意识而存在&#xff0c;应该将其看作一种工具。李德毅认为&#xff0c;意识和智能相互之间有很多典…

matplotlib给坐标轴特定的位置加上文字

比如我想在横坐标0和1这两个位置分别用文字“y1”和“y2”代替&#xff0c;即实现以下这种效果&#xff1a; plt.xlim([-1, 2]) plt.xticks([0, 1], ["y1", "y2"])