Malloc 如何理解对齐

how does malloc understand alignment?

本文关键字:对齐 何理解 Malloc      更新时间:2023-10-16

>以下摘自此处

pw = (widget *)malloc(sizeof(widget));

分配原始存储。实际上,malloc 调用分配了存储 足够大且适当对齐以容纳类型的对象 控件

还看到Herb sutter的Fast pImpl,他说:

对齐。任何内存对齐。分配的任何内存 动态通过新的或 malloc 保证正确对齐 任何类型的对象,但未动态分配的缓冲区 没有这样的保证

我对此很好奇,malloc 如何知道自定义类型的对齐方式?

对齐

要求是递归的:任何struct的对齐方式只是其任何成员的最大对齐方式,这是递归理解的。

例如,假设每个基本类型的对齐方式等于其大小(通常并不总是如此(,struct X { int; char; double; }具有 double 的对齐方式,并且它将被填充为双精度大小的倍数(例如 4 (int(、1 (char(、3 (填充(、8 (double((。struct Y { int; X; float; }的对齐方式为X,最大且等于double的对齐方式,Y的布局相应为:4(整数(,4(填充(,16(X(,4(浮点(,4(填充(。

(所有数字都只是示例,在您的计算机上可能会有所不同。

因此,通过将其分解为基本类型,我们只需要知道少数基本对齐,其中有一个众所周知的最大对齐方式。C++甚至定义了一个类型max_align_t其对齐方式最大的对齐方式。

malloc()需要做的就是选择一个是该值的倍数的地址。

我认为Herb Sutter引用中最相关的部分是我用粗体标记的部分:

对准。任何内存对齐。通过 new 或 malloc 动态分配的任何内存都保证与任何类型的对象正确对齐,但未动态分配的缓冲区没有这样的保证

它不必知道你想到什么类型,因为它与任何类型对齐。在任何给定的系统上,都有一个必要或有意义的最大对齐大小;例如,具有四字节字的系统可能最多具有四字节对齐方式。

malloc(3)手册页也清楚地表明了这一点,其中部分内容是:

malloc()calloc() 函数返回一个指向已分配内存的指针,该指针与任何类型的变量适当对齐。

malloc()可以使用的唯一信息是传递给它的请求的大小。通常,它可能会执行诸如将传递的大小四舍五入到最接近的更大(或等于(的 2 的幂之类的操作,并根据该值对齐内存。对齐值也可能有一个上限,例如 8 个字节。

以上是一个假设的讨论,实际实现取决于您使用的机器体系结构和运行时库。也许您的malloc()总是返回在 8 个字节上对齐的块,并且它永远不必做任何不同的事情。

1( 对齐到所有对齐方式中最小公的倍数。 例如,如果整数需要 4 字节对齐,但指针需要 8,则将所有内容分配给 8 字节对齐。这会导致所有内容对齐。

2( 使用 size 参数确定正确的对齐方式。对于较小的大小,您可以推断类型,例如malloc(1)(假设其他类型大小不是 1(始终是字符。 C++ new具有类型安全的优点,因此始终可以通过这种方式做出对齐决策。

在 C++11 之前,通过使用最大对齐方式来处理相当简单的对齐,其中确切值未知,malloc/calloc 仍然以这种方式工作。这意味着 malloc 分配可以正确对齐任何类型。

根据标准,错误的对齐可能会导致未定义的行为,但我看到 x86 编译器很慷慨,只会惩罚较低的性能。

请注意,您还可以通过编译器选项或指令调整对齐方式。(例如VisualStudio的编译指示包(。

但是当涉及到放置新位置时,C++11 为我们带来了新的关键字,称为 alignofalignas。下面是一些代码,显示了编译器最大对齐方式大于 1 时的效果。下面第一个新展示位置自动良好,但第二个位置不是。

#include <iostream>
#include <malloc.h>
using namespace std;
int main()
{
        struct A { char c; };
        struct B { int i; char c; };
        unsigned char * buffer = (unsigned char *)malloc(1000000);
        long mp = (long)buffer;
        // First placment new
        long alignofA = alignof(A) - 1;
        cout << "alignment of A: " << std::hex << (alignofA + 1) << endl;
        cout << "placement address before alignment: " << std::hex << mp << endl;
        if (mp&alignofA)
        {
            mp |= alignofA;
            ++mp;
        }
        cout << "placement address after alignment : " << std::hex <<mp << endl;
        A * a = new((unsigned char *)mp)A;
        mp += sizeof(A);
        // Second placment new
        long alignofB = alignof(B) - 1;
        cout << "alignment of B: " <<  std::hex << (alignofB + 1) << endl;
        cout << "placement address before alignment: " << std::hex << mp << endl;
        if (mp&alignofB)
        {
            mp |= alignofB;
            ++mp;
        }
        cout << "placement address after alignment : " << std::hex << mp << endl;
        B * b = new((unsigned char *)mp)B;
        mp += sizeof(B);
}

我想这段代码的性能可以通过一些按位运算来提高。

编辑:用按位运算取代了昂贵的模计算。仍然希望有人能更快地找到一些东西。

malloc 不知道它分配了什么,因为它的参数只是总大小。它只是对齐对任何对象都安全的对齐方式。

你可能会找到这个小 C 程序的 malloc(( 实现的分配位:

#include <stdlib.h>
#include <stdio.h>
int main()
{
    size_t
        find = 0,
        size;
    for( unsigned i = 1000000; i--; )
        if( size = rand() & 127 )
            find |= (size_t)malloc( size );
    char bits = 0;
    for( ; !(find & 1); find >>= 1, ++bits );
    printf( "%d", (int)bits );
}