Windows+GCCでのメモリの位置合わせに関するよくある質問

4175 ワード

構造/クラスの配置の宣言


gccとwindowsのmodifier/attributeに対するサポートは実はそれほど悪くない.例えばgccの例では、メモリの位置合わせは次のように書きます.
class X
{
  //...
} __attribute__((aligned(16)));

でも実際には
class __attribute__((aligned(16))) X 
{
    /*...*/
};

gccと同様に認識できる.これによりMSVCとgccはマクロを用いてプラットフォーム間コンパイルを完了することができる.

スタックとスタック上の整列型変数の割り当て


[位置合わせ](Align)次の場合、コンパイラにその変数に位置合わせされたアドレスを割り当てるように求められます.
void foo()
{
    X v; // v 16 
    X* p = new X; // p 16 
    X* a = new X[ARRAY_SIZE]; //
}

スタック上の変数スタックに割り当てられた変数は,alignというhintの存在により16バイト整列の要件を満たすことができる.でも配列は?一般的な法則に従って分析すると,整列後のsizeof(X)は,整列の整数倍に違いない.例えば16バイトの位置合わせであれば、Xの大きさは16の倍数しかありません.したがって,本例の配列では,コンパイラもaが16バイト整列すべきであることを知っているはずである.
しかし、実はおかしいです.MSVCでは、pとaは位置合わせの要求をよく守っています.gccではpは整列しているが,aはそうではない.実はこの問題は2004年に提起されたが、これまで誰も手を出したことがない.もちろん、Xの配列は必ず整列するという基準もありません.この問題を解決するには、classのoperator new/deleteを再ロードするか、memalign/aligned_を使用します.mallocは整列したメモリを割り当て、newをplacementします.使いやすいため、オペレータの再ロードを選択しました.
clangは位置合わせのサポートに対してもっとはっきりしています:16 Bの位置合わせはもう十分です.だからalignは完全にコンパイラに無視された.結局IntelがAVXを出てきて、Clangは馬鹿になった.この問題が3.4修正されるかどうか分かりません.

コンパイラはメモリの位置合わせを実現する方法


MSVCは、x 86の下でデフォルトでサポートされている4 Bのメモリ整列である.すなわち,関数入口では,ESPとEBPは4バイトの整列しか保証されていない.このとき,現在の関数ドメインスタック上の変数のアドレスはすべてESP+4*xの形式である.関数内に整列変数がある場合は、次のようになります.
void foo()
{
    int __declspec(align(16)) x;
    // ...
}

では、コンパイラはコード生成時に関数の前部にprologというコードを挿入し、このコードはスタックを16 B整列に修正します.例えば
PUSH EBP
MOV  EBP, ESP
SUB  ESP, XXX
AND  ESP, 0xFFFFFFF0h

これによりESPは必ず16バイト整列します.このときxに割り当てられたアドレスは,ESP+0 x 10*nの形式であり,整列の必要性を満たす.
GCCでは、gccは、すべての関数が他の関数を呼び出す際にESPが16バイト整列する義務があると考えている(もちろん、コンパイルオプションでこの要求を修正することができる).呼び出し元だけが保証するのではなく、呼び出し元もデフォルトです.したがって,GCCは呼び出し効率を向上させるために,呼び出し元の仮定に基づいて「スタック修正」というステップを削除した.
元のコードが
PUSH EBP             ;  ESP 16B ,Push EBP,ESP 16x-4 。
MOV  EBP, ESP
SUB  ESP, 0x0000023Ch ;  16 

では、呼び出された側がこの約束を守った場合、ESPは当然16バイト整列である.しかし、例外がある.MinGWではスレッドのエントリ関数はAPIによってコールバックされる.この関数はWindowsの標準4バイトに準拠している可能性が高い.これにより、スタック補正がない場合、スレッド呼び出しチェーン16 B全体の整列の暗黙の了解が破られる.この時点でSSEコードが「16バイト整列」の変数にアクセスしようとすると、これらの変数のアドレスが整列していないため、segment faultの異常が発生する可能性があります.
この問題を解決するには、2つの一般的な方法があります.第一に、Wrapper関数を書いて、ESPを整列させた後に呼び出しを転送します.第二に、コンパイルオプション-mstackrealignを使用します.このオプションは、すべての関数に対してスタック修正のPROLOGコードを追加し、関数スタックフレームが必ず16バイトまたはユーザー指定のサイズで整列することを保証します.