C/C++中的内存对齐 C/C++中的内存对齐
一、什么是内存对齐、为什么需要内存对齐?
现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特 定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
字,双字,和四字在自然边界上不需要在内存中对齐。(对字,双字,和四字来说,自然边界分别是偶数地址,可以被4整除的地址,和可以被8整除的地址。)
无论如何,为了提高程序的性能,数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;然而,对齐的内存访问仅需要一次访问。
某些操作双四字的指令需要内存操作数在自然边界上对齐。如果操作数没有对齐,这些指令将会产生一个通用保护异常。双四字的自然边界是能够被16整除的地址。其他的操作双四字的指令允许未对齐的访问(不会产生通用保护异常),然而,需要额外的内存总线周期来访问内存中未对齐的数据。
用术语来讲就是,基本C类型在X86和ARM上都是自对齐的(self-aligned)。指针,不管是32位(4字节)还是64位(8字节)也是自对齐的。
自对齐能存取得更快是因为它能用一条指令来存取该类型数据。 另一方面,如果没有对齐限制,代码可能会在跨机器字边界存取的时候使用两条以上的指令。字符是特殊情况: 不管它在们在机器字的哪个位置,存取代价都是一样的。所以它们没有对齐要求。
二、对齐规则
每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的“对齐系数”。
规则:
(1)数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。
(2)结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。
(3)当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。
三、填充(padding)
现在我们来看一个简单的例子,变量在内存中的分布。
1
2 3 |
char *p;
char c; int a; |
如果你不知道数据对齐,你可能会假定这三个变量在内存里占用连续的字节。 即,在32位机器上4字节的指针后面会紧跟1字节的char,而它后面会紧跟4字节的int。在64位机器上,唯一的差别是指针是8字节的。
这是(在x86或ARM或任何自对齐的机器上)实际的情况:p 存储在4字节或8字节对齐的位置上(由机器的字长决定)。这是指针对齐-可能的最严格的情况。
c的存储紧跟着p。但a的4字节对齐要求造成一个缺口,就好像有第四个变量插入其中:
1
2 3 4 |
char *p; //4 或 8字节
char c; //1 字节 charpad[3]; //3 字节 int a; //4 字节 |
charpad[3]; 表示有3个字节浪费了。
如果a是2字节的short的话,这种情况内存分布是这样的:
1
2 3 4 |
char *p; //4 或 8字节
char c; //1 字节 charpad[1]; //1 字节 short a; //2 字节 |
如果你想让这些变量占用较少的空间,你可以交换a和c的位置:
1
2 3 |
char *p; //4 或 8字节
int a; //4 字节 char c; //1 字节 |
四、结构体的对齐和填充
上面说到结构体实际会和它的最宽成员一样对齐,编译这样做因为这是保证所有成员自对齐获取快速存取的最容易方法。
看看这个结构:
1
2 3 4 5 |
struct user{
char *name; char c; int age; }; |
假设是在32位机器上,内存分布是这样的:
1
2 3 4 5 6 |
struct user{
char *name; //4 字节 char c; //1 字节 charpad[3]; //3 字节 int age; //4 字节 }; |
这样的话sizeof(user) 为 12字节
那么如果我们交换c和age的位置。
1
2 3 4 5 |
struct user{
char *name; //4 字节 int age; //4 字节 char c; //1 字节 }; |
你可能会认为sizeof(user)为9,但是这样 sizeof(user) 还是为 12字节。
因为struct是根据最宽的成员对齐,所以最后还是有3个字节填充但未使用。
struct user uu[4];
这样,在uu数组里,每个成员都有3字节的拖尾填充,因为下一个结构体的第一个成员需要在4字节边界上对齐。
现在让我们考虑位域(bitfields)。它们使得你能声明比字节宽度更小的成员,低至1位。
1
2 3 4 5 6 7 |
struct st{
short s; char c; int flip:1; int nybble:4; int septet:7; }; |
从编译器的角度来看,struct st里的位域就像2字节,16位的字符数组,只用到了12位。为了使结构体的长度是它的最宽成员长度(即sizeof(short))的整数倍,还有一个字节的填充:
1
2 3 4 5 6 7 8 9 |
struct st{
short s; //2个字节 char c; //1个字节 int flip:1; //总 1 bit int nybble:4; //总 5 bits int septet:7; //总 12 bits int pad:4; //总16 bits charpad; //1个字节 }; |
如果你的结构体中含有结构体,里面的结构体也要和最长的标量有相同的对齐。
五、结构成员重排
来看看32位系统下的这两种情况:
1
2 3 4 5 6 7 8 9 10 11 12 13 |
struct user{
char a; //1字节 charpad[3]; //3字节 int c; //4字节 char b; //1字节 charpad[3]; //3字节 }; struct user{ int c; //4字节 char a; //1字节 char b; //1字节 charpad[2]; //2字节 }; |
上面结构成员都是一样,只是顺序不一样,但是大小前面一个是12字节,后面一个是8字节。
首先我们注意到溢出只发生在两个地方。 一个是较大的数据类型(从而需要更严格的对齐)跟在较小的数据后面。另一个是结构体自然结束的位置到跨步地址之间需要填充,以使下一个相同结构能正确地对齐。最简单的消除溢出的方式是按对齐值的递减来排序成员。
1
2 3 4 5 |
union u{
char a; int b; long double c; }; //大小为8字节 32位 |
struct/class/union内存对齐原则都是一样的。
C/C++中的内存对齐 C/C++中的内存对齐的更多相关文章
- 什么是内存对齐,go中内存对齐分析
内存对齐 什么是内存对齐 为什么需要内存对齐 减少次数 保障原子性 对齐系数 对齐规则 总结 参考 内存对齐 什么是内存对齐 弄明白什么是内存对齐的时候,先来看一个demo type s struct ...
- stm32中使用#pragma pack(非常有用的字节对齐用法说明)
#pragma pack(4) //按4字节对齐,但实际上由于结构体中单个成员的最大占用字节数为2字节,因此实际还是按2字节对齐 typedef struct { char buf[3];//bu ...
- Java中的String到底占用多大的内存空间?你所了解的可能都是错误的!!
写在前面 最近小伙伴加群时,我总是问一个问题:Java中的String类占用多大的内存空间?很多小伙伴的回答着实让我哭笑不得,有说不占空间的,有说1个字节的,有说2个字节的,有说3个字节的,有说不知道 ...
- .NET Core中遇到奇怪的线程死锁问题:内存与线程数不停地增长
一个 asp.net core 站点,之前运行在Linux 服务器上,运行一段时间后有时站点会挂掉,在日志中记录很多“EMFILE too many open files”的错误: Microsoft ...
- c++中的内存空间不足和自定义处理内存不足
new操作符动态分配内存时,首先它会调用对象的operator new()函数分配相应大的内存(如果对象类没有重载operator new()函数,则默认调用<new>头文件里的opera ...
- 解决MWPhotoBrowser中的SDWebImage加载大图导致的内存警告问题
下面两种现象,用同一种方法解决 1.解决MWPhotoBrowser中的SDWebImage加载大图导致的内存警告问题 2.突然有一天首页访问图片很慢,至少隔20多秒所有图片才会出来.(解析:app使 ...
- Java中基本数据类型的存储方式和相关内存的处理方式(java程序员必读经典)
1.java是如何管理内存的 java的内存管理就是对象的分配和释放问题.(其中包括两部分) 分配:内存的分配是由程序完成的,程序员需要通过关键字new为每个对象申请内存空间(基本类型除外),所有的对 ...
- 如何修改myeclipse 内存?eclipse.ini中各个参数的作用。
修改MyEclipse/eclipse文件夹中配置文件eclipse.ini中的内存分配就哦了 =================================== 一般的ini文件设置主要包括以下 ...
- 九、Android学习笔记_ Android开发中使用软引用和弱引用防止内存溢出
在<Effective Java 2nd Edition>中,第6条“消除过期的对象引用”提到,虽然Java有 垃圾回收机制,但是只要是自己管理的内存,就应该警惕内存泄露的问题,例如的对象 ...
随机推荐
- android的消息处理机制(图+源码分析)——Looper,Handler,Message
android源码中包含了大量的设计模式,除此以外,android sdk还精心为我们设计了各种helper类,对于和我一样渴望水平得到进阶的人来说,都太值得一读了.这不,前几天为了了解android ...
- Qt学习之路(34): 国际化(下)
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://devbean.blog.51cto.com/448512/245063 上次 ...
- Java获取当前目录方法整理
假设项目路径是E:\Workspaces\MyProgram\FilePath 1.使用System.getProperty("user.dir"),获得项目的根路径,返回Stri ...
- android开发之手势识别
由于精确度等原因,手势识别在android中用的并不多,不过这并不妨碍我们来玩玩这个神奇的玩意. 在android中要使用手势,先得建立手势库,建立手势库非常简单,新建一个android sample ...
- git操作github
转自http://www.cnblogs.com/fnng/archive/2012/01/07/2315685.html 怕找不到~ 本文在我之前的那篇<git/github学习笔记>的 ...
- Android界面的View以及ViewGroup的区别
因为这个问题会经常成为面试的热点,所以我们来谈谈View以及ViewGroup的区别. 先看看View及ViewGroup类关系 Android View和ViewGroup从组成架构上看,似乎 ...
- OpenXML: Asp.net利用OpenXML 导出Excel.
http://www.cnblogs.com/skyfei/archive/0001/01/01/Openxml.html
- C#当中的多线程_任务并行库(中)
发现自己有点懒了!也可能是越往后越难了,看书理解起来有点费劲,所以这两天就每天更新一点学习笔记吧. 4.5 将APM模式转化为任务 书上提供的三种方式 方式一: class Program ...
- UITextField 对输入金额的约束
[2016/1/18更新] -- 五个人辛辛苦苦干了一年的项目终于上线了,今天有空看了一下正则表达式教程,然后开始rebuild之前的种种对字符串的约束,首先就从这个金额输入框开始吧,修改后的代码如下 ...
- Qt程序开机启动的怪现象————无法正常显示程序皮肤
事情很简单:最近公司项目在做即时通讯软件,类似QQ.该软件应该支持开机启动这样的常用功能.但是实际上开发该功能的时候碰到了个问题:开机启动程序无法正常加载皮肤文件. 这个问题让我头疼了很久啊.最终确定 ...