7

TCP ヘッダーを構造体として表す C++ プログラムがあります。

#include "stdafx.h"

/*  TCP HEADER

    0                   1                   2                   3   
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |          Source Port          |       Destination Port        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                        Sequence Number                        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                    Acknowledgment Number                      |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |  Data |           |U|A|P|R|S|F|                               |
   | Offset| Reserved  |R|C|S|S|Y|I|            Window             |
   |       |           |G|K|H|T|N|N|                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |           Checksum            |         Urgent Pointer        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                    Options                    |    Padding    |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                             data                              |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

*/

typedef struct {        // RFC793
    WORD         wSourcePort;
    WORD         wDestPort;
    DWORD        dwSequence;
    DWORD        dwAcknowledgment;
    unsigned int byReserved1:4;
    unsigned int byDataOffset:4;
    unsigned int fFIN:1;
    unsigned int fSYN:1;
    unsigned int fRST:1;
    unsigned int fPSH:1;
    unsigned int fACK:1;
    unsigned int fURG:1;
    unsigned int byReserved2:2;
    unsigned short wWindow;
    WORD         wChecksum;
    WORD         wUrgentPointer;
} TCP_HEADER, *PTCP_HEADER;


int _tmain(int argc, _TCHAR* argv[])
{
    printf("TCP header length: %d\n", sizeof(TCP_HEADER));
    return 0;
}

このプログラムを実行すると、このヘッダーのサイズが 24 バイトになり、期待していたサイズではありません。フィールド "wWindow" の型を unsigned short と同じビット数を持つ "unsigned int wWindow:16" に変更すると、プログラムは構造体のサイズが 20 バイトになり、正しいサイズであると通知します。どうしてこれなの?

32 ビット x86 マシンで Microsoft Visual Studio 2005 SP1 を使用しています。

4

9 に答える 9

6

コンパイラがビットフィールドを 16 ビット エンティティではなく 32 ビット int にパックしているためです。

一般に、フィールド内の「サブフィールド」にアクセスするには、ビットフィールドを避け、明示的なビット マスキングとシフトで他のマニフェスト定数 (列挙型など) を使用する必要があります。

ビットフィールドを避けるべき理由の 1 つは、同じプラットフォームであっても、コンパイラ間での移植性があまり高くないことです。C99標準から(C90標準にも同様の文言があります):

実装では、ビットフィールドを保持するのに十分な大きさのアドレス指定可能なストレージ ユニットを割り当てることができます。十分なスペースが残っている場合、構造内の別のビットフィールドの直後に続くビットフィールドは、同じユニットの隣接するビットにパックされます。十分なスペースが残っていない場合、収まらないビットフィールドが次のユニットに配置されるか、隣接するユニットとオーバーラップするかは実装定義です。ユニット内のビットフィールドの割り当て順序 (上位から下位、または下位から上位) は実装定義です。アドレス可能なストレージ ユニットのアラインメントは指定されていません。

ビット フィールドが int 境界に「またがる」かどうかを保証することはできません。また、ビット フィールドが int の下限または上限のどちらから始まるかを指定することもできません (これは、プロセッサがビッグエンディアンまたはリトルエンディアン)。

于 2008-09-29T18:24:50.347 に答える
4

一連の「unsigned int:xx」ビットフィールドは、int の 32 ビットのうち 16 ビットしか使用しません。残りの 16 ビット (2 バイト) は存在しますが、未使用です。これに続いて、int 境界にある unsigned short と、int 境界に沿って配置された WORD が続きます。これは、それらの間に 2 バイトのパディングがあることを意味します。

「unsigned int wWindow:16」に切り替えると、別の short ではなく、コンパイラは前のビットフィールドの未使用部分を使用するため、無駄も short も short の後のパディングもなくなるため、4 バイトが節約されます。

于 2008-09-29T19:20:22.943 に答える
2

この質問を参照してください:構造体の sizeof が各メンバーの sizeof の合計と等しくないのはなぜですか? .

「unsigned int wWindow:16」構文を使用すると、コンパイラはパディングを無効にするヒントを得ると思います。

また、ショートは 16 ビットであるとは限りません。保証は次のとおりです。16 ビット <= short のサイズ <= int のサイズ。

于 2008-09-29T18:28:36.500 に答える
0

マイク B は正しかったと思いますが、完全に明確ではありません。「ショート」でお願いすると、32bit境界で揃えられます。int:16 を要求すると、そうではありません。したがって、int:16 は ebit フィールドの直後に収まりますが、short は 2 バイトをスキップして次の 32 ビット ブロックから開始します。

彼が言っていることの残りの部分は完全に当​​てはまります。ビットフィールドは、外部から見える構造をコーディングするために決して使用してはなりません。それらがどのように割り当てられるかについて保証がないからです。せいぜい、バイトを節約することが重要な組み込みプログラムに属します。さらに、それらを使用してメモリ マップ ポートのビットを実際に制御することはできません。

于 2008-09-29T20:56:50.340 に答える
0

コンパイラは、ビットフィールド以外の構造体メンバを 32 ビットにパディングしています -- ネイティブ ワード アラインメント。これを修正するには、構造体の前に #pragma pack(0) を実行し、その後に #pragma pack() を実行します。

于 2008-09-29T18:24:50.597 に答える
0

メモリ内の構造体境界は、フィールドのサイズと順序に応じて、コンパイラによってパディングされる場合があります。

于 2008-09-29T18:25:08.643 に答える
0

パッキングに関しては、C/C++ の専門家ではありません。しかし、仕様には、非ビットフィールドがビットフィールドに続く場合、残りのスペースに収まるかどうかに関係なく、ワード境界に配置する必要があるという規則があると思います。明示的なビットベクトルにすることで、この問題を回避できます。

繰り返しますが、これは経験に基づいた憶測です。

于 2008-09-29T18:27:42.413 に答える
0

興味深い - 「WORD」は「unsigned short」と評価されると思うので、複数の場所でその問題が発生します。

また、8 ビットを超える値のエンディアンの問題に対処する必要があることに注意してください。

于 2008-09-29T18:29:13.190 に答える
0

コンパイラのパッキング ルールにより、異なる値が表示されます。Visual Studio 固有のルールはこちらで確認できます

構造体をパックする必要がある場合 (または特定の配置要件に従う必要がある場合)、#pragma pack() オプションを使用する必要があります。コードでは、すべての構造体メンバーをバイト境界に揃える #pragma pack(0) を使用できます。その後、 #pragma pack() を使用して、構造体のパッキングをデフォルトの状態にリセットできます。pack プラグマの詳細については、こちらを参照してください

于 2008-09-29T18:36:13.627 に答える