内存对齐相关问题的简要总结

查询内网中关于内存对齐的资料发现,它们往往只谈论一个层面的问题,而不涉及或稍微涉及更高或更低层面的问题;而这对于喜欢抠根问底的同学来说,是比较难受的。这里对内存对齐相关的问题和答案做一个简要总结,较为复杂的解释这里不涉及,但我会给出相关文章链接。


一、问题简述

内存对齐问题总的来说,分为How 、Who和 Why,至于What这里不再赘述:

  • Who:谁让数据在内存中对齐存放的?
  • How:内存是如何对齐的,即内存对齐的表现形式?
  • Why:为什么要内存对齐?该问题又可分成两个层面的问题:
    • 为什么内存中的数据要以内存对齐的方式排布?
    • 为什么处理器要以内存对齐的方式读取内存?

二、问题详述

1、谁让数据在内存中对齐存放的?

答案是:编译器或某类支持该操作的语言的程序员。在C/C++中,是可以精确控制数据在内存中的分布的,目的是使CPU能够更加高效的从内存中存取数据,但其实这往往不需要开发者自己来完成,因为默认的分布已经是被编译器优化过的,实际上执行了一个填充操作,具体解释见如下链接或者后文。
VS编译器举例:Alignment


2、内存如何对齐?

内存中存储的无非是指令和数据,那么,分析数据结构如何使用内存,可以有效帮助我们认识内存对齐的具体表现形式。网上有一大堆分析C/C++ struct存储结构的文章,主要涉及了这四个关键概念和一个隐含操作:

  • alignment :

A memory address a, is said to be n-byte aligned when a is a multiple of n bytes (where n is a power of 2). ——Data structure alignment

即一个地址是n字节的倍数,可称为n-字节对齐,而n = 2k(k=0,1....m).
所以一个地址a,如果a%(2k)=0,那么a就是(2k)-字节对齐。

  • natural alignment :可翻译为自然对齐。如果数据的地址与其大小对齐,则称为自然对齐,否则称为未对齐。根据上文“对齐”的概念,对某变量value,如value.addr % value.size = 0,那么就可以说该变量自然对齐。

    • 基本数据类型,自然对齐值为该类型的size,如char的自然对齐值为1,int自然对齐值为4......不难理解的是,将这些变量起始位置放置于对齐边界上后(即value.addr % value.size=0处),编译器不用再对它进行任何额外的优化,猜测这应该是“自然对齐”名称的来源。
    • 对于结构体,取值为结构体内成员的natural alignment,如果结构体里不断嵌套包含结构体,那么递归的计算natural alignment,直到递归到基本数据类型,在反过来得到最外层结构体的自然对齐值。
  • specified alignment :由编译器或用户指定的对齐值(如 #progma pack (x)),只对结构体有作用。

  • effective alignment :natural alignmentspecified alignment 两者中的最小值。

  • 隐含操作:具体来说,就要执行padding(填充)操作,所谓填充,就是在结构体成员中间或最后一个成员之后填充数据占位,填充的是什么可忽略。其目的是为了满足自然对齐的要求——不仅要满足结构体成员的自然对齐要求(中间填充),还要满足结构体本身自然对齐的要求(尾部填充)。
    简而言之,需不需要填充字节取决于 address % specified_alignment 是否为0
    举例:

#pragma pack (8)

struct S1{
    char a;
    int b;
};

struct S2{
    char c;
    struct S1 d;
    long long e;
    char f;
};

int main()
{
    struct S1 a;
    struct S2 b;

    printf("size of int, long long: %lu, %lu\n", sizeof(long),sizeof(long long));

    printf("size of S1: %lu\n", sizeof(a));
    printf("size of S2: %lu\n", sizeof(b));

    return 0;
}

输出:
size of int, long long: 4, 8
size of S1: 8
size of S2: 32

分析:

**首先分析struct S1**:
自然对齐值为4,指定对齐值为8,得到结构体有效对齐值也为4.
char a——> 0x0000 % 1 = 0,自然对齐,占一个字节
int b——> 如取值0x0001,0x0002...有0x0001 % 4 != 0,0x0002 % 4 != 0......
直至取址0x0004。
因此0x0001~0x0003将被填充(这是中间填充)。
int b 占4个字节,因此最后一个字节地址为0x0007.
结构体成员存储完毕,但我们要保证整个结构体存储完毕后,
其下一个字节地址对于该结构体是按照有效对齐值对齐的,
因为内存中有可能是连续存储着一个结构体数组。
而它的下一个字节地址为0x0008,结构体有效对齐值为4,有0x0008 % 4 = 0,
满足对齐要求,因此不必进行尾部填充。结构体大小为8字节

adr offset   element  
------   -------  
0x0000   char a;         
0x0001   char pad0[3];  //填充3字节数据
0x0004   int b;  //int b(0x0004-0x0007)
...
0x0007   int b;
------------------------------分割线-----------------------------------------------
**分析struct S2**
自然对齐值为8,指定对齐值为8,得有效对齐值为8

0x0000   char c; //1字节         
0x0001   char _pad0[7];  填充7字节数据(中间填充);
0x0008     S1 d;  //占8字节 
0x0010   long long e;  //占8字节
0x0018   char f;//1字节
0x0019   char _pad[7] //尾部填充7个字节

最后一个成员char f 的地址为0x0018,下一个地址为0x0019,
0x0019 % 8 != 0,因此需要尾部填充,填充7个字节,
因此该结构体在内存中最后的位置为0x001F,因此该结构体大小为 1+7(填充)+8+8+1+7(填充)=32字节。


3、为什么要内存对齐?

前文中讲过,Why的问题要分两个层面来问,首先是为什么编译器按照内存对齐的方式存储数据?其次是,处理器为什么按照内存对齐的方式读写内存中的数据?
实际上,之所以有第一个问题,是因为第二个问题的存在,也就是说,之所以按照内存对齐方式存储数据,是因为处理器是这么做的,而且只有这么做效率才会高。

数据的内存对齐存储

对于用内存对齐的方式存储数据,其详细解释见:
Data alignment: Straighten up and fly right
翻译后的版本:link

这篇文章总结的很好,不再多复述。只分析总结其中讲述的一个细节:


Double-byte memory access granularity
Quad-byte memory access granularity

上图中,分别是双字节存取粒度和四字节存取粒度的处理器。而假设数据是非内存对齐方式存储的,位于[1,2,3,4]字节处。

双字节存取粒度
当从内存中一次读取4个字节时,如果是从地址0处开始读,总共需要读2次,即第一次读[0,1],第二次读[2,3]。如果从地址1处开始读,则需要读3次,依次是[0,1],[2,3],[4,5],也就是说处理器一定是按照内存对齐的方式读取内存的,哪怕是想从地址1处开始取数据。

四字节存取粒度
从地址0开始读,只需读一次[0,1,2,3];从地址1开始读,需要读两次[0,1,2,3] 和 [4,5,6,7]。

那么,是怎么取得最终的数据的呢?

How processors handle unaligned memory access

上图很形象的描述了是如何取得最终的数据的。这里假设是MSB(大端字节序)。因为数据被存储在单元[1,2,3,4],因此按照上文所述,四字节处理器分别读取了[0,1,2,3]和[4,5,6,7],当就是把第一个值[0,1,2,3]读入到结果寄存器后,向左移动一个字节(去掉了0字节处对应的二进制数据),然后把第二个值[4,5,6,7]读入到临时寄存器,向右移动3个字节(去掉了5,6,7字节处对应的二进制数据),最后两者OR,最终结果存储于结果寄存器。

内存存取粒度:因为每次内存存取都会产生一个固定的开销,最小化内存存取次数将提升程序的性能。所以往往不是初学者认为的单字节,跟具体处理器有关,但不会出现3字节、5字节等奇数存取粒度的出现。

总的来说,内存对齐方式存储数据的目的有两点:

  • 提高存取效率
  • 因为有的处理器不支持非内存对齐方式存取,将影响可移植性。

处理器的内存对齐存取

该问题涉及处理器的架构设计、缓存的利用等知识,具体内容待之后添加。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,904评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,581评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,527评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,463评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,546评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,572评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,582评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,330评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,776评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,087评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,257评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,923评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,571评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,192评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,436评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,145评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352