元の答え
{
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()
十分にするように整列されるポインタを返すことになっているいずれかの。目的が、「任意の」の意味は、基本的なタイプのようなもののために主である- 、long
、double
、long 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_t
C99はほとんどのプラットフォームでアクセスできるように十分な長さであるので、私は使用しています。ステートメントで使用するの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関数のみでした。
裏で新しい整列メモリ関数は、質問で概説されているものとほとんど同じ働きをしますが、より簡単に整列を強制し、整列されたメモリの開始を内部的に追跡して、コードが特別に対処する必要があります—使用された割り当て関数によって返されたメモリを解放するだけです。