c++内存对齐 转载
转载自http://blog.csdn.net/chengonghao/article/details/51674166
例子举的特别好
很多文章大概都有像这样的结论:
1. 数据项只能存储在地址是数据项大小的整数倍的内存位置上;
2. 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
3. 对齐在N上,也就是说该数据的"存放起始地址%N=0
很明显,如果对数据存放地址的把握错误了的话,那么由此推断出来的地址对齐规则也就全都是错的了,而事实上也是如此。我研究这个课题 80%的时间都是花在这个上面,而真正的对齐规则一个下午应该就可以解决了。
当然,像上面的结论在一般情况下基本上是正确的,也就是说:
char变量的地址 %1 =0;
short变量地址 %2 =0;
int变量的地址 %4 =0;
double变量的地址 %8 =0
这里假设:
sizeof(char) = 1;
sizeof(short) = 2;
sizeof(int)=4;
sizeof(double)=8
这是 win32 平台上的实际值,此篇都以此假设为基础。
当这些变量是处于内存的数据区(或只读数据区)或者是从堆上分配出来的话,应该都是正确的,因为编译器和堆管理可能会帮你把这件事情做得很好,而程序员在代码里面基本上控制不了这些区域的变量的起始地址,实则我的多次实际测试也都符合上面的结论,即变量存放起始地址%N=0,结构体也符合首地址能够被其最宽基本类型成员的大小所整除。
而唯一我们比较好灵活控制的就是栈上数据,也就是局部变量。在 32 位的系统上栈的单位大小是 32 bit,即 4 字节,每一个栈的地址肯定也是 %4 = 0 的,如果一个栈存放了 char / short / int 的话那么他们肯定也满足 % N = 0。我们唯一可以找到破绽的就是使用一个8字节的数据,也就是 double,通过对栈上数据的巧妙安排让 double 变量的地址处于一个可以让 4 整除而不可以让 8 整除的地址上,那么我们的目的就达到了,"存放起始地址 % N = 0 "的结论即可推翻。当然,熟悉栈布局的话这些是可以轻易做到的。具体可以按如下步骤实验。
- #include "stdafx.h"
- int _tmain(int argc, _TCHAR* argv[])
- {
- double a = ;
- double b = ;
- double c = ;
- printf("&a=0x%X, &b=0x%X, &c=0x%X", &a, &b, &c);
- getchar();
- return ;
- }
以下是 VS2013 上的输出
可以看到,变量地址的最后一位是 4,不能被 8 整除(double 为 8 字节),因此已经可以推翻结论1了。
得到的这些地址可能有些随机性,这些地址依赖于具体的环境和编译参数等,但是,无论如何,我们实际看到的东西已经可以推翻上面结论 1 对地址假设的错误的结论了。我们再来看结构体的情况,把他们彻底推翻!为了让内部类型的变量一定可以按照其自身的对齐参数对齐,我们指定对齐参数设为16。
- #include "stdafx.h"
- #pragma pack(16)
- struct A{
- char a;
- double b;
- };
- int _tmain(int argc, _TCHAR* argv[]){
- struct A a;
- struct A b;
- struct A c;
- printf("&a=0x%X, &b=0x%X, &c=0x%X\n", &a, &b, &c);
- printf("&a.b=0x%X, &b.b=0x%X, &c.b=0x%X", &a.b, &b.b, &c.b);
- getchar();
- return ;
- }
由上面可以看到,结构体 A 的最大长度成员的类型是 double,就是成员变量 b 。 可以看到,结构体变量的起始地址不能被8整除,结构体中的 double 成员的地址也不能被 8 整除,当然这个的原因还是结构体变量的起始地址不能被8整除导致 double 的成员也不能被 8 整除。
我们实际看到的东西已经可以推翻上面结论 2 对地址假设的错误的结论了
尽管在不同的编译环境和系统上会有不同的值,但是从上面的两个实验我们确实得到了不满足那些结论的值,也就是说,无论如何,那些对变量起始地址的假设和结论一定错了~!
我本来以为我对栈布局还比较熟悉,通过多次试验是不是可以完全预测,但是后来证明这些一切都是徒劳,在不同的编译环境下,特别是编译器的优化选项,栈的存放简直就是五花八门,根本预测不到,我们确实不能对变量的地址做过多的假设。
2. 地址的字节对齐规则
地址对齐的规则也不是很复杂,只要把对起始地址有假设的那些结论稍微改一下基本上就差不多了。以下我先给出我自己总结的一个版本,然后再慢慢论证与解析。
编译器都有一个指定的对齐参数用于 structure, union, and class 成员,在 win32 平台上的编译器都是默认为 8,这个指定的对齐参数可以在代码里面使用 pack(n) 指令指定,n合法的值是1,2,4,8,16。
每个内部类型自身也都有一个自己的对齐参数,一般来说这个对齐参数就是 sizeof(具体type) 的值,在 win32 平台上就是采用sizeof作为具体类型的自身对齐参数的,也就是讲,char 的自身对齐参数是 1 , short 是 2 , int 是 4, float 也是 4, double 是 8 等。
地址对齐是相对于结构的成员来说的,单个内部类型的变量这种就没什么对齐不对齐的说法了。
结构的成员按照结构中声明的顺序依次排放,对齐的意思是成员相对于结构变量的起始地址的相对对齐,关键是在于相对于结构变量的起始地址的偏移。
有效对齐参数,内部类型的有效对齐是指它的自身对齐参数和指定对齐参数中较小的那个对齐参数;结构类型的有效对齐参数是指它的成员中,有效对齐参数最大的那个值。数组的有效对齐就是它的成员类型的有效对齐。
有了这些就可以得出对齐规则了:
1. (成员的起始地址相对于结构的起始地址的偏移) % (成员的有效对齐) == 0
2. (结构的总大小) % (结构的有效对齐) == 0
3. 如果无法满足对齐规则的话就填充字节直到满足对齐规则
从上面可以看到,如果指定的对齐参数大于了变量的自身对齐参数的话,指定的对齐参数将不起作用,这就是之前为什么要 #pragma pack(16) 的原因了,使得指定对齐参数没用,各个变量按照自己的类型的自身对齐参数对齐。
结构的总大小也要求符合对齐规则,主要是考虑到了结构体数组的情况,数组的各个成员是紧密排列的,不会有空隙,如果结构总大小满足对齐要求的话那么整个数组就自然满足对齐要求了,如果总大小不满足对齐要求的话,数组各个成员又要紧密排列,那么这个对齐就又没意义了,CPU 读取这些数组成员还是要花多余的开销。
说了这么多,还是举例子讲话来得实在。
- # pragma pack()
- struct A{
- char a;
- double b;
- };
第一个成员的地址就是结构的起始地址,所以它的地址相对于结构的起始地址的偏移是 0,而 a 是 char 类型,它的自身对齐是 1 小于指定的对齐参数 16,所以 a 的有效对齐是1,a 的起始地址偏移也满足 0 % 1 = 0;第二个成员是 double 类型,其自身对齐参数是 8,也小于指定的对齐参数,所以它的有效对齐是 8,这样我们指定的 # pragma pack(16) 就相当于一点用都没有了。而 double 类型的成员 b 要想满足对齐规则就必须在 a 的后面填充字节以使得 b 的地址相对于结构的起始地址的偏移至少为 8。所以结构 A的内存布局会是这样:
- CC CC CC CC CCCC CC
而 sizeof(A) = 16;0 分别表示 a 和 b 的位置,CC就是填充的 7 个字节。
- # pragma pack(16)
- struct A{
- char a;
- short c;
- double b;
- };
同样指定的对齐参数仍然没任何作用,a 还是在偏移为 0 的地址上,c 在 a 之后,c 的有效对齐就是自身对齐 2,位于相对起始地址偏移为 2 的地址上,满足对齐要求 2 % 2 = 0,c 和 a 之间填充了 1 个字节。b 仍然位于偏移地址为 8 的地址上,b 和 c 之间填充了 4 个字节。结构 A 的内存布局如下:
- 00 CC 00 00 CC CCCC CC 00 00 00 00 00 00 00 00
而 sizeof(A) = 16;0 分别代表 a,c,b的位置,CC 就是填充字节。
- # pragma pack()
- struct A{
- char a;
- double b;
- short c;
- };
指定对齐仍然没用,a 在偏移为 0 的地址上,b 在偏移为 8 的地址上,c 紧紧挨着 b 的屁股,因为此时的地址偏移 16 已经满足 c 的对齐要求 16 % 2 = 0;所以就没必要填充字节了。但是结构体 A 的总大小也要满足对齐规则的第二条,即 (结构的总大小)%(结构的有效对齐) == 0;而结构 A 的有效对齐就是各个成员中有效对齐最大的那个数,也就是 b的对齐参数 8,所以 A 的有效对齐就是 8,结构的总大小要满足对齐要求还必须在 c 后面填充 6 个字节。此时 A 的内存布局如下:
- CC CC CC CC CCCC CC CC CC CC CC CC CC
而 sizeof(A) = 24;0 分别代表 a,b 的位置,1111 代表 c 的位置。
- # pragma pack()
- struct A{
- char a;
- double b;
- short c;
- };
把指定对齐参数设置成 4,此时 a 和 c 的有效对齐仍然是其自身对齐,而 b 因为它的自身对齐 8 大于了指定的对齐 4,所以 b 的有效对齐现在变成了 4 而不再是 8 了。a 仍然位于偏移 0,b 要满足对齐规则的话,地址偏移必须是其有效对齐的整数倍,所以 b 的偏移应该是 4,c 仍然紧紧跟在 b 的后面,因为此时的偏移 12 满足了 c 的对齐要求12 % 2 = 0;结构 A 的有效对齐现在也变成了 4,即等于成员中最大的有效对齐,b 的有效对齐。A 的总大小要满足对齐规则的话还必须在 c 的后面填充 2 个字节,让总大小变为 16 字节。此时 A 的内存布局如下:
- CC CC CC CC CC
而 sizeof(A) = 16;0 分别代表 a,b 的位置,1111 代表c的位置。
- # pragma pack()
- struct A{
- char a;
- double b;
- };
- struct B{
- int i;
- struct A sa;
- int c;
- };
我们来看结构 B 的布局,把指定对齐参数设置成 8,其实也还是没起作用,我们最大的内部类型就是 8 的 double 了,刚好和指定的对齐参数相等。第一个成员 i 肯定是位于偏移为 0 的地址上了。然后第二个成员是一个结构成员,我们要找到这个成员的有效对齐参数,结构的有效对齐参数是其成员中最大的那个对齐参数,对于结构 A 来说就是 b 的对齐参数 8,所以 A 的有效对齐是 8。结构成员 sa 要想满足对齐要求,即 偏移 % 有效对齐 8 = 0;它的地址偏移应该为 8。所以 sa 和 i 之间需要填充 4 个字节。成员 c 仍然紧紧跟在 sa 后面,因为 sa 占 16 字节,此时的地址偏移 24 已经可以满足 c 的对齐要求 24 % 4 = 0 ;而结构 B 的总大小也要满足对齐规则,B 的有效对齐就是成员中最大的,sa的有效对齐 8。所以 B 的总大小要能被 8 整除,就必须在 c 的后面再填充 4 个字节。此时结构 B 的内存布局如下:
- CC CCCC CC CC CC CC CC CC CC CC CC CC CC CC
而 sizeof(A) = 32;0 分别代表 i,sa.a,sa.b 的位置,11111111代表 c 的位置。
- # pragma pack()
- struct A{
- char a;
- double b;
- };
- struct B{
- int i;
- int c;
- struct A sa;
- };
把 c 移到 sa 的上面,这样就不需要填充任何字节了。B的所有的成员刚好满足对齐规则。注意,结构 A 中的 b 和 a 之间还是要填充字节的,它内部要满足自己的对齐要求。此时 B 的内存布局如下:
- CC CC CC CC CC CC CC
而 sizeof(A) = 24;0分别代表 i ,sa.a,sa.b的位置,11111111 代表 c 的位置。
- # pragma pack()
- struct A{
- char a[];
- double b;
- char c[];
- int d[];
- };
数组成员 a 的有效对齐是和其成员类型 char 一样,1。成员 b 的有效对齐是指定的对齐参数 4,因为指定的比它自身的小。c 数组同样也是 1 字节对齐,数组 d 是 4 字节对齐,指定的对齐和它自身的对齐一样,都是 4。数组的各个成员是紧密排列的,所以,b 和 a 之间填充了 3 个字节,c 和 b之间不填充字节,d 和 c 之间填充3个字节,c 之后不填充字节。sizeof(A) = 80;
c++内存对齐 转载的更多相关文章
- 【转载】【内存对齐(二)】__declspec( align(#) )的用法和大小计算
转自:http://www.cppblog.com/deercoder/archive/2011/03/13/141747.html 感谢作者! 在上面讲到了关于pack的内存对齐和计算方法,这里继续 ...
- golang内存对齐分析(转载)
问题 type Part1 struct { a bool b int32 c int8 d int64 e byte } 在开始之前,希望你计算一下 Part1 共占用的大小是多少呢? func m ...
- C语言内存对齐详解
一.字节对齐基本概念 现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问,这就需要各种类型 ...
- 【转】C/C++ struct/class/union内存对齐
原文链接:http://www.cnblogs.com/Miranda-lym/p/5197805.html struct/class/union内存对齐原则有四个: 1).数据成员对齐规则:结构(s ...
- C/C++中的内存对齐问题和pragma pack命令详解
这个内存对齐问题,居然影响到了sizeof(struct)的结果值.突然想到了之前写的一个API库里,有个API是向后台服务程序发送socket请求.其中的socket数据包是一个结构体.在发送soc ...
- c++内存对齐原理
转载自http://blog.csdn.net/it_yuan/article/details/24651347 #类中的元素 0. 成员变量 1. 成员函数 2. 静态成员变量 3. 静 ...
- 从硬件到语言,详解C++的内存对齐(memory alignment)
转载请保留以下声明 作者:赵宗晟 出处:https://www.cnblogs.com/zhao-zongsheng/p/9099603.html 很多写C/C++的人都知道“内存对齐”的概念以及规则 ...
- C/C++中struct/union/class内存对齐
struct/union/class内存对齐原则有四个: 1).数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储 ...
- 为什么要内存对齐 Data alignment: Straighten up and fly right
转载于http://blog.csdn.net/lgouc/article/details/8235471 为了速度和正确性,请对齐你的数据. 概述:对于所有直接操作内存的程序员来说,数据对齐都是很重 ...
随机推荐
- 组件接口(API)设计指南[5]-最后的思考
*阅读其它章节: http://blog.csdn.net/cuibo1123/article/details/39894477 最后的思考 我通过困难的学习以及多年的失误.写了这片篇关于创建组件和a ...
- 提高C#编程水平的50个要点 你掌握了多少呢?
提高C#编程水平的50个要点,程序员都是追求极致的完美主义者,下面的这些注意点和要点,你都掌握运用了多少呢? 总是用属性(Property)来代替可访问的数据成员 在 readonly 和 const ...
- 深入了解jsonp解决跨域访问
在这个项目中,我们做的充分利用jsonp这是一个特点跨界,完成简单的单点登录认证和权限控制的统一.道,各有各的优点.各有各的优点,选择什么方式实现全然取决于我们自己或者项目经理的开发经验,对各种框架的 ...
- 《STL源代码剖析》---stl_hash_set.h阅读笔记
STL仅仅规定接口和复杂度,对于详细实现不作要求.set大多以红黑树实现,但STL在标准规格之外提供了一个所谓的hash_set,以hash table实现.hash_set的接口,hash_tabl ...
- WebService和AngularJS实现模糊过滤查询
WebService和AngularJS实现模糊过滤查询 [概要] 网上看到一个不错的帖子,用WebService获取json,然后在前端使用AngularJs进行过滤搜索,看完文章后,按自己的想 ...
- Effective C++(16) 成对使用new和delete时要采取相同的形式
问题聚焦: 我们都知道,new和delete要成对使用,但是有时候,事情往往不是按我们预期的那样发展. 对于单一对象和对象数组,我们要分开考虑 遇到typedef时,也需要 ...
- 迟到的 WPF 学习 —— 控件
这一章书中内容比较多而杂,但每个对象的内容又相对简短,所以只挑选里边有代表性的内容做记录. 1. Label 控件:一个基础的简单的 ContentControl,Labe 支持快捷键文本的设置,可以 ...
- download youtube video
using youtube-dl to download youtube video: (1) sudo apt-get install youtube-dl (2) run.sh #!/bin/ba ...
- 强烈推荐:240多个jQuery插件【转】
强烈推荐:240多个jQuery插件 概述 jQuery 是继 prototype 之后又一个优秀的 Javascript 框架.其宗旨是—写更少的代码,做更多的事情.它是轻量级的 js 库(压缩后只 ...
- iOS多线程的初步研究3
iOS多线程的初步研究(三) 弄清楚NSRunLoop确实需要花时间,这个类的概念和模式似乎是Apple的平台独有(iOS+MacOSX),很难彻底搞懂(iOS没开源,呜呜). 官网的解释是说run ...