为什么动态分配的内存总是16字节对齐



我写了一个简单的例子:

#include <iostream>
int main() {
void* byte1 = ::operator new(1);
void* byte2 = ::operator new(1);
void* byte3 = malloc(1);
std::cout << "byte1: " << byte1 << std::endl;
std::cout << "byte2: " << byte2 << std::endl;
std::cout << "byte3: " << byte3 << std::endl;
return 0;
}

运行该示例,我得到以下结果:

字节1:0x1f53e70

字节2:0x1f53e90

字节3:0x1f53eb0

每次分配一个字节的内存时,它总是对齐16个字节。为什么会发生这种情况?

我在GCC 5.4.0和GCC 7.4.0上测试了这个代码,得到了相同的结果。

为什么会发生这种情况?

因为标准是这么说的。更具体地说,它说动态分配1至少与最大基本2对齐(它可能有更严格的对齐)。有一个预定义的宏(从C++17开始),只是为了告诉您这种保证对齐的确切含义:__STDCPP_DEFAULT_NEW_ALIGNMENT__。为什么在你的例子中这可能是16。。。这是一种语言实现的选择,受目标硬件架构所允许的限制。

这是一个必要的设计,考虑到没有办法将有关所需对齐的信息传递给分配函数(直到C++17引入了对齐的新语法来分配"过对齐"内存)。

malloc对您打算在内存中创建的对象类型一无所知。有人可能认为new在理论上可以推导出排列,因为它被赋予了一个类型。。。但是,如果您想将该内存重新用于具有更严格对齐的其他对象,例如在std::vector的实现中,该怎么办?一旦您了解了运算符new的API:void* operator new ( std::size_t count ),您就可以看到类型或其对齐方式不是可能影响分配对齐的参数。

1由默认分配器或malloc函数族生成。

2最大基本对准是alignof(std::max_align_t)。没有任何基本类型(算术类型、指针)的对齐比这更严格。

实际上有两个原因。第一个原因是,对某些类型的对象有一些对齐要求。通常,这些对齐要求是软的:未对齐的访问"只是"较慢(可能是数量级)。它们也可能很难:例如,在PPC上,如果向量没有对齐到16个字节,你就无法访问内存中的向量对齐不是可选的,而是在分配内存时必须考虑的。永远

请注意,无法指定与malloc()的对齐方式。因此,必须实现malloc()以提供一个指针,该指针可以在平台上正确对齐。C++中的CCD_ 10遵循相同的原理。

需要多少对齐完全取决于平台。在PPC上,不可能通过小于16字节的对齐来逃脱惩罚。X86在这方面稍微宽容一点,afaik。


第二个原因是分配器函数的内部工作。典型的实现具有至少2个指针的分配器开销:每当您从malloc()请求一个字节时,它通常需要为至少两个额外的指针分配空间来进行自己的记账(具体数量取决于实现)。在64位体系结构上,这是16个字节。因此,malloc()用字节来思考是不明智的,用16字节块来思考更有效。至少您可以在示例代码中看到这一点:得到的指针实际上相距32个字节。每个内存块占用16字节的有效负载+16字节的内部记账内存。

由于分配器从内核请求整个内存页(4096字节,4096字节对齐!),因此生成的内存块在64位平台上自然是16字节对齐的提供不太一致的内存分配是不现实的


因此,综合考虑这两个原因,从分配器函数提供严格对齐的内存块既实用又必要。对齐的确切数量取决于平台,但通常不会小于两个指针的大小。

这可能是内存分配器设法向解除分配函数获取必要信息的方式:解除分配函数(如free或一般的全局operator delete)的问题是只有一个参数,指向所分配的内存的指针,而没有指示所请求的块的大小(或者如果它更大,则指示所分配的大小),因此需要以某种其他形式向解除分配函数提供该指示(以及更多)。

最简单但最有效的方法是为附加信息加上请求的字节分配空间,并返回一个指向信息块末尾的指针,我们称之为IBIB的大小和对齐会自动对齐mallocoperator new返回的地址,即使您分配的数量很小:malloc(s)分配的实际数量是sizeof(IB)+s

对于这种较小的分配,该方法相对浪费,可能会使用其他策略,但具有多个分配方法会使释放复杂化,因为函数必须首先确定使用了哪种方法。

为什么会发生这种情况?

因为在一般情况下,库不知道要在内存中存储什么类型的数据,所以它必须与该平台上最大的数据类型对齐。如果您存储的数据不对齐,您将受到硬件性能的严重损失。在某些平台上,如果试图访问未对齐的数据,甚至会出现segfault。

由于平台原因。在X86上,这是不必要的,但可以提高操作性能。正如我所知,在较新的模型上,这并没有什么不同,但编译器会达到最佳效果。如果未正确对齐,例如m68k处理器上的长未对齐4字节将崩溃。

不是。这取决于操作系统/CPU的要求。在32位版本的linux/win32的情况下,分配的内存总是8字节对齐的。在64位版本的linux/win32的情况下,由于所有64位CPU都至少有SSE2,因此当时将所有内存对齐为16字节是有意义的(因为使用未对齐的内存时,使用SSE2的效率较低)。使用最新的基于AVX的CPU,未对齐内存的性能损失已经消除,因此它们实际上可以在任何边界上分配。

如果你想一想,将内存分配的地址对齐为16字节会在指针地址中留下4比特的空白。这可能在内部用于存储一些附加标志(例如可读、可写、可执行等)。

归根结底,推理完全取决于操作系统和/或硬件需求。这与语言无关。

最新更新