一、什么是字节对齐,为什么要对齐

现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特 定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。

对齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问 一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对 数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那 么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数 据。显然在读取效率上下降很多。

二、请看下面的结构:

struct MyStruct

{

double dda1;

char dda;

int type

};

对结构MyStruct采用sizeof会出现什么结果呢?sizeof(MyStruct)为多少呢?也许你会这样求:

sizeof(MyStruct)=sizeof(double)+sizeof(char)+sizeof(int)=13

但是当在VC中测试上面结构的大小时,你会发现sizeof(MyStruct)为16。你知道为什么在VC中会得出这样一个结果吗?

其实,这是VC对变量存储的一个特殊处理。为了提高CPU的存储速度,VC对一些变量的起始地址做了“对齐”处理。在默认情况下,VC规定各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数。下面列出常用类型的对齐方式(vc6.0,32位系统)。

类型

对齐方式(变量存放的起始地址相对于结构的起始地址的偏移量)

Char

偏移量必须为sizeof(char)即1的倍数

int

偏移量必须为sizeof(int)即4的倍数

float

偏移量必须为sizeof(float)即4的倍数

double

偏移量必须为sizeof(double)即8的倍数

Short

偏移量必须为sizeof(short)即2的倍数

各成员变量在存放的时候根据在结构中出现的顺序依次申请空间,同时按照上面的对齐方式调整位置,空缺的字节VC会自动填充。同时VC为了确保结构的大小为结构的字节边界数(即该结构中占用最大空间的类型所占用的字节数)的倍数,所以在为最后一个成员变量申请空间后,还会根据需要自动填充空缺的字节。

下面用前面的例子来说明VC到底怎么样来存放结构的。

struct MyStruct

{

double dda1;

char dda;

int type

};

为上面的结构分配空间的时候,VC根据成员变量出现的顺序和对齐方式,先为第一个成员dda1分配空间,其起始地址跟结构的起始地址相同(刚好偏移量0刚好为sizeof(double)的倍数),该成员变量占用sizeof(double)=8个字节;接下来为第二个成员dda分配空间,这时下一个可以分配的地址对于结构的起始地址的偏移量为8,是sizeof(char)的倍数,所以把dda存放在偏移量为8的地方满足对齐方式,该成员变量占用 sizeof(char)=1个字节;接下来为第三个成员type分配空间,这时下一个可以分配的地址对于结构的起始地址的偏移量为9,不是sizeof (int)=4的倍数,为了满足对齐方式对偏移量的约束问题,VC自动填充3个字节(这三个字节没有放什么东西),这时下一个可以分配的地址对于结构的起始地址的偏移量为12,刚好是sizeof(int)=4的倍数,所以把type存放在偏移量为12的地方,该成员变量占用sizeof(int)=4个字节;这时整个结构的成员变量已经都分配了空间,总的占用的空间大小为:8+1+3+4=16,刚好为结构的字节边界数(即结构中占用最大空间的类型所占用的字节数sizeof(double)=8)的倍数,所以没有空缺的字节需要填充。所以整个结构的大小为:sizeof(MyStruct)=8+1+ 3+4=16,其中有3个字节是VC自动填充的,没有放任何有意义的东西。

下面再举个例子,交换一下上面的MyStruct的成员变量的位置,使它变成下面的情况:

struct MyStruct

{

char dda;

double dda1;

int type

};

这个结构占用的空间为多大呢?在VC6.0环境下,可以得到sizeof(MyStruc)为24。结合上面提到的分配空间的一些原则,分析下VC怎么样为上面的结构分配空间的。(简单说明)

struct MyStruct

{

char dda;      //偏移量为0,满足对齐方式,dda占用1个字节;

double dda1;//下一个可用的地址的偏移量为1,不是sizeof(double)=8

//的倍数,需要补足7个字节才能使偏移量变为8(满足对齐

//方式),因此VC自动填充7个字节,dda1存放在偏移量为8

//的地址上,它占用8个字节。

int type;    //下一个可用的地址的偏移量为16,是sizeof(int)=4的倍

//数,满足int的对齐方式,所以不需要VC自动填充,type存

//放在偏移量为16的地址上,它占用4个字节。

};//所有成员变量都分配了空间,空间总的大小为1+7+8+4=20,不是结构

//的节边界数(即结构中占用最大空间的类型所占用的字节数sizeof

//(double)=8)的倍数,所以需要填充4个字节,以满足结构的大小为

//sizeof(double)=8的倍数。

所以该结构总的大小为:sizeof(MyStruc)为1+7+8+4+4=24。其中总的有7+4=11个字节是VC自动填充的,没有放任何有意义的东西。

VC对结构的存储的特殊处理确实提高CPU存储变量的速度,但是有时候也带来了一些麻烦,我们也屏蔽掉变量默认的对齐方式,自己可以设定变量的对齐方式。

VC 中提供了#pragma pack(n)来设定变量以n字节对齐方式。n字节对齐就是说变量存放的起始地址的偏移量有两种情况:第一、如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式,第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。结构的总大小也有个约束条件,分下面两种情况:如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数;

否则必须为n的倍数。下面举例说明其用法。

#pragma pack(push) //保存对齐状态

#pragma pack(4)//设定为4字节对齐

struct test

{

char m1;

double m4;

int m3;

};

#pragma pack(pop)//恢复对齐状态

以上结构的大小为16,下面分析其存储情况,首先为m1分配空间,其偏移量为0,满足我们自己设定的对齐方式(4字节对齐),m1占用1个字节。接着开始为 m4分配空间,这时其偏移量为1,需要补足3个字节,这样使偏移量满足为n=4的倍数(因为sizeof(double)大于n),m4占用8个字节。接着为m3分配空间,这时其偏移量为12,满足为4的倍数,m3占用4个字节。这时已经为所有成员变量分配了空间,共分配了16个字节,满足为n的倍数。如果把上面的#pragma pack(4)改为#pragma pack(16),那么我们可以得到结构的大小为24。(请读者自己分析)

三、再看下面这个例子

#pragma pack(8)

struct S1{

char a;

long b;

};

struct S2 {

char c;

struct S1 d;

long long e;

};

#pragma pack()

sizeof(S2)结果为24.

成员对齐有一个重要的条件,即每个成员分别对齐.即每个成员按自己的方式对齐.

也就是说上面虽然指定了按8字节对齐,但并不是所有的成员都是以8字节对齐.其对齐的规则是,每个成员按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数(这里是8字节)中较小的一个对齐.并且结构的长度必须为所用过的所有对齐参数的整数倍,不够就补空字节.

S1中,成员a是1字节默认按1字节对齐,指定对齐参数为8,这两个值中取1,a按1字节对齐;成员b是4个字节,默认是按4字节对齐,这时就按4字节对齐,所以sizeof(S1)应该为8;

S2 中,c和S1中的a一样,按1字节对齐,而d 是个结构,它是8个字节,它按什么对齐呢?对于结构来说,它的默认对齐方式就是它的所有成员使用的对齐参数中最大的一个,S1的就是4.所以,成员d就是按4字节对齐.成员e是8个字节,它是默认按8字节对齐,和指定的一样,所以它对到8字节的边界上,这时,已经使用了12个字节了,所以又添加了4个字节的空,从第16个字节开始放置成员e.这时,长度为24,已经可以被8(成员e按8字节对齐)整除.这样,一共使用了24个字节.

a b

S1的内存布局:11**,1111,

c S1.a S1.b d

S2的内存布局:1***,11**,1111,****11111111

这里有三点很重要:

1.每个成员分别按自己的方式对齐,并能最小化长度。

2.复杂类型(如结构)的默认对齐方式是它最长的成员的对齐方式,这样在成员是复杂类型时,可以最小化长度。

3.对齐后的长度必须是成员中最大的对齐参数的整数倍,这样在处理数组时可以保证每一项都边界对齐。

Win32平台下的微软 编译器(cl.exe for 80×86)的对齐策略:

1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;

备注:编译器在给结构体开辟空间时,首先找到结构体中最宽的基本数据类型,然后寻找内存地址能被该基本数据类型所整除的位置,作为结构体的首地址。将这个最宽的基本数据类型的大小作为上面介绍的对齐模数。

2) 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding);

备注:为结构体的一个成员开辟空间之前,编译器首先检查预开辟空间的首地址相对于结构体首地址的偏移是否是本成员的整数倍,若是,则存放本成员,反之,则在本成员和上一个成员之间填充一定的字节,以达到整数倍的要求,也就是将预开辟空间的首地址后移几个字节。

3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要,编译器会在最末一个成员之后加上填充字节(trailing padding)。

备注:结构体总大小是包括填充字节,最后一个成员满足上面两条以外,还必须满足第三条,否则就必须在最后填充几个字节以达到本条要求。

以上来自:http://wenku.baidu.com/view/8eaaa26925c52cc58bd6bed2.html

#pragma pack( [show] | [push | pop] [, identifier], n )

说明:
1,pack提供数据声明级别的控制,对定义不起作用;
2,调用pack时不指定参数,n将被设成默认值;
3,一旦改变数据类型的alignment,直接效果就是占用memory的减少,但是performance会下降;

语法具体分析:
1,show:可选参数;显示当前packing aligment的字节数,以warning message的形式被显示;
2,push:可选参数;将当前指定的packing alignment数值进行压栈操作,这里的栈是the internal compiler stack,同时设置当前的packing alignment为n;如果n没有指定,则将当前的packing alignment数值压栈;
3,pop:可选参数;从internal compiler stack中删除最顶端的record;如果没有指定n,则当前栈顶record即为新的packing alignment数值;如果指定了n,则n将成为新的packing aligment数值;如果指定了identifier,则internal compiler stack中的record都将被pop直到identifier被找到,然后pop出identitier,同时设置packing alignment数值为当前栈顶的record;如果指定的identifier并不存在于internal compiler stack,则pop操作被忽略;
4,identifier:可选参数;当同push一起使用时,赋予当前被压入栈中的record一个名称;当同pop一起使用时,从internal compiler stack中pop出所有的record直到identifier被pop出,如果identifier没有被找到,则忽略pop操作;
5,n:可选参数;指定packing的数值,以字节为单位;缺省数值是8,合法的数值分别是1、2、4、8、16。

重要规则:
1,复杂类型中各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个类型的地址相同;
2,每个成员分别对齐,即每个成员按自己的方式对齐,并最小化长度;规则就是每个成员按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数中较小的一个对齐;
3,结构、联合或者类的数据成员,第一个放在偏移为0的地方;以后每个数据成员的对齐,按照#pragma pack指定的数值和这个数据成员自身长度两个中比较小的那个进行;也就是说,当#pragma pack指定的值等于或者超过所有数据成员长度的时候,这个指定值的大小将不产生任何效果;
4,复杂类型(如结构)整体的对齐是按照结构体中长度最大的数据成员和#pragma pack指定值之间较小的那个值进行;这样在成员是复杂类型时,可以最小化长度;
5,结构整体长度的计算必须取所用过的所有对齐参数的整数倍,不够补空字节;也就是取所用过的所有对齐参数中最大的那个值的整数倍,因为对齐参数都是2的n次方;这样在处理数组时可以保证每一项都边界对齐;

更改c编译器的缺省字节对齐方式:
在缺省情况下,c编译器为每一个变量或数据单元按其自然对界条件分配空间;一般地可以通过下面的两种方法来改变缺省的对界条件:
方法一:
使用#pragma pack(n),指定c编译器按照n个字节对齐;
使用#pragma pack(),取消自定义字节对齐方式。

方法二:
__attribute(aligned(n)),让所作用的数据成员对齐在n字节的自然边界上;如果结构中有成员的长度大于n,则按照最大成员的长度来对齐;
__attribute((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。

综上所述,下面给出例子并详细分析:

例子一:
#pragma pack(4)
class TestB
{
public:
int aa; //第一个成员,放在[0,3]偏移的位置,
  char a; //第二个成员,自身长为1,#pragma pack(4),取小值,也就是1,所以这个成员按一字节对齐,放在偏移[4]的位置。
  short b; //第三个成员,自身长2,#pragma pack(4),取2,按2字节对齐,所以放在偏移[6,7]的位置。
  char c; //第四个,自身长为1,放在[8]的位置。
};
可见,此类实际占用的内存空间是9个字节。根据规则5,结构整体的对齐是min( sizeof( int ), pack_value ) = 4,所以sizeof( TestB ) = 12;

例子二:
#pragma pack(2)
class TestB
{
public:
int aa; //第一个成员,放在[0,3]偏移的位置,
  char a; //第二个成员,自身长为1,#pragma pack(4),取小值,也就是1,所以这个成员按一字节对齐,放在偏移[4]的位置。
  short b; //第三个成员,自身长2,#pragma pack(4),取2,按2字节对齐,所以放在偏移[6,7]的位置。
  char c; //第四个,自身长为1,放在[8]的位置。
};
可见结果与例子一相同,各个成员的位置没有改变,但是此时结构整体的对齐是min( sizeof( int ), pack_value ) = 2,所以sizeof( TestB ) = 10;

例子三:
#pragma pack(4)
class TestC
{
public:
char a; //第一个成员,放在[0]偏移的位置,
  short b; //第二个成员,自身长2,#pragma pack(4),取2,按2字节对齐,所以放在偏移[2,3]的位置。
  char c; //第三个,自身长为1,放在[4]的位置。
};
整个类的实际内存消耗是5个字节,整体按照min( sizeof( short ), 4 ) = 2对齐,所以结果是sizeof( TestC ) = 6;

例子四:
struct Test
{
char x1; //第一个成员,放在[0]位置,
short x2; //第二个成员,自身长度为2,按2字节对齐,所以放在偏移[2,3]的位置,
float x3; //第三个成员,自身长度为4,按4字节对齐,所以放在偏移[4,7]的位置,
char x4; //第四个陈冠,自身长度为1,按1字节对齐,所以放在偏移[8]的位置,
};
所以整个结构体的实际内存消耗是9个字节,但考虑到结构整体的对齐是4个字节,所以整个结构占用的空间是12个字节。

例子五:
#pragma pack(8)

struct s1
{
short a; //第一个,放在[0,1]位置,
long b; //第二个,自身长度为4,按min(4, 8) = 4对齐,所以放在[4,7]位置
};
所以结构体的实际内存消耗是8个字节,结构体的对齐是min( sizeof( long ), pack_value ) = 4字节,所以整个结构占用的空间是8个字节。

struct s2
{
char c; //第一个,放在[0]位置,
s1 d; //第二个,根据规则四,对齐是min( 4, pack_value ) = 4字节,所以放在[4,11]位置,
long long e; //第三个,自身长度为8字节,所以按8字节对齐,所以放在[16,23]位置,
};
所以实际内存消耗是24自己,整体对齐方式是8字节,所以整个结构占用的空间是24字节。

#pragma pack()
所以:
sizeof(s2) = 24, s2的c后面是空了3个字节接着是d。

以上来自:http://hi.baidu.com/zhanghuisky365/item/047273e832aa5d0d64db0010

 

转载于:https://www.cnblogs.com/kunhu/p/3629902.html

C++之C/C++内存对齐相关推荐

  1. Eigen向量化内存对齐/Eigen的SSE兼容,内存分配/EIGEN_MAKE_ALIGNED_OPERATOR_NEW

    1.总结 对于基本数据类型和自定义类型,我们需要用预编译指令来保证栈内存的对齐,用重写operator new的方式保证堆内存对齐.对于嵌套的自定义类型,申请栈内存时会自动保证其内部数据类型的对齐,而 ...

  2. nginx源码分析--内存对齐处理

    1.nginx内存对齐主要是做2件事情: 1) 内存池的内存地址对齐: 2) 长度按照2的幂取整.因为前面结构体已经是对齐了,如果后面的内存池每一小块不是2的幂,那么后面的就不能对齐 2.通用内存对齐 ...

  3. Linux下的内存对齐函数

    在Linux下内存对齐的函数包括posix_memalign, aligned_alloc, memalign, valloc, pvalloc,其各个函数的声明如下: int posix_memal ...

  4. C++中的内存对齐介绍

    网上有很多介绍字节对齐或数据对齐或内存对齐的文章,虽然名字不一样,但是介绍的内容大致都是相同的.这里以内存对齐相称.注:以下内容主要来自网络. 内存对齐,通常也称为数据对齐,是计算机对数据类型合法地址 ...

  5. 4 OC 中的内存分配以及内存对齐

    目录 一  OC  中的内存分配 一  OC  中的内存分配 student 结构体明明是20?为什么是24个字节,因为结构体会按照本身成员变量最大的内存进行对齐,最大成员变量是8个字节,因此就是8的 ...

  6. 内存对齐与ANSI C中struct型数据的内存布局 【转】

    转自:http://blog.chinaunix.net/uid-25909619-id-3032209.html 当在C中定义了一个结构类型时,它的大小是否等于各字段(field)大小之和?编译器将 ...

  7. 内存对齐/字节对齐/数据对齐/地址总线对齐

    其实是使用InitializeAcl()时发现有个aligned,可我不知道什么是aligned 于是乎我就先搜索了对齐,结果发现了一大堆,什么[字节对齐].[内存对齐].[地址总线 对齐].[数据对 ...

  8. 内存对齐的规则以及作用

    内存对齐能够用一句话来概括: "数据项仅仅能存储在地址是数据项大小的整数倍的内存位置上" 比如int类型占用4个字节.地址仅仅能在0,4.8等位置上. 由一个程序引入话题: //环 ...

  9. c语言20字节的内存的数据怎么读取_C++编程-内存对齐

    内存对齐可以大大提升内存访问速度,是一种用空间换时间的方法. 1.内存对齐的计算机原理 内存地址对齐,是一种在计算机内存中排列数据(表现为变量的地址).访问数据(表现为CPU读取数据)的一种方式,包含 ...

  10. C 语言编程 — 内存对齐

    目录 文章目录 目录 内存对齐 为什么要内存对齐? 内存对齐跟平台有关 对齐系数 使用 pragma 宏指令修改对齐系数 内存对齐的原则 内存对齐 计算机的内存空间都是按照字节划分的,元素(包括:变量 ...

最新文章

  1. 运行代码后总是会出现很多的的debug [main请问如何解决_Flutter的Hot Reload是如何做到的...
  2. 全球及中国彩超市场销售渠道与投资竞争力研究报告2022版
  3. SAP OData:MPC class get last modified - how to implement
  4. arduino代码_纯纯小白开发arduino--我的调试经验
  5. tcp 测试软件,ztcp
  6. gcc、arm-linux-gcc和arm-elf-gcc的关系?
  7. 让WebStorm支持dojo的智能提示
  8. 基于51单片机的数码管显示方案
  9. java 调用tomcat api,tomcat处理http请求-下
  10. BZOJ5218[Lydsy2017省队十连测] 友好城市
  11. Java笔记——关于设置自定义字体、全局字体方面和getFont()的使用
  12. 车载网络信号的编码方式
  13. U盘在电脑上读不出来是什么原因 U盘在电脑上读不出来怎么修复
  14. create和qypt qt_pyqt 与 qt c++ 开发windows客户端程序区别
  15. 风机性能测试的软件设计,风机性能测试系统
  16. 汇编语言-怎样查看标志位、怎样生成列表文件、列表文件格式
  17. 【思维题 集合hash 树上差分】11.5撸树
  18. zoj3716Ribbon Gymnastics
  19. IIS服务器(Windows)远程修改ftp密码方法
  20. axios拦截器封装与使用

热门文章

  1. 推荐系统遇上深度学习(六)--PNN模型理论和实践
  2. [转]如何处理机器学习中的不平衡类别
  3. 通过导入txt数据画出python turtle图形_【Python】txt文件读取绘画
  4. jquery 动态加载html,jQuery – 动态创建iframe并加载页面
  5. 2016下半年网络规划设计师考试上午真题
  6. Python学习入门7:python学习从基础到高手,再到就业
  7. 09 进程池的异步方法
  8. B-treeB+tree数据库索引原理
  9. MySQL数据库之MyISAM与InnoDB的区别
  10. flask 上传头像