内存对齐原理

看到学长的博客有一个有关内存对齐原理的小程序,楞的看不懂是什么意思。

留下来等待以后的解决。必须要解决。

============================2017/2/22

简单描述问题是这样的(虽然只在结构体看到过,但是,可能还会在其他地方存在过吧?):

结构体类型的占多少位?

typedef struct point_type
{
    int x;
    int y;
}Point;
可以知道的是,Point类型的变量所占字节为两个int的字节。

但,问题是,并不是所有的结构体类型变量所在位数,并不是简单的,将其所有成员的变量所在位数相加。

比如:

typedef struct MS
{
    int x;
    char y;
    short z;
}Ms;
按照相加原理,(假设环境 int 占4位,char占1位,short 占2位,上面的例子同),那么Ms类型的变量应该占7位的。但是,事实上,并不是这样的。根据sizeof测试为8位。

在比如:

typedef struct MS2
{
    char y;
    int x;
    short z;
}Ms2;
Ms2类型的变量应该和Ms类型变量所占位数,应该是一样的。但是,事实上,根据sizeof测试并不是一样的。而Ms2所占位数却是12位。

是不是,有点意外。事实上,结构体(目前是知识层面,只能了解到这里)变量所占内存大小,并不是根据简单的相加原理。而是根据内存对齐原理。那么,我们就来一探究竟吧。。

============================

#include<iostream>
using namespace std;
struct st1
{
    char a;
    int b;
    short c;
};
struct st2
{
    short c;
    char a;
    int b;
};
int main()
{
    cout<<"st1="<<sizeof(st1)<<endl;
    cout<<"st2="<<sizeof(st2)<<endl;
    return 0;
}

输出的结果是:st1=12

                         st2=8

 很是不理解,如果没有找到内存对齐原理的话,应该就就st1=st2=7。但是,不是这样的。

-------------------------------------------------------------------------------------------------------------------------------

2015.4.27终于遇到这个问题了,现在也正是学习这个的时候,来解决下。

内存对齐原理

对于大多数的程序员来说,内存对齐基本上是透明的,这是编译器该干的活,编译器为程序中的每个数据单元安排在合适的位置上,从而导致了相同的变量,不同声明顺序的结构体大小的不同。那么编译器为什么要进行内存对齐呢?程序1中结构体按常理来理解sizeof(st1)和sizeof(st2)结果都应该是7,4(int) + 2(short) + 1(char) = 7 。经过内存对齐后,结构体的空间反而增大了。

首先我们来看一下内存对齐的规则:

  1. 对于结构的各个成员(成员对齐),第一个成员位于偏移为0的位置,以后每个数据成员的偏移量必须是min(#pragma pack()指定的数,这个数据成员的自身长度) 的倍数。
  2. #pragma pack(n) 表示设置为n字节对齐。(VC默认8字节对齐)
  3. 在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐(整体对齐),对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。

以程序1为例解释对齐的规则:

St1 :char占一个字节,起始偏移为0 ,int 占4个字节,min(#pragma pack()指定的数,这个数据成员的自身长度) = 4(VC6默认8字节对齐),所以int按4字节对齐,起始偏移必须为4的倍数,所以起始偏移为4,在char后编译器会添加3个字节的额外字节,不存放任意数据。short占2个字节,按2字节对齐,起始偏移为8,正好是2的倍数,无须添加额外字节。到此规则1的数据成员对齐结束,此时的内存状态为(x表示额外添加的字节):

oxxx|oooo|oo

0123 4567 89 (地址)

共占10个字节。还要继续进行结构本身的对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行,st1结构中最大数据成员长度为int,占4字节,而默认的#pragma pack 指定的值为8,所以结果本身按照4字节对齐,结构总大小必须为4的倍数,需添加2个额外字节使结构的总大小为12 。此时的内存状态为:

oxxx|oooo|ooxx

0123 4567 89ab (地址)

到此内存对齐结束。St1占用了12个字节而非7个字节。

我们来直观的看一下:

#include <stdio.h>
#include <stdlib.h>

typedef struct MS
{
    int x;
    char y;
    short z;
}Ms;

int main()
{
    Ms ms;
    int *p = &ms.x;
    char *p1 = &ms.y;
    short *p2 = &ms.z;
    printf ("%d\n", p);
    printf ("%d\n", p1);
    printf ("%d\n", p2);
    printf ("%d\n", sizeof(ms));
   return 0;
}

输出:


可以看到ms的x的地址是一个绝对地址,偏移量为0吧。

然后y(char)的偏移量为4。z(short)的偏移量为6。这里是符合内存对齐原理的。

我再指定了一下:#pragma pack(1), 看看其效果。

#include <stdio.h>
#include <stdlib.h>
#pragma pack(1)
typedef struct MS
{
    int x;
    char y;
    short z;
}Ms;

int main()
{
    Ms ms;
    int *p = &ms.x;
    char *p1 = &ms.y;
    short *p2 = &ms.z;
    printf ("%d\n", p);
    printf ("%d\n", p1);

    printf ("%d\n", p2);
    printf ("%d\n", sizeof(ms));
    return 0;
}

输出:

可以发现,正好是其成员内存相加。

三、内存对齐的作用
  1. 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
  2. 性能原因:经过内存对齐后,CPU的内存访问速度大大提升。具体原因稍后解释。

图一:

howProgrammersSeeMemory

这是普通程序员心目中的内存印象,由一个个的字节组成,而CPU并不是这么看待的。

图二:

howProcessorsSeeMemory

CPU把内存当成是一块一块的,块的大小可以是2,4,8,16字节大小,因此CPU在读取内存时是一块一块进行读取的。块大小成为memory access granularity(粒度) 本人把它翻译为“内存读取粒度” 。

假设CPU要读取一个int型4字节大小的数据到寄存器中,分两种情况讨论:

  1. 数据从0字节开始
  2. 数据从1字节开始

再次假设内存读取粒度为4。

图三:

quadByteAccess

  1. 当该数据是从0字节开始时,很CPU只需读取内存一次即可把这4字节的数据完全读取到寄存器中。
  2. 当该数据是从1字节开始时,问题变的有些复杂,此时该int型数据不是位于内存读取边界上,这就是一类内存未对齐的数据。

图四:

unalignedAccess

此时CPU先访问一次内存,读取0—3字节的数据进寄存器,并再次读取4—5字节的数据进寄存器,接着把0字节和6,7,8字节的数据剔除,最后合并1,2,3,4字节的数据进寄存器。对一个内存未对齐的数据进行了这么多额外的操作,大大降低了CPU性能。

这还属于乐观情况了,上文提到内存对齐的作用之一为平台的移植原因,因为以上操作只有有部分CPU肯干,其他一部分CPU遇到未对齐边界就直接罢工了。


---------------------------------------------

今天遇到了一个关于内存对齐的面试题:

环境呢???

struct _THUNDER{
       int iVersion;
       char cTag;
       char cAdv;
       int iUser;
       char cEnd;
}Thunder;
int sz = sizeof(Thunder);


解释一下:

我们知道结构体的数据成员进行对齐iVersion(0 - 3), cTag(4), cAdv(5), iUser(8 - 11), cEnd(12)。

结构体对齐:4 * 4 >= 13      所以 ans  = 16.

========================================================

还有一种类型的复杂数据结构:Union也是需要了解的。

对于联合体,系统将为之分配最长字节数目的存储空间。

union union_type
{
    int a;
    char b[10];
};

int main(int argc, char *argv[])
{
    printf("%d\n", sizeof(union union_type));
    return 0;
}

我们的期望是输出10。但是,事实上,并不是这样的。因为内存对齐的原因,他需要12个内存空间。

是很值得考虑一下的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值