C / C ++構造にドットを付けましょう

私は最近、C / C ++構造-structに精通しました。 主よ、はい、「彼らと知り合うことは何ですか」とあなたは言いますか? したがって、あなたはすぐに2つの間違いを犯します。 第一に、私は主はありません。第二に、私はその構造も考えました。 しかし、結局のところ-いいえ。 一部の読者を1時間ごとのデバッグから救ういくつかの重要な詳細について説明します...




メモリ内のフィールドの配置


構造に注意してください:

struct Foo { char ch; int value; }; 

さて、まず第一に、メモリ内のこの構造のサイズは何ですか? sizeof(Foo)
メモリ内のこの構造のサイズは、コンパイラの設定とコード内のディレクティブに依存します...

一般的に、フィールドはメモリ内で、それ自体のサイズの倍数である境界に沿って整列されます。 つまり、1バイトのフィールドは位置合わせされず、2バイトのフィールドは偶数位置に位置合わせされ、4バイトのフィールドは4の倍数などになります。 ほとんどの場合(または今日のように)、メモリ内の構造体のサイズのアライメントは4バイトです。 したがって、 sizeof(Foo) == 8です。 余分な3バイトがどこにどのように残るか? わからない場合、あなたは推測することはありません...


次に、次の構造のメモリ内の配置を見てみましょう。

 struct Foo { char ch; short id; int value; }; 

次のようになります。


つまり、4バイト単位で整列させることができるもの-強打(メモリ内の構造体のサイズを増やすことなく)で押し込み、別のフィールドを追加します。

 struct Foo { char ch; short id; short opt; int value; }; 

メモリ内のフィールドの配置を見てみましょう。


これはどれほど悲しいことでしょうが、コードから直接これに対処する方法があります:

 #pragma pack(push, 1) struct Foo { // ... }; #pragma pack(pop) 

アライメントサイズを1バイトに設定し、構造を記述して、前の設定に戻しました。 前の設定に戻る-強くお勧めします。 そうしないと、すべてがひどく終了する可能性があります。 私はかつてこれを持っていました-Qtが落ちました。 どこかで彼は.h-nickの下に.h-nickを隠した...

ビットフィールド


コメントで、彼らは私に、標準による構造のビットフィールドは「実装定義」であると指摘しました-したがって、それらの使用を避ける方が良いですが、私にとって誘惑は大きすぎます...

心に不安を感じるわけではありませんが、一般的に、コードでマスクやシフトを使用したビットフィールドの塗りつぶしを見ると気分が悪くなります。たとえば、次のようになります。

 unsigned field = 0x00530000; // ... field &= 0xFFFF00FF; field |= (id) << 8; // ... field &= 0xFFFFFF83; field |= (proto) << 2; 

このような悲しみやエラー、それらのデバッグの匂いはすべて、私はすぐに片頭痛を感じます! そして、彼らはカーテンの後ろから出てきます-ビットフィールド。 最も驚くべきこと-彼らはまだC言語でしたが、私が尋ねる人は誰でも-誰もが初めてそれらについて聞いています。 この無法状態を修正する必要があります。 次に、すべてのリンク、または少なくともこの記事へのリンクを提供します。

このコードはどうですか?

 #pragma pack(push,1) struct IpHeader { uint8_t header_length:4; uint8_t version:4; uint8_t type_of_service; uint16_t total_length; uint16_t identificator; // Flags uint8_t _reserved:1; uint8_t dont_fragment:1; uint8_t more_fragments:1; uint8_t fragment_offset_part1:5; uint8_t fragment_offset_part2; uint8_t time_to_live; uint8_t protocol; uint16_t checksum; // ... }; #pragma pack(pop) 

そして、C / C ++のフィールドを常に操作するので、コードでフィールドを操作できます。 すべてがシフトなどで機能します。 コンパイラを引き継ぎます。 もちろん、いくつかの制限があります... 1つの物理フィールドに関連する行に複数のビットフィールドをリストする場合(ビットフィールド名の左側にあるタイプを意味します)-フィールドの末尾までのすべてのビットの名前を指定します。そうしないと、これらのビットにアクセスできませんつまり、コードになります。

 #pragma pack(push,1) stuct MyBitStruct { uint16_t a:4; uint16_t b:4; uint16_t c; }; #pragma pack(pop) 

結果の構造は4バイトです! 最初のバイトの2つの半分は、フィールドabです。 2番目のバイトは名前では使用できず、最後の2バイトは名前c使用できます。 これは非常に危険な瞬間です。 ビットフィールドで構造を記述した後、必ずそのsizeof確認してください!

また、バイトにビットペインが配置される順序は、バイトの順序によって異なります。 LITTLE_ENDIANの順序では、ビットフィールドは最初のバイトから分散され、BIG_ENDIANは反対になります...

バイトオーダー


また、C ++コードのhtons()ntohs()htonl()nthol()の呼び出しによって、コードが悲しくなりました。 Cでは、これはまだ有効ですが、C ++では無効です。 私はこれに耐えることはありません! 以下のすべてがC ++に適用されます!

さてここで簡単に説明します。 以前の記事の1つで、バイトオーダーの処理方法を既に書いています。 外部で数値として機能する構造を記述することができ、内部ではそれ自体がバイト単位で格納順序を決定します。 したがって、IPヘッダー構造は次のようになります。

 #pragma pack(push,1) struct IpHeader { uint8_t header_length:4; uint8_t version:4; uint8_t type_of_service; u16be total_length; u16be identificator; // Flags uint8_t _reserved:1; uint8_t dont_fragment:1; uint8_t more_fragments:1; uint8_t fragment_offset_part1:5; uint8_t fragment_offset_part2; uint8_t time_to_live; uint8_t protocol; u16be checksum; // ... }; #pragma pack(pop) 

2バイトフィールドのタイプu16be注意してu16be 。 これで、構造フィールドはバイト変換を必要としません。 fragment_offsetはまだ問題がありますが、問題のない人は誰でも。 それでも、この不名誉を隠し、一度テストして、すべてのコードで安全に使用するテンプレートを作成することもできます。

「C ++言語は、単純に記述できるほど複雑です」 ©奇妙なことに-I

Z.Y. 次のいずれかの記事で、TCP / IPスタックのプロトコルヘッダーを操作するための理想的な構造を自分の観点からレイアウトすることを計画しています。 思いとどまる-手遅れになる前に!

Source: https://habr.com/ru/post/J142662/


All Articles