malloc()/ free()的对齐限制

时间:2020-03-05 18:45:33  来源:igfitidea点击:

我读过的较早的K&R(第二版)和其他C语言文本讨论了以malloc()和free()形式实现动态内存分配器的实现,通常还会提及一些有关数据的信息。类型对齐限制。显然,某些计算机硬件体系结构(CPU,寄存器和内存访问)限制了我们存储和寻址某些值类型的方式。例如,可能要求必须从4的倍数的地址开始存储4字节(" long")整数。

主要平台(Intel和AMD,SPARC,Alpha)对内存分配和内存访问施加什么限制(如果有),或者我可以安全地忽略在特定地址边界上对齐内存分配吗?

解决方案

回答

对齐在今天仍然非常重要。如果我们尝试访问奇数边界上的字值,某些处理器(第68k系列会想到)会抛出异常。如今,大多数处理器将运行两个内存周期来获取未对齐的字,但这肯定比对齐的获取要慢。其他一些处理器甚至不会引发异常,但是会从内存中获取不正确的值!

如果除了性能之外,没有其他原因,明智的做法是尝试遵循处理器的对齐方式首选项。通常,编译器会处理所有细节,但是如果我们在自己布置内存结构的地方做任何事情,那么值得考虑。

回答

在C(++)中布置类或者结构时,我们仍然需要注意对齐问题。在这些情况下,编译器将为我们做正确的事,但struct / class的整体大小可能比必要的浪费得多

例如:

struct
{ 
    char A;
    int B;
    char C;
    int D;
};

大小为4 * 4 = 16字节(假设Windows在x86上)

struct
{ 
    char A;
    char C;
    int B;
    int D;
};

大小为4 * 3 = 12个字节。

这是因为编译器对整数强制执行4字节对齐,而对char强制执行1字节对齐。

通常,相同大小(类型)的pack成员变量在一起,以最大程度地减少浪费的空间。

回答

正如Greg所提到的那样,它在今天仍然很重要(也许在某些方面更重要),并且编译器通常会根据体系结构的目标来进行对齐。在托管环境中,JIT编译器可以基于运行时体系结构优化对齐方式。

我们可能会看到pragma指令(在C / C ++中)会更改对齐方式。仅在需要非常特定的对齐方式时才应使用此选项。

// For example, this changes the pack to 2 byte alignment.
#pragma pack(2)

回答

Sparc,MIPS,Alpha和大多数其他"经典RISC"体系结构仅允许对内存进行对齐访问,即使在今天也是如此。不对齐的访问将导致异常,但是某些操作系统将通过使用较小的装载和存储从软件中的所需地址进行复制来处理该异常。应用程序代码不会知道有问题,只是性能会很差。

MIPS具有特殊指令(lwl和lwr),可用于从未对齐的地址访问32位量。每当编译器告诉地址可能未对齐时,它将使用这两个指令序列而不是常规的lw指令。

x86可以毫无例外地处理硬件中未对齐的内存访问,但是与对齐访问相比,x86的性能仍然高达3倍。

Ulrich Drepper就此主题以及其他与内存相关的主题写了一篇全面的论文,《每个程序员应该了解的内存》。这是一篇很长的文章,但充满耐嚼性。

回答

请注意,即使在IA-32和AMD64上,某些SSE指令/本征也需要对齐的数据。如果数据未对齐,这些说明将引发异常,因此至少我们不必调试"错误的数据"错误。也有等效的未对齐指令,但是像Denton所说的那样,它们的速度较慢。

如果我们使用的是VC ++,那么除了#pragma pack指令之外,我们还可以使用__declspec(align)指令进行精确对齐。 VC ++文档还提到了针对特定对齐要求的__aligned_malloc函数。

根据经验,除非我们跨编译器/语言移动数据或者使用SSE指令,否则我们可能会忽略对齐问题。