CIでのプログラミングでGCCの__attribute__((__packed__))
属性を使用して構造体をパックすることが非常に重要であるとわかったとき、揮発性メモリの構造化チャンクをバイトの配列に簡単に変換して、バス経由で送信したり、ストレージに保存したり、レジスタのブロック。パックされた構造体は、バイトの配列として扱われるときに、パディングを含まないことを保証します。これは、無駄であり、セキュリティ上のリスクであり、ハードウェアとのインターフェース時に互換性がない可能性があります。
すべてのCコンパイラで機能する構造体をパッキングするための標準はありませんか?そうでない場合、私はこれがシステムプログラミングの重要な機能であると考えて異常値を示していますか? C言語の初期のユーザーは、構造体のパッキングの必要性を見つけられなかったか、または何らかの代替手段がありますか?
構造体で重要なのは、各構造体インスタンスのアドレスからの各メンバーのオフセットです。それほど物が詰め込まれているかの問題ではありません。
ただし、配列はどのように「パック」されるかが重要です。 Cの規則では、各配列要素は前の配列から正確にNバイトです。ここで、Nはその型を格納するために使用されるバイト数です。
しかし、構造体があれば、そのような均一性は必要ありません。
以下は、奇妙なパッキングスキームの1つの例です。
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;
};
2つのU24が隣接する32ビットブロックに格納され、U8は最初のU24の前の「穴」に格納されますelementA
。
ただし、必要に応じて、すべてを独自の32ビットブロックにパックするようコンパイラーに指示できます。 RAMの方が高価ですが、アクセスに使用する命令が少なくなります。
「パッキング」は「きっちりとパックする」ことを意味するのではありません-構造体の要素をオフセットで配置するためのスキームを意味するだけです。
一般的なスキームはなく、コンパイラー+アーキテクチャーに依存します。
Cでプログラミングするとき、GCCを使用して構造体をパックすることは非常に重要であることがわかりました
__attribute__((__packed__))
[...]
__attribute__((__packed__))
について言及しているので、struct
内のすべてのパディングを削除することを意図していると思います(各メンバーに1バイトアラインメントを持たせます)。
すべてのCコンパイラで機能する構造体をパッキングするための標準はありませんか?
...そして答えは「いいえ」です。構造体(およびスタックまたはヒープ内の構造体の連続した配列)に関連するパディングとデータ配置は、重要な理由で存在します。多くのマシンでは、アライメントされていないメモリアクセスにより、パフォーマンスが大幅に低下する可能性があります(一部の新しいハードウェアではパフォーマンスが低下します)。まれなケースとして、メモリアクセスの不整合が原因でバスエラーが発生し、回復不可能な場合があります(オペレーティングシステム全体がクラッシュする場合もあります)。
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つの標準的な方法です。
すべてのアーキテクチャが同じというわけではありません。1つのモジュールで32ビットオプションをオンにして、同じソースコードと同じコンパイラを使用するとどうなるかを確認してください。バイトオーダーもよく知られている制限です。浮動小数点表現を投入すると、問題はさらに悪化します。パッキングを使用してバイナリデータを送信することはできません。これを標準化して実際に使用できるようにするには、C言語仕様を再定義する必要があります。
一般的ですが、データのセキュリティ、移植性、または寿命を延ばしたい場合は、パックを使用してバイナリデータを送信することはお勧めできません。ソースからプログラムにバイナリBLOBを読み取る頻度。ハッカーやプログラムの変更がデータに「到達」していないことを、すべての値が正気であるかをどのくらいの頻度で確認しますか?チェックルーチンをコーディングしたときには、インポートルーチンとエクスポートルーチンも使用している可能性があります。
非常に一般的な代替方法は、「名前付きパディング」です。
struct s {
short s1;
char c2;
char reserved; // Padding
};
これはdoes構造が8バイトに埋め込まれないことを前提としています。