web-dev-qa-db-ja.com

Cの構造体メモリレイアウト

C#のバックグラウンドがあります。私は、Cのような低レベル言語の初心者です。

C#では、structのメモリはデフォルトでコンパイラによってレイアウトされます。コンパイラは、データフィールドを並べ替えたり、フィールド間に追加のビットを暗黙的に埋め込むことができます。そのため、正確なレイアウトのためにこの動作をオーバーライドするには、特別な属性を指定する必要がありました。

私の知る限り、Cはデフォルトでstructのメモリレイアウトを並べ替えたり整列したりしません。しかし、見つけるのが非常に難しい小さな例外があると聞きました。

Cのメモリレイアウト動作とは何ですか?何を並べ替える必要がありますか?

75
Eonil

Cでは、コンパイラーはすべてのプリミティブ型のアライメントを指示できます。通常、配置は型のサイズです。しかし、それは完全に実装固有です。

すべてのオブジェクトが適切に整列されるように、パディングバイトが導入されています。並べ替えはできません。

おそらくすべてのリモートの最新コンパイラーは#pragma packこれは、パディングの制御を許可し、ABIに準拠するためにそれをプログラマに任せます。 (ただし、これは厳密に非標準です。)

C99§6.7.2.1から:

12構造体または共用体オブジェクトの各非ビットフィールドメンバーは、そのタイプに適した実装定義の方法で整列されます。

13構造体オブジェクト内では、非ビットフィールドメンバーとビットフィールドが存在するユニットのアドレスは、宣言された順に増加します。適切に変換された構造体オブジェクトへのポインターは、その最初のメンバー(またはそのメンバーがビットフィールドである場合は、そのメンバーが存在するユニット)を指し、逆も同様です。構造体オブジェクト内には無名のパディングがありますが、その先頭にはありません。

101
Potatoswatter

実装固有ですが、実際にはルール(_#pragma pack_などがない場合)は次のとおりです。

  • 構造体のメンバーは、宣言された順序で格納されます。 (これは、前述のC99標準で必要です。)
  • 必要に応じて、各構造体メンバーの前にパディングが追加され、正しい配置が確保されます。
  • 各プリミティブ型Tには、sizeof(T)バイトのアライメントが必要です。

したがって、次の構造体が与えられます:

_struct ST
{
   char ch1;
   short s;
   char ch2;
   long long ll;
   int i;
};
_
  • _ch1_はオフセット0です
  • 詰めるためにパディングバイトが挿入されます...
  • sオフセット2
  • _ch2_は、sの直後のオフセット4にあります
  • 位置合わせのために3パディングバイトが挿入されます...
  • llオフセット8
  • iは、llの直後のオフセット16にあります
  • 構造体全体が8バイトの倍数になるように、最後に4つのパディングバイトが追加されます。 64ビットシステムでこれを確認しました。32ビットシステムでは、構造体に4バイトのアライメントを許可する場合があります。

したがって、sizeof(ST)は24です。

パディングを回避するためにメンバーを再配置することにより、16バイトに減らすことができます。

_struct ST
{
   long long ll; // @ 0
   int i;        // @ 8
   short s;      // @ 12
   char ch1;     // @ 14
   char ch2;     // @ 15
} ST;
_
103
dan04

データ構造の整列ウィキペディアの記事 を読むことから始めて、データの整列の理解を深めてください。

wikipediaの記事 から:

データの整列とは、Wordサイズの倍数に等しいメモリオフセットにデータを配置することを意味します。これにより、CPUがメモリを処理する方法によりシステムのパフォーマンスが向上します。データを整列させるには、最後のデータ構造の終わりと次のデータ構造の始まりであるデータ構造のパディングの間に無意味なバイトを挿入する必要があるかもしれません。

GCCドキュメントの 6.54.8 Structure-Packing Pragmas から:

Microsoft Windowsコンパイラとの互換性のために、GCCは一連の#pragmaディレクティブをサポートします。#pragmaディレクティブは、構造体(ゼロ幅ビットフィールド以外)、ユニオン、およびその後に定義されるクラスのメンバーの最大アライメントを変更します。以下のn値は常に2の小さな累乗である必要があり、バイト単位で新しいアライメントを指定します。

  1. #pragma pack(n)は、単に新しい配置を設定するだけです。
  2. #pragma pack()は、コンパイルを開始したときに有効だったアライメントにアライメントを設定します(コマンドラインオプション-fpack-struct [=]も参照してください。コード生成オプションを参照)。
  3. #pragma pack(Push[,n])は、内部スタックに現在のアライメント設定をプッシュし、オプションで新しいアライメントを設定します。
  4. #pragma pack(pop)は、内部スタックの最上部に保存されているものにアライメント設定を復元します(そしてそのスタックエントリを削除します)。 #pragma pack([n])はこの内部スタックに影響しないことに注意してください。したがって、#pragma pack(Push)の後に複数の#pragma pack(n)インスタンスが続き、単一の#pragma pack(pop)でファイナライズされる可能性があります。

いくつかのターゲット、例えばi386およびpowerpcは、文書化された__attribute__ ((ms_struct))として構造をレイアウトするms_struct #pragmaをサポートします。

  1. #pragma ms_struct onは、宣言された構造のレイアウトをオンにします。
  2. #pragma ms_struct offは、宣言された構造のレイアウトをオフにします。
  3. #pragma ms_struct resetはデフォルトのレイアウトに戻ります。
9
jschmier