web-dev-qa-db-ja.com

整列されたmalloc実装の説明

これは宿題ではなく、純粋に私自身の個人教育のためのものです。

私はオンラインで見て、整列されたmallocを実装する方法を理解できませんでした this website が見つかりました。読みやすいように、以下のコードを投稿します。

_#include <stdlib.h>
#include <stdio.h>

void* aligned_malloc(size_t required_bytes, size_t alignment)
{
    void* p1; // original block
    void** p2; // aligned block
    int offset = alignment - 1 + sizeof(void*);
    if ((p1 = (void*)malloc(required_bytes + offset)) == NULL)
    {
       return NULL;
    }
    p2 = (void**)(((size_t)(p1) + offset) & ~(alignment - 1));
    p2[-1] = p1;
    return p2;
}

void aligned_free(void *p)
{
    free(((void**)p)[-1]);
}

void main (int argc, char *argv[])
{
    char **endptr;
    int *p = aligned_malloc (100, strtol(argv[1], endptr, 10));

    printf ("%s: %p\n", argv[1], p);
    aligned_free (p);
}
_

実装は機能しますが、どのように機能するかを正直に理解できません。

これは私が理解できないことです:

  1. なぜオフセットが必要なのですか?
  2. And_ing with ~(alignment - 1)の機能
  3. _p2_は二重ポインターです。単一のポインターのみを返すことになっている関数からそれを返すことができるのはなぜですか?
  4. この問題を解決するための一般的なアプローチは何ですか?

どんな助けでも本当に感謝しています。

[〜#〜]編集[〜#〜]

これは 標準ライブラリを使用してのみ整列メモリを割り当てる方法? の複製ではありません。整列メモリを解放する方法も知っている必要があるためです。

12
flashburn
  1. システムのmalloc()が行うことを超えて配置をサポートする場合は、オフセットが必要です。たとえば、システムがmalloc()を8バイト境界に揃え、16バイトに揃えたい場合は、15バイト余分に要求して、結果をシフトして要求どおりに揃えることができることを確認します。また、sizeof(void*)に渡すサイズにmalloc()を追加して、簿記のための余地を残します。

  2. ~(alignment - 1)は、整列を保証するものです。たとえば、アライメントが16の場合、1を減算して15、別名0xFを取得し、それを否定すると0xFF..FF0が作成されます。これは、malloc()から返されるポインターのアライメントを満たすために必要なマスクです。このトリックは、アラインメントが2の累乗であると想定していることに注意してください(実際には通常そうですが、実際にはチェックが必要です)。

  3. _void**_です。関数は_void*_を返します。 voidへのポインタは「任意の型へのポインタ」であり、この場合、その型は_void*_であるため、これは問題ありません。言い換えると、_void*_を他のポインタ型に変換したり、他のポインタ型に変換したりすることは可能ですが、ダブルポインタは依然としてポインタです。

  4. ここでの全体的なスキームは、呼び出し元に返されるポインターの前に元のポインターを格納することです。標準のmalloc()の一部の実装は、同じことを行います。返されたブロックの前にブックキーピング情報を隠します。これにより、free()が呼び出されたときに再利用する領域を簡単に知ることができます。

そうは言っても、標準のmalloc()はシステム上で最大のアライメントを返すため、この種のことは通常は役に立ちません。それ以上の調整が必要な場合は、コンパイラ固有の属性など、他の解決策が考えられます。

10
John Zwinck

実装は機能します

多分、でも私はあまり確信が持てないでしょう。 IMO第一原則から作業するほうがよいでしょう。すぐに、

p1 = (void*)malloc

赤い旗です。 mallocvoidを返します。 Cでは、void *から任意のポインターを割り当てることができます。 mallocからのキャストは、効果が悪い場合があるため、通常は不適切な形式と見なされます。

オフセットが必要な理由

オフセットは、mallocによって返されるポインタを隠しておく場所を提供します。これは、後でfreeによって使用されます。

p1mallocから取得されます。後で、リリースされるfreeに提供される必要があります。 aligned_mallocは、p1sizeof(void*)バイトを予約し、p1をそこにスタッシュして、p2p1が指すブロック内の最初の「境界整列」アドレス)を返します。その後、呼び出し元がp2aligned_freeに渡すと、実際にはp2void *p2[]に変換し、-1をインデックスとして使用して元のp1をフェッチします。

〜(alignment-1)を使用したandingの結果

それがp2を境界に置くものです。配置は16だとしましょう。 alignment -1は15、0xFです。 ~OxFは、最後の4を除くすべてのビットがオンです。任意のポインターPの場合、P & ~0xFは16の倍数になります。

p2は二重ポインターです。

ポインタschmointermallocvoid*を返します。それはメモリのブロックです。必要に応じて対処します。あなたはまばたきしないでしょう

char **args = calloc(7, sizeof(char*));

7つのchar *ポインタの配列を割り当てるには、コードは、p1から少なくともsizeof(void*)バイトの「整列された」場所を選択し、freeの目的で、void **として扱います。

一般的なアプローチは何ですか

答えは1つではありません。おそらく、標準(または一般的な)ライブラリを使用するのが最善です。 mallocの上にビルドする場合、「実際の」ポインターを保持するのに十分な割り当てを行い、アラインされたポインターを返すのはかなり標準的ですが、コードは異なります。 syscall mmapは、ページ境界整列ポインターを返します。これは、「境界整列」のほとんどの基準を満たします。必要に応じて、それはmallocを便乗させるよりも良い場合も悪い場合もあります。

2
James K. Lowden

このコードにはいくつか問題があります。私はそれらを以下のリストにまとめました:

  1. p1 = (void*)malloc mallocの戻り値をキャストしません。
  2. free(((void**)p)[-1]);無料でキャストすることはできません。
  3. if ((p1 = (void*)malloc(required_bytes + offset)) == NULL) ifステートメントの比較の内部に割り当てを配置しないでください。私は多くの人がこれをしていることを知っていますが、私の頭では、それは単に悪い形であり、コードを読みにくくしています。

ここで彼らがしていることは、割り当てられたブロック内に元のポインタを格納することです。つまり、整列されたポインターのみがユーザーに返されます。 mallocによって返される実際のポインターは、ユーザーには表示されません。ただし、割り当てられたリストからブロックをリンク解除してフリーリストに配置するには、freeがそれを必要とするため、そのポインターを保持する必要があります。すべてのメモリブロックの先頭に、mallocはいくつかのハウスキーピング情報をそこに配置します。そのようなものと、next/prevポインタ、サイズ、割り当てステータスなど... mallocの一部のデバッグバージョンは、ガードワードを使用して、何かがバッファをオーバーフローしたかどうかをチェックします。ルーチンに渡される位置合わせ[〜#〜] [〜#〜]は2の累乗でなければなりません。

プールされたメモリアロケータで使用するために独自のバージョンのmallocを作成したとき、使用した最小ブロックサイズは8バイトでした。したがって、32ビットシステムのヘッダーを含めると、合計は28バイトになります(ヘッダーの場合は20バイト)。 64ビットシステムでは、40バイト(ヘッダー用に32バイト)でした。ほとんどのシステムでは、データが特定のアドレス値(最新のコンピューターシステムでは4バイトまたは8バイト)に揃えられると、パフォーマンスが向上します。これは、マシンが整列すると、1つのバスサイクルでWord全体を取得できるためです。そうでない場合は、Word全体を取得するために2バスサイクルを必要とし、Wordを構築する必要があります。これが、コンパイラが変数を4バイトまたは8バイトのいずれかに整列させる理由です。これは、アドレスバスの最後の2ビットまたは3ビットがゼロであることを意味します。

デフォルトの4または8よりも多くの調整が必要なハードウェアの制約があることを知っています。NvidiaのCUDAシステムは、私が正しく覚えていれば、256バイトに調整する必要があります...それはハードウェア要件です。

これは以前にも尋ねられました。参照: 標準ライブラリのみを使用して整列メモリを割り当てる方法?

お役に立てれば。

0
Daniel Rudy

整列されたメモリのSZバイトが必要であると仮定します。

A is the alignment.
W is the CPU Word size.
P is the memory returned by malloc

(P + Y)を返します(P + Y)mod A =

したがって、後でメモリを解放できるように、元のポインタ[〜#〜] p [〜#〜]を保存する必要があります。この場合、(SZ + W)バイトを割り当てる必要がありますが、メモリを整列させるために、サブクラスZバイトここで(P%A = Z>(Z∈[0、A-1]))==

So the total memory to be allocated is:  SZ + W + MAX(Z) = SZ + W + A - 1

返されるポインタはP + Y = P + W + MAX(Z)-(P + W + MAX(Z))mod A

WE HAVE:X-X mod A = INT(X/A)* A = X&〜(A-1)

SO P + W + MAX(Z)-(P + W + MAX(Z))mod A(P + W + MAX(Z))&〜( A-1)

The memory to be returned is: (P + W + MAX(Z)) & ~(A - 1)