cに構造体をパックする標準的な方法または標準的な代替手段はありますか?


13

CIでのプログラミングでは、GCC __attribute__((__packed__))属性を使用して構造体をパックすることが非常に重要であることがわかったので、揮発性メモリの構造化されたチャンクをバイトの配列に簡単に変換して、バスを介して送信したり、ストレージに保存したり、レジスタのブロックに適用したりできます。パックされた構造体は、バイトの配列として扱われる場合、パディングを含まないことを保証します。これは無駄であり、セキュリティリスクの可能性があり、ハードウェアとのインターフェースの場合に互換性がありません。

すべてのCコンパイラで動作する構造体をパックするための標準はありませんか?そうでない場合、私はこれがシステムプログラミングにとって重要な機能であると考える際に外れ値ですか?C言語の初期のユーザーは、構造体をパックする必要性を見つけられなかったか、何らかの代替手段がありましたか?


コンパイルドメイン全体で構造体を使用することは、特にハードウェア(別のコンパイルドメイン)を指すため、非常に悪い考えです。パケット構造体は、これを行うための1つのトリックにすぎません。それらには多くの悪い副作用があります。そのため、副作用の少ない問題に対する他の多くの解決策があります。
old_timer

回答:


12

構造体で重要なのは、各構造体インスタンスのアドレスからの各メンバーのオフセットです。物がどれだけぎゅうぎゅうに詰め込まれているかはそれほど問題ではありません。

ただし、配列は、どのように「パック」されるかが重要です。Cの規則では、各配列要素は前の要素から正確にNバイトです。Nはそのタイプを格納するために使用されるバイト数です。

しかし、構造体を使用すると、そのような均一性は必要ありません。

奇妙なパッキングスキームの例を次に示します。

Freescale(自動車用マイクロコントローラーを製造している)は、Time Processing Unitコプロセッサー(eTPUまたはTPU用のgoogle)を備えたマイクロを製造しています。8ビットと24ビットの2つのネイティブデータサイズがあり、整数のみを扱います。

この構造:

struct a
{
  U24 elementA;
  U24 elementB;
};

各U24は独自の32ビットブロックを格納しますが、最上位のアドレス領域にのみ表示されます。

この:

struct b
{
  U24 elementA;
  U24 elementB;
  U8  elementC;
};

隣接する32ビットブロックに2つのU24が格納され、U8は最初のU24の前の「穴」に格納されelementAます。

ただし、必要に応じて、すべてを独自の32ビットブロックにパックするようにコンパイラーに指示できます。RAMではより高価ですが、アクセスに使用する命令は少なくなります。

「パッキング」は「しっかりとパック」を意味するのではなく、構造体の要素をオフセットに配置するためのスキームを意味します。

一般的なスキームはなく、コンパイラー+アーキテクチャー依存です。


1
TPUのコンパイラが他の要素の前struct bに移動elementCするように再配置された場合、準拠するCコンパイラではありません。要素の再配置は、Cで許可されていません
バートバン隠元Schenau

興味深いが、U24は標準のCタイプen.m.wikipedia.org/wiki/C_data_typesではないため、コンパイラがやや奇妙な方法でそれを処理することを強いられることは驚くことではありません。
satur9nine

32ビットのワードサイズのメインCPUコアとRAMを共有します。ただし、このプロセッサには24ビットまたは8ビットのみを扱うALUがあります。そのため、32ビットワードで24ビット数をレイアウトするためのスキームがあります。標準的ではありませんが、パッキングとアライメントの素晴らしい例です。同意して、それは非常に非標準です。
-RichColours

6

CIでのプログラミングで、GCCを使用して構造体をパックすることが非常に重要であることがわかった場合__attribute__((__packed__))[...]

あなたが言及しているので__attribute__((__packed__))、私はあなたの意図はa内のすべてのパディングを排除することだと思いますstruct(各メンバーに1バイトのアライメントを持たせる)。

すべてのCコンパイラで動作する構造体をパックするための標準はありませんか?

...そして答えは「いいえ」です。構造体(およびスタックまたはヒープ内の構造体の連続配列)に対するパディングとデータのアライメントは、重要な理由で存在します。多くのマシンでは、アライメントされていないメモリアクセスにより、パフォーマンスが大幅に低下する可能性があります(一部の新しいハードウェアでは低下します)。まれなケースでは、メモリアクセスの不整合により、回復不能なバスエラーが発生します(オペレーティングシステム全体がクラッシュすることさえあります)。

C標準は移植性に焦点を当てているため、構造内のすべてのパディングを削除し、任意のフィールドを不整合にするだけの標準的な方法はほとんど意味がありません。

このようなデータをすべてのパディングを排除する方法で外部ソースに出力する最も安全で移植性の高い方法は、単にの生のメモリコンテンツを送信しようとするのではなく、バイトストリームへ/からシリアル化することstructsです。また、このシリアル化コンテキスト以外でプログラムがパフォーマンスのペナルティを受けることを防ぎ、structソフトウェア全体をスローしてグリッチすることなく、新しいフィールドを自由に追加することもできます。また、エンディアンに対処する余地があり、それが問題になる場合もあります。

コンパイラ固有のディレクティブに到達せずにすべてのパディングを削除する方法が1つありますが、フィールド間の相対的な順序が重要でない場合にのみ適用できます。このようなものを考えると:

struct Foo
{
    double x;  // assume 8-byte alignment
    char y;    // assume 1-byte alignment
               // 7 bytes of padding for first field
};

...次のように、これらのフィールドを含む構造体のアドレスに関連するアライメントされたメモリアクセスのためのパディングが必要です。

0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
x_______y.......x_______y.......x_______y.......x_______y.......

... .はパディングを示します。xパフォーマンスのために、すべてが8バイトの境界に合わせて調整する必要があります(場合によっては正しい動作にさえなります)。

SoA(配列の構造)表現を使用して、移植可能な方法でパディングを削除できます(8 Fooつのインスタンスが必要だと仮定しましょう)。

struct Foos
{
   double x[8];
   char y[8];
};

構造を効果的に破壊しました。この場合、メモリ表現は次のようになります。

0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
x_______x_______x_______x_______x_______x_______x_______x_______

... この:

01234567
yyyyyyyy

...パディングのオーバーヘッドはなくなりました。構造アドレスのオフセットとしてこれらのデータフィールドにアクセスするのではなく、実際には配列であるもののベースアドレスのオフセットとしてアクセスするため、ミスアライメントのメモリアクセスは発生しません。

これには、消費するデータが少ないため(マシンの関連するデータ消費率を低下させるための無関係なパディングがなくなります)、コンパイラーが処理を非常に些細な方法でベクトル化できるため、シーケンシャルアクセスが高速になるというボーナスもあります。

欠点は、コーディングするのがPITAであることです。また、AoSまたはAoSoAの担当者がよりうまく処理できるフィールド間の大きなストライドを使用したランダムアクセスでは、潜在的に効率が低下します。しかし、これは、パディングをなくし、すべてを揃えてねじ込むことなく、できる限り緊密に梱包するための1つの標準的な方法です。


2
構造レイアウトを明示的に指定する手段を持つことで、移植性が大幅に向上すると主張します。一部のレイアウトは一部のマシンでは非常に効率的なコードになり、他のマシンでは非常に非効率なコードになりますが、コードはすべてのマシンで機能し、少なくとも一部では効率的です。対照的に、このような機能がない場合、すべてのマシンでコードを機能させる唯一の方法は、すべてのマシンでコードを非効率にするか、マクロと条件付きコンパイルを使用して高速な非移植性を結合することです同じソース内のプログラムと遅いポータブルなもの。
スーパーキャット

概念的には、ビットとバイト表現、アライメント要件、エンディアンなどに至るまですべてを指定でき、Cでそのような明示的な制御を可能にし、オプションで基礎となるアーキテクチャからさらに離すことができる機能があれば... ATM-現在、シリアライザの最もポータブルなソリューションは、正確なビットおよびバイト表現とデータ型のアライメントに依存しないようにシリアライザを記述することです。残念ながら、他の方法では効率的に(Cで)行うATMがありません。

5

すべてのアーキテクチャが同じというわけではなく、1つのモジュールで32ビットオプションをオンにして、同じソースコードと同じコンパイラを使用した場合に何が起こるかを確認してください。バイト順もよく知られている制限です。浮動小数点表現をスローすると、問題が悪化します。Packingを使用してバイナリデータを送信することは移植できません。実際に使用できるように標準化するには、C言語仕様を再定義する必要があります。

一般的ですが、データのセキュリティ、移植性、または寿命が必要な場合は、Packを使用してバイナリデータを送信することはお勧めできません。ソースからプログラムにバイナリblobを読み込む頻度。ハッカーやプログラムの変更がデータを「取得」していないことを確認して、すべての値が正常であることをどのくらいの頻度で確認しますか?チェックルーチンをコーディングするまでに、インポートおよびエクスポートルーチンを使用することもできます。


0

非常に一般的な選択肢は「名前付きパディング」です:

struct s {
  short s1;
  char  c2;
  char  reserved; // Padding
};

これ、構造が8バイトに埋め込まれないことを前提としてます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.