web-dev-qa-db-ja.com

「スタックアライメント」とは

スタックアライメントとはなぜ使用されるのですか?コンパイラ設定で制御できますか?

この質問の詳細は、msvcでffmpegライブラリを使用しようとするときに直面する問題からとられていますが、私が本当に興味を持っているのは、「スタックアライメント」とは何かの説明です。

詳細:

  • Avcodecにリンクするmsvc準拠プログラムを実行すると、次のエラーが発生します。「コンパイラーがスタック変数を調整しませんでした。Libavcodecが誤ってコンパイルされました」というエラーが発生し、avcodec.dllがクラッシュします。
  • avcodec.dllはmsvcでコンパイルされていないため、内部で何が行われているのかわかりません。
  • Ffmpeg.exeを実行し、同じavcodec.dllを使用すると、すべてがうまく機能します。
  • ffmpeg.exeはmsvcでコンパイルされず、gcc/mingwに準拠していました(avcodec.dllと同じ)。

おかげで、

ダン

45
DanJ

メモリ内の変数の整列(短い履歴)。

過去のコンピュータには8ビットのデータバスがありました。これは、各クロックサイクルで8ビットの情報を処理できることを意味します。その時は大丈夫だった。

その後、16ビットコンピュータが登場しました。下位互換性やその他の問題により、8ビットバイトが維持され、16ビットワードが導入されました。各ワードは2バイトでした。また、各クロックサイクルで16ビットの情報を処理できます。しかし、これには小さな問題がありました。

メモリマップを見てみましょう:

+----+
|0000| 
|0001|
+----+
|0002|
|0003|
+----+
|0004|
|0005|
+----+
| .. |

各アドレスには、個別にアクセスできるバイトがあります。ただし、単語は偶数アドレスでのみフェッチできます。したがって、0000でWordを読み取る場合、0000と0001でバイトを読み取ります。ただし、0001の位置でWordを読み取る場合は、2つの読み取りアクセスが必要です。最初は0000,0001、次に0002,0003で、0001,0002のみが保持されます。

もちろん、これには余分な時間がかかりましたが、それは高く評価されませんでした。だからこそ彼らはアラインメントを発明しました。そのため、Word変数はWord境界に、バイト変数はバイト境界に格納します。

たとえば、バイトフィールド(B)とワードフィールド(W)(および非常に単純なコンパイラ)を持つ構造がある場合、次のようになります。

+----+
|0000| B
|0001| W
+----+
|0002| W
|0003|
+----+

面白くない。しかし、Wordの配置を使用すると、次のことがわかります。

+----+
|0000| B
|0001| -
+----+
|0002| W
|0003| W
+----+

ここでは、アクセス速度のためにメモリが犠牲になります。

ダブルワード(4バイト)またはクワッドワード(8バイト)を使用する場合、これはさらに重要であると想像できます。そのため、最近のほとんどのコンパイラでは、プログラムのコンパイル時に使用する配置を選択できます。

119
Toon Krijthe

IIRC、スタックアライメントとは、変数が特定のバイト数に「アライメント」されたスタックに配置される場合です。したがって、16ビットのスタックアライメントを使用している場合、スタック上の各変数は、関数内の現在のスタックポインターからの2バイトの倍数であるバイトから始まります。

つまり、char(1バイト)などの2バイト未満の変数を使用すると、その変数と次の変数の間に8ビットの未使用の「パディング」が存在することになります。これにより、変数の場所に基づく仮定を使用した特定の最適化が可能になります。

関数を呼び出すとき、次の関数に引数を渡す1つの方法は、それらをレジスタに直接配置するのではなく、スタックに配置することです。呼び出し関数が変数をスタックに配置し、オフセットを使用して呼び出し関数によって読み取られるようにするため、ここでアライメントが使用されているかどうかは重要です。呼び出し元の関数が変数を整列し、呼び出された関数がそれらが整列されていないことを予期している場合、呼び出された関数は変数を見つけることができません。

Msvcでコンパイルされたコードは、変数の配置について意見の相違があるようです。すべての最適化をオフにしてコンパイルしてみてください。

11
Shaun Bouckaert

一部のCPUアーキテクチャは、さまざまなデータ型の特定の配置を必要とし、このルールを守らないと例外がスローされます。標準モードでは、x86は基本的なデータ型に対してこれを必要としませんが、パフォーマンスが低下する可能性があります(低レベルの最適化のヒントについては、www.agner.orgを確認してください)。

ただし、 [〜#〜] sse [〜#〜] 命令セット(高性能でよく使用されます)オーディオ/ビデオ処理には厳密な配置要件があり、使用しようとすると例外がスローされます非整列データ(一部のプロセッサでは、はるかに遅い非整列バージョンを使用しない限り)。

あなたの問題は 恐らく 1つのコンパイラが期待すること 発信者 他の人が期待している間、スタックを揃えておく 被呼者 必要に応じてスタックを調整します。

編集する:例外が発生する理由については、DLLのルーチンがおそらく一部の一時スタックデータでSSE命令を使用する必要があり、2つの異なるコンパイラが原因で失敗します。呼び出し規約に同意しないでください。

11
snemarch

私の知る限り、コンパイラは通常、スタック上にある変数を整列しません。ライブラリは、コンパイラでサポートされていない一部のコンパイラオプションセットに依存している可能性があります。通常の修正は、静的に配置する必要がある変数を宣言することですが、他の人のコードでこれを行う場合は、問題の変数が関数ではなく関数の後で初期化されるようにする必要があります。宣言。

// Some compilers won't align this as it's on the stack...
int __declspec(align(32)) needsToBe32Aligned = 0;
// Change to
static int __declspec(align(32)) needsToBe32Aligned;
needsToBe32Aligned = 0;

または、スタック上の変数を調整するコンパイラスイッチを見つけます。明らかに、ここで使用した "__declspec" align構文は、コンパイラが使用するものとは異なる場合があります。

2
Dan Olson