標準ライブラリのみを使用して整列メモリを割り当てる方法は?


422

就職の面接の一環としてテストを終えたばかりで、Googleを参考にしても、1つの質問で困惑しました。StackOverflowの乗組員がそれで何ができるかを見たいのですが。

このmemset_16aligned関数には、16バイト境界で整列されたポインターを渡す必要があります。そうしないと、クラッシュします。

a)どのようにして1024バイトのメモリを割り当て、それを16バイト境界に揃えますか?
b)のmemset_16aligned実行後にメモリを解放します。

{    
   void *mem;
   void *ptr;

   // answer a) here

   memset_16aligned(ptr, 0, 1024);

   // answer b) here    
}

89
hmmm ...長期的なコードの実行可能性については、「memset_16alignedを書き込んだ人は誰でも、修正して置き換えるか、特有の境界条件を持たないように置き換える」
スティーブンA.ロウ

29
確かに尋ねる有効な質問-「なぜ奇妙な記憶整列なのか」。しかし、それには十分な理由がある可能性があります。この場合、memset_16aligned()が128ビット整数を使用できる可能性があり、メモリが整列していることがわかっている場合は、これが簡単です。その他
ジョナサンレフラー、

5
memsetを作成したユーザーは、内部ループをクリアするために内部の16バイトアライメントを使用し、アライメントされていない端をクリーンアップするために小さなデータプロローグ/エピローグを使用できます。これは、コーダーに追加のメモリポインターを処理させるよりもはるかに簡単です。
Adisak、

8
なぜ誰かがデータを16バイト境界に揃えたいのでしょうか?おそらくそれを128ビットSSEレジスタにロードします。(新しい)整列されていないmovs(たとえば、movupd、lddqu)が遅いか、おそらくSSE2 / 3のないプロセッサをターゲットにしていると

11
アドレスの調整により、キャッシュの使用が最適化されるとともに、さまざまなレベルのキャッシュとRAMの間の帯域幅が増加します(最も一般的なワークロードの場合)。こちらをご覧くださいstackoverflow.com/questions/381244/purpose-of-memory-alignment
Deepthought '25 / 11/13

回答:


587

元の答え

{
    void *mem = malloc(1024+16);
    void *ptr = ((char *)mem+16) & ~ 0x0F;
    memset_16aligned(ptr, 0, 1024);
    free(mem);
}

正解

{
    void *mem = malloc(1024+15);
    void *ptr = ((uintptr_t)mem+15) & ~ (uintptr_t)0x0F;
    memset_16aligned(ptr, 0, 1024);
    free(mem);
}

要求通りの説明

最初のステップは、念のため、十分なスペアスペースを割り当てることです。メモリは16バイト境界で整列する必要があるため(先頭のバイトアドレスは16の倍数である必要がある)、16バイトを追加すると、十分なスペースが確保されます。最初の16バイトのどこかに、16バイト境界で整列されたポインタがあります。(注malloc()十分にするように整列されるポインタを返すことになっているいずれかの。目的が、「任意の」の意味は、基本的なタイプのようなもののために主である- 、longdoublelong double。、long longおよびオブジェクトへのポインタとポインタ機能にあなたがいる場合にはグラフィックスシステムで遊ぶなど、より専門的なことを行うと、他のシステムよりも厳しい調整が必要になる場合があります。したがって、このような質問と回答が必要になります。

次のステップは、voidポインターをcharポインターに変換することです。GCCにもかかわらず、voidポインターに対してポインター演算を行うことは想定されていません(GCCには、乱用したことを通知する警告オプションがあります)。次に、開始ポインタに16を追加します。仮定は、malloc()あなたに信じられないほどひどく整列ポインタが返されました:0x800001。16を追加すると0x800011になります。次に、16バイト境界に切り捨てたいので、最後の4ビットを0にリセットします。0x0Fでは、最後の4ビットが1に設定されています。したがって、~0x0F最後の4つを除いてすべてのビットが1に設定されています。それを0x800011でANDすると、0x800010になります。他のオフセットを反復処理して、同じ演算が機能することを確認できます。

最後のステップfree()は簡単です。いつでも、そして唯一、のfree()いずれかの値に戻るかmalloc()calloc()またはrealloc()あなたに返されます。それ以外はすべて災害です。memその値を保持するために正しく提供しました—ありがとうございます。無料でリリースします。

最後に、システムのmallocパッケージの内部について知っている場合は、16バイト境界で整列されたデータ(または8バイト境界で整列されたデータ)を返す可能性があると推測できます。16バイト境界で整列されている場合は、値をいじる必要はありません。ただし、これは危険で移植malloc性がありません。他のパッケージでは最小の配置が異なるため、何か異なることを行うと1つのことを想定すると、コアダンプが発生します。広い範囲で、このソリューションは移植可能です。

posix_memalign()整列されたメモリを取得する別の方法として他の誰かが言及しました。これはどこでも利用できるわけではありませんが、多くの場合、これをベースとして実装できます。整列が2の累乗であると便利であったことに注意してください。他の配置は厄介です。

もう1つのコメント—このコードは、割り当てが成功したかどうかをチェックしません。

修正

Windows Programmerは、ポインターに対してビットマスク操作を実行できないことを指摘し、実際、GCC(3.4.6および4.3.1テスト済み)はそのように不満を述べています。したがって、基本コードの修正バージョンがメインプログラムに変換され、以下のようになります。また、指摘されているように、16ではなく15を追加するという自由も取っています。uintptr_tC99はほとんどのプラットフォームでアクセスできるように十分な長さであるので、私は使用しています。ステートメントで使用するのPRIXPTRでなければ、を使用printf()する#include <stdint.h>代わりにそれで十分#include <inttypes.h>です。[このコードには、CRによって指摘された修正が含まれています。これは、何年も前にBill Kによって最初に作成された点を繰り返しており、私はこれまで何とか見過ごしていました。]

#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static void memset_16aligned(void *space, char byte, size_t nbytes)
{
    assert((nbytes & 0x0F) == 0);
    assert(((uintptr_t)space & 0x0F) == 0);
    memset(space, byte, nbytes);  // Not a custom implementation of memset()
}

int main(void)
{
    void *mem = malloc(1024+15);
    void *ptr = (void *)(((uintptr_t)mem+15) & ~ (uintptr_t)0x0F);
    printf("0x%08" PRIXPTR ", 0x%08" PRIXPTR "\n", (uintptr_t)mem, (uintptr_t)ptr);
    memset_16aligned(ptr, 0, 1024);
    free(mem);
    return(0);
}

そして、これはわずかに一般化されたバージョンで、2の累乗のサイズで機能します。

#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static void memset_16aligned(void *space, char byte, size_t nbytes)
{
    assert((nbytes & 0x0F) == 0);
    assert(((uintptr_t)space & 0x0F) == 0);
    memset(space, byte, nbytes);  // Not a custom implementation of memset()
}

static void test_mask(size_t align)
{
    uintptr_t mask = ~(uintptr_t)(align - 1);
    void *mem = malloc(1024+align-1);
    void *ptr = (void *)(((uintptr_t)mem+align-1) & mask);
    assert((align & (align - 1)) == 0);
    printf("0x%08" PRIXPTR ", 0x%08" PRIXPTR "\n", (uintptr_t)mem, (uintptr_t)ptr);
    memset_16aligned(ptr, 0, 1024);
    free(mem);
}

int main(void)
{
    test_mask(16);
    test_mask(32);
    test_mask(64);
    test_mask(128);
    return(0);
}

test_mask()複数の人が回答で示したように、汎用の割り当て関数に変換するには、アロケータからの単一の戻り値でリリースアドレスをエンコードする必要があります。

面接官の問題

Uriはコメントしました:たぶん私は今朝[a]読解の問題を抱えているかもしれませんが、インタビューの質問が具体的に「どうやって1024バイトのメモリを割り当てるのですか?」それはインタビュアーからの自動的な失敗ではないでしょうか?

私の応答は300文字のコメントに収まりません...

場合によります。ほとんどの人(私を含む)は「1024バイトのデータを格納できるスペースを割り当て、ベースアドレスが16バイトの倍数であるスペースをどのように割り当てるか」という質問をしたと思います。インタビュアーがどうやって1024バイト(のみ)を割り当てて16バイトにアラインさせることができるかを本当に意味しているのであれば、オプションはより制限されます。

  • 明らかに、1つの可能性は1024バイトを割り当て、そのアドレスに「アライメント処理」を与えることです。このアプローチの問題は、実際の使用可能なスペースが適切に決定されないことです(使用可能なスペースは1008〜1024バイトですが、どのサイズを指定できるメカニズムがありません)。
  • 別の可能性としては、完全なメモリアロケータを作成し、返される1024バイトのブロックが適切に配置されていることを確認する必要があります。その場合は、おそらく、提案されたソリューションとほぼ同様の操作を実行することになりますが、アロケーター内で非表示にします。

ただし、インタビュアーがこれらの応答のいずれかを期待している場合は、このソリューションが密接に関連する質問に答えることを認識し、質問を再構成して会話を正しい方向に向けることを期待します。(さらに、面接担当者が本当に気難しい場合は、私はその仕事を望んでいません。不十分に正確な要件への回答が修正なしに炎上で撃たれた場合、面接担当者は安全に働くことができる人ではありません。)

世界は進む

質問のタイトルが最近変更されました。私を困惑させたのは、Cインタビューの質問でのメモリ調整解決することでし。改訂されたタイトル(標準ライブラリを使用してのみアラインメントされたメモリを割り当てる方法?)は、わずかに改訂された回答を要求します—この補遺がそれを提供します。

C11(ISO / IEC 9899:2011)追加機能aligned_alloc()

7.22.3.1 aligned_alloc関数

あらすじ

#include <stdlib.h>
void *aligned_alloc(size_t alignment, size_t size);

説明
このaligned_alloc関数は、位置合わせがで指定されalignment、サイズがで指定されsize、値が不定であるオブジェクトにスペースを割り当てます。の値はalignment、実装によってサポートされている有効な配置であり、の値はsizeの整数倍でなければなりませんalignment

戻り値関数が返すNULLポインタまたは割り当てられた領域へのポインタのいずれかを。
aligned_alloc

そしてPOSIXは以下を定義しますposix_memalign()

#include <stdlib.h>

int posix_memalign(void **memptr, size_t alignment, size_t size);

説明

posix_memalign()関数は、割り当てなければならないsizeで指定された境界に整列バイトalignment、及びで割り当てられたメモリへのポインタを返しますmemptr。の値はalignment、の2の倍数の累乗になりますsizeof(void *)

正常に完了すると、が指す値memptrはの倍数になりalignmentます。

要求されたスペースのサイズが0の場合、動作は実装定義です。返される値はmemptr、nullポインタまたは一意のポインタのいずれかです。

free()機能は以前によって割り当てられたメモリを解放しなければなりませんposix_memalign()

戻り値

正常に完了すると、posix_memalign()ゼロを返します。そうでない場合は、エラーを示すエラー番号が返されます。

これらのいずれかまたは両方を使用して、質問に答えることができますが、質問が最初に回答されたときのオプションはPOSIX関数のみでした。

裏で新しい整列メモリ関数は、質問で概説されているものとほとんど同じ働きをしますが、より簡単に整列を強制し、整列されたメモリの開始を内部的に追跡して、コードが特別に対処する必要があります—使用された割り当て関数によって返されたメモリを解放するだけです。


13
そして、私はC ++に錆びていますが、〜0x0Fがポインターのサイズに適切に拡張されるとは本当に信じていません。そうでない場合は、ポインタの最上位ビットもマスクするので、すべての地獄が解けます。私はそれについて間違っているかもしれません。
ビルK

66
ところで、「+ 15」は「+16」と同様に機能しますが、この状況では実際的な影響はありません。
Menkboy、2008年

15
MenkboyとGregからの「+ 15」コメントは正しいですが、malloc()はほぼ間違いなくそれを16に切り上げます。+16を使用すると、説明が少し簡単になります。一般化されたソリューションは厄介ですが、実行可能です。
ジョナサンレフラー、

6
@Aerovistae:これは少し難しい質問であり、任意の数(実際にはメモリアロケータによって返されるアドレス)を特定の要件(16の倍数)に一致させる方法の理解に大きく依存します。53を最も近い16の倍数に切り上げるように指示された場合、それをどのように行いますか?プロセスは、アドレスについてはそれほど違いはありません。それはあなたが通常扱っている数がより多いというだけです。面接の質問は、答えを知っているかどうかではなく、自分の考えを見つけるために行われます。
ジョナサンレフラー、2012年

3
@akristmann:<inttypes.h>C99から入手できる場合は、元のコードは正しいです(少なくともフォーマット文字列の場合-おそらく、値はキャストで渡す必要があります:)(uintptr_t)mem, (uintptr_t)ptr。フォーマット文字列は文字列連結に依存し、PRIXPTRマクロは、値のprintf()16進数出力の正しい長さとタイプ指定子uintptr_tです。別の方法を使用することです%pが、それからの出力はプラットフォームによって異なり(一部は先頭0xにを追加しますが、ほとんどは追加しません)、通常は小文字の16進数字で書き込まれます。私が書いたものは、プラットフォーム間で統一されています。
ジョナサンレフラー2013年

58

質問の見方に応じて、わずかに異なる3つの答え:

1)Jonathan Lefflerの解は、正確な質問に十分対応できますが、16桁に切り上げるには、16バイトではなく15バイトだけ追加する必要があります。

A:

/* allocate a buffer with room to add 0-15 bytes to ensure 16-alignment */
void *mem = malloc(1024+15);
ASSERT(mem); // some kind of error-handling code
/* round up to multiple of 16: add 15 and then round down by masking */
void *ptr = ((char*)mem+15) & ~ (size_t)0x0F;

B:

free(mem);

2)より一般的なメモリ割り当て関数の場合、呼び出し元は2つのポインタ(1つは使用、もう1つは解放)を追跡する必要はありません。したがって、「実際の」バッファーへのポインターを境界整列バッファーの下に格納します。

A:

void *mem = malloc(1024+15+sizeof(void*));
if (!mem) return mem;
void *ptr = ((char*)mem+sizeof(void*)+15) & ~ (size_t)0x0F;
((void**)ptr)[-1] = mem;
return ptr;

B:

if (ptr) free(((void**)ptr)[-1]);

(1)とは異なり、memに15バイトしか追加されなかったため、実装でmallocからの32バイトのアライメントが保証された場合、このコードは実際にアライメントを減らす可能性があることに注意してください(可能性は低いですが、理論的にはCの実装は32バイトになる可能性があります整列型)。memset_16alignedを呼び出すだけの場合は問題ありませんが、構造体にメモリを使用する場合は問題になる可能性があります。

実装固有のアライメント保証をプログラムで決定する方法がないため、これに対して適切な修正が何であるかはわかりません(返されるバッファーが必ずしも任意の構造体に適しているとは限らないことをユーザーに警告する以外)。起動時に1バイトのバッファを2つ以上割り当てることができると思います。また、表示される最悪のアライメントは保証されたアライメントであると想定しています。間違っていると、メモリを浪費します。より良いアイデアをお持ちの方は、そう言ってください...

[ 追加:「標準」のトリックは、必要な配置を決定するために、「最大限に配置される可能性が高い型」の和集合を作成することです。最大限に整列された型は、(C99では) ' long long'、 ' long double'、 ' void *'、または ' void (*)(void)' になる可能性があります。を含める場合<stdint.h>、おそらくintmax_t代わりに「」を使用できますlong long(Power 6(AIX)マシンでintmax_tは、128ビット整数型になります)。その共用体の配置要件は、単一の文字とそれに続く共用体を持つ構造体に埋め込むことで決定できます。

struct alignment
{
    char     c;
    union
    {
        intmax_t      imax;
        long double   ldbl;
        void         *vptr;
        void        (*fptr)(void);
    }        u;
} align_data;
size_t align = (char *)&align_data.u.imax - &align_data.c;

次に、要求された配置(例では16)とalign上記で計算された値の大きい方を使用します。

(64ビット)Solaris 10では、結果の基本的な配置malloc()は32バイトの倍数であるように見えます。
]

実際には、アラインされたアロケーターは、ハードワイヤードではなく、アラインメントのパラメーターを取ることがよくあります。したがって、ユーザーは、気になる構造体のサイズ(または2以上の最小の2のべき乗)を渡し、すべてが正常になります。

3)プラットフォームが提供するものを使用します:posix_memalignPOSIXの場合_aligned_malloc、Windowsの場合。

4)C11を使用する場合、最もクリーンでポータブルで簡潔なオプションはaligned_alloc、このバージョンの言語仕様で導入された標準ライブラリー関数を使用することです。


1
私は同意します-質問の意図は、メモリブロックを解放するコードが「調理された」16バイト境界のポインタにのみアクセスできることだと思います。
マイケルバー

1
一般的な解決策-あなたは正しいです。ただし、質問のコードテンプレートは両方を明確に示しています。
ジョナサンレフラー

1
確かに、良い面接では何が起きるかというと、あなたが答えを出すということです。面接担当者が私の答えを見たい場合は、質問を変更します。
スティーブジェソップ

1
ASSERT(mem);割り当て結果を確認するためにを使用することに反対します。assertプログラミングエラーをキャッチするためであり、ランタイムリソースの不足ではありません。
hlovdal 2010

4
バイナリ&をa char *およびa size_tと一緒に使用すると、エラーが発生します。のようなものを使用する必要がありますuintptr_t
マルコ


20

ここでは、「切り上げ」部分への代替アプローチを示します。最も華麗にコード化されたソリューションではありませんが、それは仕事を成し遂げます、そしてこのタイプの構文は覚えるのが少し簡単です(さらに、2の累乗ではない整列値で機能します)。uintptr_tキャストは、コンパイラをなだめるために必要でした。ポインタ演算は、除算や乗算があまり好きではありません。

void *mem = malloc(1024 + 15);
void *ptr = (void*) ((uintptr_t) mem + 15) / 16 * 16;
memset_16aligned(ptr, 0, 1024);
free(mem);

2
一般に、「unsigned long long」がある場合、データポインターを保持するのに十分な大きさ(void *)が明示的に定義されているuintptr_tもあります。しかし、何らかの理由で2の累乗ではないアラインメントが必要な場合、ソリューションには確かにメリットがあります。
Jonathan Leffler、

@Andrew:このタイプの構文に賛成すると、覚えるのが少し簡単になります(2の累乗ではない位置合わせ値でも機能します)
legends2k

19

残念ながら、C99では、C99に準拠するすべてのC実装間で移植可能な方法であらゆる種類の配置を保証することはかなり難しいようです。どうして?ポインタは「バイトアドレス」であることが保証されていないため、フラットメモリモデルでは想像できるかもしれません。uintptr_tの表現も保証されていないため、それ自体はオプションのタイプです。

単純なバイトアドレスであるvoid *(および定義により、char *)の表現を使用するいくつかの実装を知っているかもしれませんが、C99によって、プログラマーには不透明です。実装は、セット{によってポインタを表すかもしれないセグメントオフセット } オフセット配向を知っている誰-有することができる「現実に」を ポインタは、何らかの形のハッシュテーブルルックアップ値、またはリンクリストルックアップ値でさえある可能性があります。境界情報をエンコードできます。

C標準の最近のC1Xドラフトには、_Alignasキーワードが含まれています。それは少し役立つかもしれません。

C99が提供する唯一の保証は、メモリ割り当て関数が、任意のオブジェクト型を指すポインターへの割り当てに適したポインターを返すことです。オブジェクトの配置を指定することはできないため、明確に定義された移植可能な方法で配置を行う独自の割り当て関数を実装することはできません。

この主張について間違っているのは良いことです。


C11は持っていaligned_alloc()ます。(C ++ 11/14 / 1zにはまだありません)。 _Alignas()また、C ++ alignas()は、自動および静的ストレージ(または構造体レイアウト)に対してのみ、動的割り当てのために何もしません。
Peter Cordes

15

16対15バイトカウントのパディングフロントでは、Nのアラインメントを取得するために追加する必要がある実際の数はmax(0、NM)です。ここで、Mはメモリアロケーターの自然なアラインメントです(両方とも2の累乗です)。

アロケータの最小メモリアラインメントは1バイトであるため、15 = max(0,16-1)は控えめな答えです。ただし、メモリアロケータが32ビットのint境界整列アドレスを提供することがわかっている場合(これはかなり一般的です)、パッドとして12を使用することもできます。

これはこの例では重要ではありませんが、すべてのintが保存する12KのRAMを備えた組み込みシステムでは重要になる可能性があります。

実際に可能なすべてのバイトを保存しようとする場合に実装するための最良の方法は、ネイティブメモリアライメントにフィードできるようにマクロとして使用することです。繰り返しますが、これはおそらく、すべてのバイトを保存する必要がある組み込みシステムでのみ役立ちます。

次の例では、ほとんどのシステムで値1はで問題MEMORY_ALLOCATOR_NATIVE_ALIGNMENTありませんが、32ビット境界で割り当てられた理論上の組み込みシステムの場合、次のようにすると、わずかな貴重なメモリを節約できます。

#define MEMORY_ALLOCATOR_NATIVE_ALIGNMENT    4
#define ALIGN_PAD2(N,M) (((N)>(M)) ? ((N)-(M)) : 0)
#define ALIGN_PAD(N) ALIGN_PAD2((N), MEMORY_ALLOCATOR_NATIVE_ALIGNMENT)

8

おそらく、彼らはmemalignの知識に満足していたでしょうか?そして、ジョナサン・レフラーが指摘するように、知っておくべき2つの新しい望ましい関数があります。

おっと、フロリンは私をそれに打ち負かした。しかし、私がリンクしているmanページを読むと、おそらく以前のポスターで提供された例を理解できるでしょう。


1
ノートの現在(2016年2月)バージョンその参照ページには、「言うmemalign機能が廃止され、aligned_allocまたはposix_memalign代わりに使用する必要があります」。2008年10月に何が言ったのかはわかりませんがaligned_alloc()、C11に追加されたため、おそらく言及しませんでした。
Jonathan Leffler、2016

5

常にベクトル化されたOS X / iOSライブラリであるAccelerate.frameworkでは、常にこの種のことを行っています。常に位置合わせに注意を払う必要があります。かなりの数のオプションがあり、そのうちの1つまたは2つは、上記で言及していませんでした。

このような小さな配列の最速の方法は、スタックにスタックするだけです。GCC / clangの場合:

 void my_func( void )
 {
     uint8_t array[1024] __attribute__ ((aligned(16)));
     ...
 }

free()は必要ありません。これは通常、2つの命令です。スタックポインターから1024を減算し、次に-alignmentでスタックポインターをANDします。おそらく、リクエスタがヒープ上のデータを必要としていたのは、アレイの寿命がスタックを超えたか、再帰が機能しているか、スタックスペースが非常に貴重なためです。

OS X / iOSでは、malloc / calloc / etcへのすべての呼び出し。常に16バイトにアラインされます。たとえば、AVXに32バイト境界で整列させる必要がある場合は、posix_memalignを使用できます。

void *buf = NULL;
int err = posix_memalign( &buf, 32 /*alignment*/, 1024 /*size*/);
if( err )
   RunInCirclesWaivingArmsWildly();
...
free(buf);

一部の人々は、同様に機能するC ++インターフェースについて言及しています。

ページが2のべき乗に揃えられていることを忘れないでください。そのため、ページ揃えバッファも16バイトに揃えられます。したがって、mmap()とvalloc()およびその他の同様のインターフェースもオプションです。mmap()には、必要に応じて、バッファーにゼロ以外の何かを事前初期化して割り当てることができるという利点があります。これらはページアラインされたサイズであるため、これらから最小割り当てを取得することはなく、最初に触れたときにVMフォルトの影響を受ける可能性があります。

Cheesy:ガードmallocなどをオンにします。このようなn * 16バイトのバッファーは、VMがオーバーランをキャッチするために使用され、その境界がページ境界にあるため、n * 16バイトにアラインされます。

一部のAccelerate.framework関数は、ユーザー提供の一時バッファーを取り込んで、スクラッチスペースとして使用します。ここで、渡されたバッファが大幅にずれており、ユーザーが積極的に私たちの生活を困難にしようとしていると想定する必要があります。(私たちのテストケースでは、一時バッファーの直前と直後にガードページを貼り付けて、スパイトに下線を引きます。)ここでは、16バイトで整列されたセグメントを保証するために必要な最小サイズを返し、その後、手動でバッファーを整列します。このサイズはdesired_size + alignment-1です。したがって、この場合は1024 + 16-1 = 1039バイトです。次に、次のように調整します。

#include <stdint.h>
void My_func( uint8_t *tempBuf, ... )
{
    uint8_t *alignedBuf = (uint8_t*) 
                          (((uintptr_t) tempBuf + ((uintptr_t)alignment-1)) 
                                        & -((uintptr_t) alignment));
    ...
}

alignment-1を追加すると、ポインターは最初の境界整列されたアドレスを越えて移動し、次に-alignment(例えば、alignment = 16の場合は0xfff ... ff0)との論理積によって、整列されたアドレスに戻ります。

他の投稿で説明されているように、16バイトアラインメントが保証されていない他のオペレーティングシステムでは、より大きなサイズでmallocを呼び出し、後でfree()のポインターを脇に置いてから、上記のようにアラインし、アラインされたポインターを使用できます。一時バッファの場合について説明しました。

aligned_memsetに関しては、これはかなりばかげています。アライメントされたアドレスに到達するには、最大15バイトでループするだけでよく、その後、最後にいくつかの可能なクリーンアップコードを使用して、アライメントされたストアに進みます。整列された領域と重なる非整列ストアとして(長さが少なくともベクトルの長さである場合)、またはmovmaskdquのようなものを使用して、ベクトルコードでビットをクリーンアップすることもできます。誰かが怠けているだけです。ただし、インタビュアーがstdint.h、ビットごとの演算子、およびメモリの基礎に慣れているかどうかを知りたい場合は、おそらく妥当なインタビューの質問なので、不自然な例は許されます。


5

私が理解しているように、ポインタを整数型に正式に変換することは未定義の動作であるため、標準のC99で求められていることを実行することは不可能であるというShao回答に誰も投票しなかったことに驚きます。(uintptr_t<->の変換を許可void*する標準は別ですが、標準はuintptr_t値の操作を行ってから再び変換することを許可していないようです。)


uintptr_t型が存在すること、またはそのビットが基本となるポインターのビットと何らかの関係があることは必要ありません。ストレージを過剰に割り当てる場合は、ポインタをとして保存しますunsigned char* myptr。そして、 `mptr + =(16-(uintptr_t)my_ptr)&0x0Fを計算すると、my_ptrを定義するすべての実装で動作が定義されますが、結果のポインターが整列するかどうかは、uintptr_tビットとアドレスの間のマッピングに依存します。
スーパーキャット


3

この質問を読んだときに最初に頭に浮かんだのは、整列された構造体を定義してインスタンス化し、それを指すことでした。

他に誰もこれを提案していないので、私が見逃している根本的な理由はありますか?

補足として、charの配列を使用したため(システムのcharが8ビット(つまり1バイト)であると想定)、__attribute__((packed))必ずしも必要ではない(間違っている場合は修正してください)が、とにかく。

これは私が試した2つのシステムで動作しますが、コードの有効性に対して私が誤検知を与えることを知らないコンパイラの最適化がある可能性があります。gcc 4.9.2OSXとgcc 5.2.1Ubuntuで使用しました。

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

int main ()
{

   void *mem;

   void *ptr;

   // answer a) here
   struct __attribute__((packed)) s_CozyMem {
       char acSpace[16];
   };

   mem = malloc(sizeof(struct s_CozyMem));
   ptr = mem;

   // memset_16aligned(ptr, 0, 1024);

   // Check if it's aligned
   if(((unsigned long)ptr & 15) == 0) printf("Aligned to 16 bytes.\n");
   else printf("Rubbish.\n");

   // answer b) here
   free(mem);

   return 1;
}

1

MacOS X固有:

  1. mallocで割り当てられたすべてのポインタは、16バイトにアラインされています。
  2. C11がサポートされているため、aligned_malloc(16、size)を呼び出すことができます。

  3. MacOS Xは、memset、memcpy、memmoveのブート時に個々のプロセッサー用に最適化されたコードを選択し、そのコードは、聞いたことのないトリックを使用して高速化します。99%の確率でmemsetが手書きのmemset16よりも速く実行されるため、質問全体が無意味になります。

100%ポータブルなソリューションが必要な場合は、C11より前にはありません。ポインタの位置合わせをテストするポータブルな方法がないためです。100%ポータブルである必要がない場合は、

char* p = malloc (size + 15);
p += (- (unsigned int) p) % 16;

これは、ポインターをunsigned intに変換するときに、ポインターのアライメントが最下位ビットに格納されることを前提としています。unsigned intに変換すると、情報が失われ、実装によって定義されますが、結果をポインターに変換しないため、問題はありません。

もちろん、恐ろしい部分は、元のポインターをfree()で呼び出すために、どこかに保存する必要があることです。したがって、全体として、私はこのデザインの知恵を本当に疑っています。


1
aligned_mallocOS Xのどこを探していますか?私はXcode 6.1を使用していますが、それはiOS SDKのどこにも定義されておらず、のどこにも宣言されていません/usr/include/*
トッドリーマン

El Capitan上のXCode 7.2のDitto(Mac OS X 10.11.3)。C11関数は、いずれの場合aligned_alloc()もですが、それも宣言されていません。GCC 5.3.0から、興味深いメッセージalig.c:7:15: error: incompatible implicit declaration of built-in function ‘aligned_alloc’ [-Werror]とを受け取りますalig.c:7:15: note: include ‘<stdlib.h>’ or provide a declaration of ‘aligned_alloc’。コードには実際にが含まれていましたが、エラーメッセージは変更されて<stdlib.h>いませ-std=c11-std=gnu11
ジョナサンレフラー、2016

0

いくつかの16バイトを追加してから、ポインターの下に(16-mod)を追加することにより、元のptrを16ビットに揃えてプッシュすることもできます。

main(){
void *mem1 = malloc(1024+16);
void *mem = ((char*)mem1)+1; // force misalign ( my computer always aligns)
printf ( " ptr = %p \n ", mem );
void *ptr = ((long)mem+16) & ~ 0x0F;
printf ( " aligned ptr = %p \n ", ptr );

printf (" ptr after adding diff mod %p (same as above ) ", (long)mem1 + (16 -((long)mem1%16)) );


free(mem1);
}

0

1バイトを無駄にすることができないという制約がある場合、このソリューションは機能します。注:これは無限に実行される場合があります:D

   void *mem;  
   void *ptr;
try:
   mem =  malloc(1024);  
   if (mem % 16 != 0) {  
       free(mem);  
       goto try;
   }  
   ptr = mem;  
   memset_16aligned(ptr, 0, 1024);

Nバイトのブロックを割り当ててから解放してから、Nバイトの別のブロックを要求すると、元のブロックが再び返される可能性が非常に高くなります。したがって、最初の割り当てがアライメント要件を満たさない場合、無限ループが発生する可能性が非常に高くなります。もちろん、それは多くのCPUサイクルを浪費するという犠牲を払って1バイトを浪費することを避けます。
ジョナサンレフラー、2016

あなたは確信している%オペレータが定義されてvoid*意味のある方法では?
Ajay Brahmakshatriya

0

ソリューションでは、メモリを揃え、1バイトのメモリを無駄にしないパディングの概念を使用しました。

制約がある場合、1バイトを無駄にすることはできません。mallocで割り当てられたすべてのポインタは、16バイトにアラインされています。

C11がサポートされているため、を呼び出すだけaligned_alloc (16, size)です。

void *mem = malloc(1024+16);
void *ptr = ((char *)mem+16) & ~ 0x0F;
memset_16aligned(ptr, 0, 1024);
free(mem);

1
多くの64ビットシステムでは、malloc()実際にによって返されるポインタは16バイト境界に配置されますが、どの標準でもそれを保証するものはありません。 8バイト境界で十分であり、4バイト境界で十分な場合もあります。
ジョナサンレフラー、2016

0
size =1024;
alignment = 16;
aligned_size = size +(alignment -(size %  alignment));
mem = malloc(aligned_size);
memset_16aligned(mem, 0, 1024);
free(mem);

これが最も単純な実装であることを願って、コメントを教えてください。


-3
long add;   
mem = (void*)malloc(1024 +15);
add = (long)mem;
add = add - (add % 16);//align to 16 byte boundary
ptr = (whatever*)(add);

あなたの追加はmallocされていない場所を指すため、これには問題があると思います-これがどのように機能したかわかりません。
resultsway 2013年

@サムそれはする必要がありますadd += 16 - (add % 16)(2 - (2 % 16)) == 0
SSアン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.