就職の面接の一環としてテストを終えたところで、参考のために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
}
{
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()
は、anyの目的に十分に揃ったポインタを返すことになっています。ただし、 'any'の意味は基本的に基本型のようなものです - long
、double
、long double
、long long
、そしてオブジェクトへのポインタと関数へのポインタあなたがグラフィックスシステムで遊ぶことのように、より特殊なことをするとき、それらはシステムの他の部分よりも厳密なアラインメントを必要とするかもしれません。
次のステップは、voidポインタをcharポインタに変換することです。それにもかかわらず、GCCは無効ポインタに対してポインタ算術演算を実行することは想定されていません(GCCには、それを悪用したときに警告するオプションがあります)。その後、開始ポインタに16を追加します。 malloc()
が信じられないほど整列不良のポインタ:0x800001を返したとします。 16を加算すると0x800011が得られます。 16バイト境界に切り捨てたいので、最後の4ビットを0にリセットします。0x0Fは最後の4ビットを1に設定します。したがって、~0x0F
では、最後の4つを除いて、すべてのビットが1に設定されています。それを0x800011で終わらせると0x800010になります。他のオフセットを反復処理して、同じ算術が機能することを確認できます。
最後のステップfree()
は簡単です。free()
、malloc()
、またはcalloc()
のいずれかがあなたに返した値を常にrealloc()
に返すだけです。それ以外は災害です。その値を保持するためにmem
を正しく指定しました - ありがとうございます。無料でそれを解放します。
最後に、システムのmalloc
パッケージの内部について知っていれば、16バイト境界で整列されたデータ(または8バイト境界で整列されたデータ)が返される可能性があります。 16バイト境界で整列されている場合は、値を無視する必要はありません。しかし、これは手間がかかり移植性がない - 他のmalloc
パッケージは異なる最小アラインメントを持っているので、何か違うことをしたときにコアダンプを起こすことになるだろう。広い範囲内で、このソリューションは移植可能です。
他の誰かが整列メモリを取得する別の方法としてposix_memalign()
を言及しました。それはいたるところで利用できるわけではありませんが、これを基礎として使用して実装されることが多いでしょう。アライメントが2のべき乗であると便利であることに注意してください。他の配置は面倒です。
もう1つのコメント - このコードは割り当てが成功したことを確認しません。
Windows Programmer ポインタに対してビットマスク操作はできないことを指摘しました。実際、GCC(3.4.6と4.3.1でテスト済み)はそのように文句を言います。そのため、基本プログラムの修正版 - メインプログラムに変換されたもの - が続きます。指摘されているように、私は16の代わりに15だけを追加するという自由を取った。私はuintptr_t
を使っています。なぜならC99はほとんどのプラットフォームでアクセスできるようになっているからです。 printf()
ステートメントでPRIXPTR
を使用しないのであれば、#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()
を汎用の割り当て関数に変換するには、何人かの人々が彼らの答えで示したように、アロケータからの単一の戻り値がリリースアドレスをエンコードする必要があります。
ri コメント:今朝、読解力の問題を読んでいるかもしれませんが、インタビューの質問で「1024バイトのメモリをどのように割り当てますか」と言って、それ以上は明らかに割り当てているとします。インタビュアーからの自動的な失敗ではないでしょうか。
私の回答は300文字のコメントに収まりません...
それは違います、と思います。私を含め、ほとんどの人が「1024バイトのデータを格納できるスペースを割り当てるにはどうすればよいですか。ベースアドレスは16バイトの倍数になります」と質問したと思います。インタビュアーが実際にどのようにして1024バイト(のみ)を割り振り、それを16バイトに位置合わせすることができるかを意図している場合、選択肢はさらに限定されます。
しかし、インタビュアーがこれらの回答のいずれかを期待していた場合、私は彼らがこの解決策が密接に関連した質問に答えていることを認識し、会話を正しい方向に向けるように彼らの質問をリフレームします。 (さらに、インタビュアーが本当にぎこちなくなった場合、その仕事はしたくないでしょう。不十分な正確さの要求に対する答えが修正なしに炎の中で打ち破られるのであれば、インタビュアーは安全に働く人ではありません。)
質問のタイトルが最近変更されました。それは私を困惑させた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
の整数倍になる必要があります。戻り値
aligned_alloc
関数は、nullポインタまたは割り当てられた空間へのポインタを返します。
そして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関数のみがオプションでした。
舞台裏では、新しい整列メモリ関数は、問題に概説されているのとほぼ同じ仕事をしますが、整列をより簡単に強制し、コードが内部で整列されないように整列メモリの開始を追跡できます。特別に対処する必要があります - それはちょうど使用された割り当て関数によって返されたメモリを解放します。
質問の見方によって、わずかに異なる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]);
Memに15バイトしか追加されていなかった(1)とは異なり、実際の実装で32バイトが保証されている場合、このコードは実際にアライメントを減らすことができます。 mallocからのアライメント(ありそうにないが、理論的にはCの実装は32バイトのアライメントされた型を持つことができる)。 memset_16alignedを呼び出すだけで構いませんが、構造体にメモリを使用する場合は問題になる可能性があります。
実装固有のアライメント保証についてプログラムで決定する方法がないので、これに対する良い修正が何であるかがわからない(ユーザに返されるバッファが必ずしも任意の構造体に適しているわけではないことをユーザに警告する以外に)。起動時に、2つ以上の1バイトバッファを割り当てることができ、最悪のアライメントが保証されていると想定しています。あなたが間違っているならば、あなたは記憶を浪費します。もっと良いアイディアをお持ちの方は、そう言ってください...
[追加: '標準'トリックは、必要な配置を決定するために '最大配置タイプになる可能性'の和集合を作成することです。最大揃えの型は(C99では) 'long long
'、 'long double
'、 'void *
'、または 'void (*)(void)
'である可能性があります。 <stdint.h>
を含める場合は、おそらくintmax_t
の代わりに 'long long
'を使用することができます(そしてPower 6(AIX)マシンでは、intmax_t
は128ビット整数型になります)。その共用体のアライメント要件は、単一のcharとそれに続く共用体を持つ構造体にそれを埋め込むことで決定できます。
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のべき乗)を渡しますが、すべて問題ありません。
3)あなたのプラットフォームが提供するものを使用してください:POSIXの場合はposix_memalign
、Windowsの場合は_aligned_malloc
。
4)C11を使用する場合、最も明快で移植性があり簡潔なオプションは、このバージョンの言語仕様で導入された標準ライブラリ関数 aligned_alloc
を使用することです。
posix_memalign()
(もちろんPOSIXプラットフォームで)を試すこともできます。
これは「切り上げ」部分への代替アプローチです。最も見事にコード化された解決策ではありませんが、それは仕事を完成させます、そしてこのタイプの構文は覚えるのが少し簡単です(プラス2のべき乗でないアライメント値のために働くでしょう)。コンパイラをなだめるにはuintptr_t
キャストが必要でした。ポインタ演算は、除算や乗算をあまり好きではありません。
void *mem = malloc(1024 + 15);
void *ptr = (void*) ((uintptr_t) mem + 15) / 16 * 16;
memset_16aligned(ptr, 0, 1024);
free(mem);
残念なことに、C99では、C99に準拠したあらゆるC実装に渡って移植可能な方法であらゆる種類のアライメントを保証するのはかなり難しいようです。どうして?ポインタが「バイトアドレス」であることが保証されていないので、フラットメモリモデルで想像するかもしれません。 intptr_tの表現もそれほど保証されていませんが、それ自体はオプションの型です。
単純なバイトアドレスであるvoid *(および定義上、char *)の表現を使用する実装はいくつか知っているかもしれませんが、C99では不透明です。私たちプログラマ。実装はポインタを集合{セグメント、offset}で表すことができます。 whereoffsetは、「実際には」誰がどのようなアラインメントを持つことができるのでしょう。なぜなら、ポインタは何らかの形のハッシュテーブル検索値、あるいはリンクリスト検索値でさえある可能性さえあります。境界情報をエンコードできます。
C標準に関する最近のC1Xドラフトでは、_ Alignasというキーワードがあります。それは少し助けになるかもしれません。
C99が保証する唯一の保証は、メモリ割り当て関数が、任意のオブジェクト型を指すポインタへの割り当てに適したポインタを返すということです。オブジェクトの整列を指定することはできないので、明確に定義された移植可能な方法で整列を担当する独自の割り当て関数を実装することはできません。
この主張について間違っているのは良いことです。
16バイト対15バイトカウントのパディングフロントでは、Nのアライメントを取得するために追加する必要がある実際の数は、max(0、NM)です。ここで、Mはメモリアロケータの自然なアライメントです。どちらも2の累乗です。
どのアロケータの最小メモリアライメントも1バイトなので、15 = max(0,16-1)が控えめな答えです。しかし、あなたのメモリアロケータがあなたに32ビットのintアラインされたアドレスを与えることを知っているなら(これはかなり一般的です)、あなたはパッドとして12を使用したかもしれません。
これはこの例では重要ではありませんが、保存されたint値が1つ1つカウントされる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)
おそらく彼らは memalign の知識に満足していたでしょうか?そしてJonathan Lefflerが指摘するように、知っておくべき2つの新しい望ましい関数があります。
おっと、フロリンは私にそれを打ちました。しかし、私がリンクしたmanページを読めば、おそらくあなたは以前のポスターによって提供された例を理解するでしょう。
私達はこのようなことをいつも重視している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障害が発生する可能性があります。
安っぽい:ガードmallocまたは同様のものをつけなさい。 VMはオーバーランをキャッチするために使用され、その境界はページ境界にあるため、このようなサイズがn * 16バイトのバッファーは、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とのAND(例:alignment = 16の場合は0xfff ... ff0)を使って整列アドレスに戻します。
他の記事で説明されているように、16バイトのアラインメントが保証されていない他のオペレーティングシステムでは、より大きいサイズでmallocを呼び出し、後でfree()のためにポインタを確保します。一時バッファの場合について説明します。
Aligned_memsetに関しては、これはかなり愚かです。整列されたアドレスに到達するには、最大15バイトでループし、その後、整列されたストアに進み、最後に可能なクリーンアップコードを追加するだけで済みます。整列された領域とオーバーラップする整列されていないストア(長さが少なくともベクトルの長さであれば)として、またはmovmaskdquのようなものを使用して、ベクトルコードでクリーンアップビットを実行することもできます。誰かがただ怠け者です。しかし、インタビュアーがstdint.h、ビット演算子、メモリの基本に慣れているかどうかを知りたいのであれば、それはおそらく妥当なインタビューの質問です。そのため、考案された例は許されません。
この質問を読んだときに頭に浮かんだ最初のことは、位置合わせされた構造体を定義し、それをインスタンス化してからそれをポイントすることでした。
他に誰もこれを提案していないので私が行方不明になっている根本的な理由はありますか?
補足として、(システムのcharが8ビット(すなわち1バイト)であると仮定して)charの配列を使用したので、必ずしもattribute((packed))が必要とは思われません私が間違っているならば私を直しなさい)、しかし私はとにかくそれを入れます。
これは私が試した2つのシステムで動作しますが、コードの有効性に対して誤検知を与えることに気付いていないコンパイラ最適化がある可能性があります。 OSXではgcc 4.9.2、Ubuntuではgcc 5.2.1を使用しました。
#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;
}
memalignの使い方、 Aligned-Memory-Blocks が問題を解決するのに良いかもしれません。
MacOS X特有のもの
C11がサポートされているので、aligned_malloc(16、size)を呼び出すだけです。
MacOS Xはmemset、memcpy、memmoveの起動時に個々のプロセッサ用に最適化されたコードを選びます。そしてそのコードはあなたがそれを速くするために今まで聞いたことがないトリックを使います。 memsetが99%の手書きのmemset16よりも速く実行される可能性が99%で、質問全体が無意味になります。
あなたが100%ポータブルソリューションを望んでいるなら、C11の前にはありません。ポインタの配置をテストするための移植性のある方法がないからです。それが100%ポータブルである必要がないならば、あなたは使うことができます
char* p = malloc (size + 15);
p += (- (unsigned int) p) % 16;
これは、ポインタをunsigned intに変換するときに、ポインタのアライメントが最下位ビットに格納されることを前提としています。 unsigned intへの変換は情報を失い、実装定義ですが、結果をポインタに変換しないので問題ありません。
恐ろしい部分はもちろん、元のポインタがfree()を呼び出すためにどこかに保存されなければならないということです。それで、全体として私はこのデザインの知恵を本当に疑うでしょう。
Memalignを使うだけ? http://linux.die.net/man/3/memalign
解決策のために私はメモリを整列させ、単一バイトのメモリを無駄にしないパディングの概念を使いました。
制約がある場合は、1バイトを無駄にすることはできません。 mallocで割り当てられたすべてのポインタは、16バイト境界で整列されています。
C11がサポートされているので、aligned_malloc(16、size)を呼び出すだけです。
void *mem = malloc(1024+16);
void *ptr = ((char *)mem+16) & ~ 0x0F;
memset_16aligned(ptr, 0, 1024);
free(mem);
また、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);
}
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);