#pragma packエフェクト


233

誰かが#pragma packプリプロセッサステートメントの機能を説明できるかどうか、さらに重要なことには、なぜそれを使用する必要があるのか​​と思っていました。

いくつかの洞察を提供してくれたMSDNページをチェックアウトしましたが、経験のある人からもっと聞いてもらいたいと思っていました。以前はコードで見たことがありますが、もうどこにあるかわかりません。


1
構造体の特定の配置/パッキングを強制しますが、すべての#pragmaディレクティブと同様に、実装で定義されます。
dreamlax 2010

A mod s = 0ここで、Aはアドレス、sはデータ型のサイズです。これにより、データが正しく配置されているかどうかがチェックされます。
legends2k 2013年

回答:


422

#pragma pack特定の配置で構造体メンバーをパックするようコンパイラーに指示します。ほとんどのコンパイラーは、構造体を宣言すると、メンバーの間にパディングを挿入して、メンバーがメモリ内の適切なアドレス(通常は型のサイズの倍数)に整列されるようにします。これにより、適切に位置合わせされていない変数へのアクセスに関連する一部のアーキテクチャーでのパフォーマンスの低下(または完全なエラー)が回避されます。たとえば、4バイトの整数と次の構造体が与えられたとします。

struct Test
{
   char AA;
   int BB;
   char CC;
};

コンパイラは、次のように構造体をメモリに配置することを選択できます。

|   1   |   2   |   3   |   4   |  

| AA(1) | pad.................. |
| BB(1) | BB(2) | BB(3) | BB(4) | 
| CC(1) | pad.................. |

そしてsizeof(Test)、それはデータのみの6つのバイトを含んでいても、4×3 = 12であろう。#pragma(私の知る限り)の最も一般的な使用例は、コンパイラーがデータにパディングを挿入せず、各メンバーが前のものに従っていることを確認する必要があるハードウェアデバイスで作業する場合です。を使用する#pragma pack(1)と、上記の構造体は次のように配置されます。

|   1   |

| AA(1) |
| BB(1) |
| BB(2) |
| BB(3) |
| BB(4) |
| CC(1) |

そしてsizeof(Test)、1×6 = 6になります。

を使用する#pragma pack(2)と、上記の構造体は次のように配置されます。

|   1   |   2   | 

| AA(1) | pad.. |
| BB(1) | BB(2) |
| BB(3) | BB(4) |
| CC(1) | pad.. |

そしてsizeof(Test)、2×4 = 8になります。

構造体内の変数の順序も重要です。変数は次のように並べられます:

struct Test
{
   char AA;
   char CC;
   int BB;
};

を使用する#pragma pack(2)と、構造体は次のように配置されます。

|   1   |   2   | 

| AA(1) | CC(1) |
| BB(1) | BB(2) |
| BB(3) | BB(4) |

そしてsizeOf(Test)、3×2 = 6であろう。


76
パッキングの欠点を追加する価値があるかもしれません。(整列されていないオブジェクトへのアクセスは、最良の場合は低速ですが、一部のプラットフォームではエラーが発生します。)
jalf

11
言及されている「パフォーマンスペナルティ」の調整は、一部のシステムでは実際にメリットになる可能性があるようですdanluu.com/3c-conflict

6
@Pacerierそうではない。この投稿では、かなり極端な調整(4KB境界での調整)について説明しています。CPUは、さまざまなデータ型に対して特定の最小整列を期待していますが、最悪の場合、8バイトの整列が必要です(16または32バイトの整列を必要とする可能性のあるベクトル型は数えません)。これらの境界に沿って整列しないと、一般に顕著なパフォーマンスヒットが発生します(ロードを1つの操作ではなく2つの操作として実行する必要がある場合があるため)が、型は適切に整列されているかそうではありません。それよりも厳密な配置では何も買いません(そしてキャッシュの利用率を台無しにします
15年

6
言い換えると、doubleは8バイト境界にあることを期待しています。7バイト境界に配置すると、パフォーマンスが低下します。しかし、それを16、32、64、または4096バイトの境界に配置しても、8バイトの境界がすでに提供しているものを超えるものはありません。CPUからは同じパフォーマンスが得られますが、その投稿で概説されている理由により、キャッシュの使用率は大幅に低下します。
2015年

4
したがって、レッスンは「パッキングが有益である」(パッキングはタイプの自然な配置に違反するため、パフォーマンスが低下する)のではなく、単に「必要以上にオーバーアライメントしないでください」
jalf

27

#pragma(このコンパイラのみのように)移植できないメッセージをコンパイラに送信するために使用されます。特定の警告の無効化や構造体のパッキングなどが一般的な理由です。特定の警告を無効にすることは、エラーフラグをオンにして警告を付けてコンパイルする場合に特に役立ちます。

#pragma pack具体的には、パックされる構造体のメンバーを揃えてはならないことを示すために使用されます。ハードウェアの一部へのメモリマップインターフェイスがあり、異なる構造体メンバーが指す場所を正確に制御できる必要がある場合に役立ちます。ほとんどのマシンは整列されたデータの処理がはるかに高速であるため、これは特に適切な速度の最適化ではありません。


17
後で元に戻すには、次のようにします。#pragma pack(push、1)および#pragma pack(pop)
malhal

16

これは、構造体のオブジェクトを整列する境界をコンパイラに通知します。たとえば、次のような場合:

struct foo { 
    char a;
    int b;
};

典型的な32ビットマシンを使用すると、通常は「したい」と思い間のパディングの3バイト持つようにaし、bそれはとてもbそのアクセス速度を最大化するために、4バイト境界に着陸します(通常はデフォルトでは何が起こるかのように)。

ただし、外部で定義された構造と一致させる必要がある場合は、コンパイラーがその外部定義に従って構造を正確にレイアウトするようにする必要があります。このケースでは、コンパイラを与えることができ#pragma pack(1)、それを教えていない構造体の定義は、メンバー間のパディングが含まれている場合、あなたは通常、名前のメンバーと、例えば(明示的に挿入する-メンバー間のいずれかのパディングを挿入するunusedNignoreN、またはその上で何か注文)。


「通常、aとbの間に3バイトのパディングを入れて、bが4バイトの境界に到達してアクセス速度を最大にしたい」-3バイトのパディングでアクセス速度を最大にする方法は?
アシュウィン

8
@Ashwin:b4バイト境界に配置することは、プロセッサが単一の4バイトロードを発行することでロードできることを意味します。それはプロセッサに多少依存しますが、それが奇妙な境界にある場合、ロードするためにプロセッサが2つの個別のロード命令を発行する必要があり、シフターを使用してそれらのピースを組み合わせる可能性が高くなります。典型的なペナルティは、そのアイテムの3倍遅いロードです。
Jerry Coffin 2014年

...境界整列および非境界整列のintを読み取るためのアセンブリコードを見ると、境界整列された読み取りは通常、単一のニーモニックです。非整列読み取りは、intを1つにまとめ、バイトごとに選択してレジスタの正しい位置に配置するため、10行のアセンブリで簡単にできます。
SF。

2
@SF .: x86 CPU(明らかな例の1つ)では、操作はハードウェアで実行されますが、そうでない場合でも誤解しないでください。ただし、操作はほぼ同じです。と減速。
Jerry Coffin

8

データ要素(クラスや構造体のメンバーなど)は通常、アクセス時間を改善するために、現在の世代のプロセッサーのWORDまたはDWORD境界に整列されます。4で割り切れないアドレスのDWORDを取得するには、32ビットプロセッサで少なくとも1つの余分なCPUサイクルが必要です。したがって、たとえば3つのcharメンバーがあるchar a, b, c;場合、実際には6バイトまたは12バイトのストレージを使用する傾向があります。

#pragmaこれをオーバーライドして、アクセス速度を犠牲にして、より効率的なスペース使用を実現したり、異なるコンパイラー・ターゲット間で保管されたデータの整合性を保つことができます。この16ビットから32ビットのコードへの移行で、私はたくさんの楽しみを持ちました。64ビットコードに移植すると、一部のコードで同じ種類の頭痛の種になると思います。


実際にchar a,b,c;は、通常、少なくとも3バイトまたは4バイトのストレージが必要です(少なくともx86では)。これは、アライメント要件が1バイトであるためです。そうでない場合、どのように対処しchar str[] = "foo";ますか?aへのアクセスcharは常に単純なフェッチシフトマスクですが、aへのアクセスは、int整列されているかどうかに応じて、フェッチ、フェッチ、マージ、または単にフェッチすることができます。intそうでなければ、(例えば)の半分を取得したいので(x86の)32ビット(4バイト)アライメントを持ってint一つにDWORD他に半、そしてその2つのルックアップを取るだろう。
TimČas12年

3

コンパイラーは、特定のプラットフォームで最大のパフォーマンスを達成するために、構造体のメンバーを調整できます。#pragma packディレクティブを使用すると、その配置を制御できます。通常、最適なパフォーマンスを得るには、デフォルトのままにしておく必要があります。リモートマシンに構造体を渡す必要がある場合は、通常#pragma pack 1、不要な配置を除外するために使用します。


2

コンパイラー、特定のアーキテクチャーでのパフォーマンスのために、構造体メンバーを特定のバイト境界に配置する場合があります。これにより、メンバー間に未使用のパディングが残る場合があります。構造体のパッキングにより、メンバーは連続します。

これは、たとえば、データがシーケンス内の特定の位置にある必要がある特定のファイルまたは通信フォーマットに準拠する構造が必要な場合に重要になることがあります。ただし、このような使用法ではエンディアンの問題は処理されないため、使用しても移植性がない場合があります。

また、レジスタアクセスが直接アドレスではなく構造を介して行われるように、UARTまたはUSBコントローラなどの一部のI / Oデバイスの内部レジスタ構造を正確にオーバーレイすることもできます。


1

これは、レジスタの順序と配置に厳しい要件があるハードウェア(メモリマップデバイスなど)にコーディングする場合にのみ使用することになるでしょう。

ただし、これはその目的を達成するためのかなり率直なツールのように見えます。より良いアプローチは、このプラグマをいじくるのではなく、アセンブラーでミニドライバーをコーディングしてC呼び出しインターフェイスを提供することです。


頻繁にアクセスされない大きなテーブルのスペースを節約するために、私は実際にそれをかなり使用します。そこでは、スペースを節約するためだけであり、厳密な配置のためではありません。(ところで、あなたに賛成票を投じました。誰かがあなたに反対票を投じました。)
Todd Lehman

1

以前にコードで使用したことがありますが、レガシーコードとのインターフェイスとしてのみ使用されます。これは、以前のCarbonバージョン(それ自体、元のM68k System 6.5バージョンとの下位互換性がありました...アイデアが出ました)から設定ファイルをロードする必要があるMac OS X Cocoaアプリケーションでした。元のバージョンの設定ファイルは、構成構造のバイナリダンプでした。これは、#pragma pack(1)余分なスペースを取り、ジャンク(つまり、構造内にあるはずのパディングバイト)を節約することを回避するために使用されていました。

コードの最初の作成者は、#pragma pack(1)プロセス間通信でメッセージとして使用される構造を格納するためにも使用していました。コードがメッセージ構造体の特定の部分を最初から(ewww)から数えたバイト数をカウントすることで見たため、ここでの理由は、不明または変更されたパディングサイズの可能性を回避するためだったと思います。


1

マルチスレッドコンテキストでの誤った共有を防ぐために、構造体がキャッシュライン全体を使用するようにするために、これを使用する人を見てきました。デフォルトで緩やかにパックされる多数のオブジェクトを使用する場合は、メモリを節約し、キャッシュのパフォーマンスを向上させてそれらをより密にパックすることができますが、通常、非境界整列のメモリアクセスは速度を低下させるので、欠点があるかもしれません。


0

#pragma packが提供するデータ整合性を実現する他の方法があることに注意してください(たとえば、一部の人々は、ネットワークを介して送信する必要のある構造に#pragma pack(1)を使用します)。たとえば、次のコードとその後の出力を参照してください。

#include <stdio.h>

struct a {
    char one;
    char two[2];
    char eight[8];
    char four[4];
};

struct b { 
    char one;
    short two;
    long int eight;
    int four;
};

int main(int argc, char** argv) {
    struct a twoa[2] = {}; 
    struct b twob[2] = {}; 
    printf("sizeof(struct a): %i, sizeof(struct b): %i\n", sizeof(struct a), sizeof(struct b));
    printf("sizeof(twoa): %i, sizeof(twob): %i\n", sizeof(twoa), sizeof(twob));
}

出力は次のとおりです。sizeof(struct a):15、sizeof(struct b):24 sizeof(twoa):30、sizeof(twob):48

構造体aのサイズがバイト数とまったく同じであることに注意してください。ただし、構造体bにはパディングが追加されています(パディングの詳細については、こちらを参照してください)。#pragmaパックとは対照的にこれを行うことにより、「ワイヤー形式」を適切なタイプに変換する制御を行うことができます。たとえば、「char two [2]」を「short int」などに変換します。


いいえ、それは間違っています。b.twoのメモリ内の位置を見ると、それはb.oneの1バイトではありません(コンパイラはb.twoを整列できる(そして頻繁に)ので、ワードアクセスに整列されます)。a.twoの場合は、a.oneの直後の1バイトです。短いintとしてa.twoにアクセスする必要がある場合は、2つの代替手段が必要です。ユニオンを使用する(ただし、エンディアンの問題がある場合、これは通常失敗します)、またはコードでアンパック/変換(適切なntohX関数を使用)
xryl669

1
sizeofを使用して出力する必要があるsize_tを返します。間違ったフォーマット指定子を使用すると、未定義の動作が発生します%zu
phuclv

0

なぜそれを使いたいのですか?

構造のメモリを減らすには

なぜそれを使用すべきではないのですか?

  1. 一部のシステムはアライメントされたデータでより適切に機能するため、これによりパフォーマンスが低下する可能性があります
  2. 一部のマシンは、アライメントされていないデータの読み取りに失敗します
  3. コードは移植できません
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.