内存对齐

作者: 罗蓁蓁 | 来源:发表于2020-04-28 19:28 被阅读0次

为什么要进行内存对齐

在计算机组成原理中我们学到:一块内存芯片一般只提供 8 位数据线,要进行 16 位数据的读写可采用奇偶分体来组织管理多个芯片,32 位也类似:

1.jpg

这样,连续的四个字节会分布在不同的芯片上,送入地址 0,我们可将第 0、1、2、3 四个字节一次性读出组成一个 32 位数,送入地址 4(每个芯片接收到的地址是1),可一次性读出 4、5、6、7 四个字节。

但是如果要读 1、2、3、4 四个字节,就麻烦了,有的 CPU 直接歇菜了:我处理不了!但 Intel 的 CPU 走的是复杂指令集路线,岂能就此认输,它通过两次内存读,然后进行拼接合成我们想要的那个 32 位数,而这一切是在比机器码更低级的微指令执行阶段完成的,所以 movl 1, %eax 会不出意外地读出 1、2、3、4 四个字节到 eax,证据如下(mem.c):

#include <stdio.h>
char a[]={0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
int main()
{
    int *p = (int*)(a + 1);
    int ans = *p;
    
    printf("*p:\t%p\n", ans);
    printf("a:\t%p\n", a);
    printf("p:\t%p\n", p);
    return 0;
}

该程序的运行结果如下:

[lqy@localhost temp]$ gcc -o mem mem.c
[lqy@localhost temp]$ ./mem
*p: 0x55443322
a:  0x80496a8
p:  0x80496a9
[lqy@localhost temp]$ 

可看出程序确实从一个未对齐到 4 字节的地址(0x80496a9)后读出了 4 个字节,从汇编可看出确实是 1 条 mov 指令读出来的:

movl    $a, %eax
addl    $1, %eax
movl    %eax, 28(%esp)  # 初始化指针 p
movl    28(%esp), %eax
movl    (%eax), %eax    # 这里读出了 0x55443322
movl    %eax, 24(%esp)  # 初始化 ans

虽然 Intel 的 CPU 能这样处理,但还是要浪费点时间不是,所以 C 程序还是要采取措施避免这种情况的发生,那就是内存对齐。

内存对齐的结果

内存对齐的完整描述你还是去百度吧,这里我只是含糊地介绍一下:

  1. 保证最大类型对齐到它的 size
  2. 尽量不浪费空间

比如:

struct A{
    char a;
    int c;
};

它的大小为 8,c 的内部偏移为 4,
这样就可以一次性读出 c 了。

再如:

struct B{
    char a;
    char b;
    int c;
};

它的大小还是 8,第 2 条起作用了!

关闭内存对齐

讲到内存对齐,估计大家最期待的一大快事就是怎么关闭它(默认是开启的),毕竟 Intel CPU 如此强大,关闭了也没事。

关闭它也甚是简单,添加预处理指令 #pragma pack(1) 就行,windows linux 都管用:

#include <stdio.h>
#pragma pack(1)
struct _A{
    char c;
    int i;
};
//__attribute__((packed));

typedef struct _A A;

int main()
{
    printf("%d\n", sizeof(A));
    return 0;
}

gcc 中更常见的是使用 __attribute__((packed)),这个属性只解除对一个结构体的内存对齐,而 #pragma pack(1) 解除了整个 C源文件 的内存对齐,所以有时候 __attribute__((packed)) 显得更为合理。

什么时候可能需要注意或者关闭内存对齐呢?
我想大概是这两种情况:

  • 结构化文件的读写
  • 网络数据传输

另一个浪费内存的家伙

说到内存对齐,我想起了另一个喜欢浪费内存的家伙:参数对齐(我瞎编的名字,C 标准中或许有明确规定)。
看下面这个程序:

#include <stdio.h>
typedef unsigned char u_char;
u_char add(u_char a, u_char b)
{
    return (u_char)(a+b);
}

int main()
{
    u_char a=1, b=2;
    
    printf("ans:%d\n", add(a, b));
    return 0;
}

你说 add 函数的参数会占几个字节呢?2个?4个?
结果是 8 个……

“可恨”的是,这个家伙浪费内存的行为却被所有编译器纵容,我们无法追究它的责任(应该是为了方便计算参数位置而规定的)。

出差必备

买火车票、高铁票、机票,订酒店都打9折的出行工具TRIP,点击注册

相关文章

  • 2.iOS底层学习之内存对齐

    学习了内存对齐之后的疑问?? 1.为啥要内存对齐?2.内存对齐的规则?3.内存对齐实例分析。 内存对齐的目的 上网...

  • 内存对齐

    本次主要讨论三个问题: 什么是内存对齐 内存对齐的好处 如何对齐 内存对齐 内存对齐是一种提高内存访问速度的策略。...

  • 结构体内存对齐

    对象内存对齐 探讨的问题 1.什么是内存对齐?2.为什么要做内存对齐?3.结构体内存对齐规则4.源码内存对齐算法 ...

  • 内存对齐

    内存对齐 什么叫内存对齐内存对齐就是按照特定的规则对数据进行存储,一般编译器按照8字节对齐标准处理。内存对齐一般用...

  • iOS内存对齐

    这篇文章我们来探索一下iOS内存对齐的原理,在探索完内存对齐原理之后,你就会明白内存对齐的好处。 在讲述内存对齐时...

  • iOS 开发 内存对齐(练习)

    目录 内存对齐规则 对齐系数 面试题演练 一、内存对齐规则 (关于面试题中结构体内存对齐计算总结) 1.1、数据成...

  • 内存对齐

    在C语言柔性数组一文中,提到了内存对齐,于是想写篇文章总结总结内存对齐。 内存对齐 为什么需要内存对齐 计算机系统...

  • iOS底层探究 - 内存对齐

    目录1:内存对齐的原因2:内存对齐的规则3:结构体内存分配演练以及在iOS中对象成员的内存分配探索 一 :内存对齐...

  • 内存对齐

    知识点概要 OC对象内存对齐结构体内存对齐 OC对象内存对齐 计算内存大小的三种方式 1.sizeof:系统提供的...

  • C/C++内存对齐

    在面试或工作中,经常会遇到内存对齐的问题。这里结合我的理解谈一谈对内存对齐的理解。 1. 为什么要内存对齐,不对齐...

网友评论

    本文标题:内存对齐

    本文链接:https://www.haomeiwen.com/subject/ajibwhtx.html