構造体の4バイトの位置合わせの詳細

4826 ワード

4バイト整列のルール
C++の構造体変数の格納にはなぜ4バイト整列のルールがあるのか、ここでは32ビットマシンでCPUがメモリデータを読み出すときに4バイト整列がより速い速度を得ると仮定する.これは,1バイト8ビット,4バイトが32ビット,32ビットマシンのレジスタ,アドレスなどが32ビットで,ちょうど1回の処理で完了するためである.
二関連内容の解釈
たとえば、次の構造の各メンバー空間の割り当て状況です.
struct test
{
     char x1;
     short x2;
     float x3;
     char x4;
};

「構造の1番目のメンバーx 1は、オフセットアドレスが0で1番目のバイトを占めている.2番目のメンバーx 2はshortタイプであり、その開始アドレスは2バイトの境界を持たなければならないため、コンパイラはx 2とx 1の間に空のバイトを埋めた.構造の3番目のメンバーx 3と4番目のメンバーx 4は、その自然な境界アドレスに適切に落ちており、それらの前に追加の充填バイトは必要ない.test構造では、メンバーx 3は4バイトの境界を要求し、その構造のすべてのメンバーに要求される最大の境界単位であるため、test構造の自然な境界条件は4バイトであり、コンパイラはメンバーx 4の後ろに3つの空のバイトを埋め込んでいる.構造全体が占める空間は12バイトである.Cコンパイラのデフォルトのバイト位置合わせを変更します.」
1、この意味は、前の2つのメンバーがそれぞれ2バイトを占め、後の2つのメンバーがそれぞれ4バイトを占め、言い換えれば、各メンバーが占めるバイト数は、それに隣接する前のメンバータイプと関連している.
2、__attribute__ ((packed))、構造のコンパイル中の最適化位置合わせを解除し、実際の占有バイト数で位置合わせします.このような方法で作った欠点は何ですか.以前はコンピューティングリソースが限られていたので、このバイトを揃えることでリソースを節約していましたが、今はハードウェアリソースが十分なので、この問題をあまり考えなくてもいいのではないでしょうか.
3、#pragma pack(1)//1バイトで揃える
struct TCPHEADER
{
     short SrcPort; // 16     
     short DstPort; // 16      
     int SerialNo; // 32    
     int AckNo; // 32    
     unsigned char HaderLen : 4; // 4     
     unsigned char Reserved1 : 4; //   6   4 
     unsigned char Reserved2 : 2; //   6   2 
     unsigned char URG : 1;
     unsigned char ACK : 1;
     unsigned char PSH : 1;
     unsigned char RST : 1;
     unsigned char SYN : 1;
     unsigned char FIN : 1;
     short WindowSize; // 16     
     short TcpChkSum; // 16 TCP   
     short UrgentPointer; // 16     
};
#pragma pack() //   1      
このcodeは、すべてのメンバーが1バイトで整列していることを示していますか?それはshortタイプのようで、最初のバイトの値だけを取って、後の3つは無視しますか?
4、各データ型は下位が上位ですか、それとも上位ですか.どう判断しますか?OSまたはその他の条件によって?
バイト整列は異なるコンパイラの下で文法が異なり、GCCでは#pragma push(1)#pragma pack();MS C++でVCのコード項目で調整可能で、デフォルトは8バイトです.
typedef struct 
{
   char c;
   int  i;
}test;

バイトアライメントは、アライメントです.例えば、charとintが4バイトアライメントであれば、charも4バイトを占有し、合計8バイトを占め、構造体オブジェクトのストレージは順番に保存されます.charはintの前にあるに違いありません.第2のケースでは、1バイトが整列している場合、charは1バイトしか占めていないことを意味し、結合intは4バイトを占有し、このNバイトが整列しているのは、各メンバーの占有空間がNバイトの倍数でなければならず、Nバイト未満の占有Nバイトであることを意味する.では、1バイトで整列すると、5バイトかかります.
また、各データが下位であるか、上位であるかは、このルートプロセッサに関係しており、Intel処理は小端整列である.例えば、整数522387969を16進数で表すと、0 x 1 f 23 02 01、Intelプロセッサでは0 x 01 02 23 1 fと表すので、メモリでは0 x 01 02 03 1 fで522387969を示す.これはいわゆる小端整列である.しかしarmプロセッサでは522387969は0 x 1 f 23 02 01であり、これがいわゆる大端整列であり、この方式はネットワークバイトシーケンスとも呼ばれる.
三実践例分析
例1:
	struct A
	{
		bool a1;
		bool a2;
		int a;
		bool a3;
	};

	struct B
	{
		bool a1;
		bool a2;
		bool a3;
		int a;
	};

	struct C
	{
		bool a1;		
		int a;
		bool a2;
		bool a3;
	};

	bool t;

	printf("Sizeof(bool) = %d,Sizeof(A) = %d,Sizeof(B) = %d,Sizeof(C) = %d
",sizeof(t),sizeof(A),sizeof(B),sizeof(C));
回答:1,12,8,12
例2:
        struct s1
	{
		int m1;

		char m2;

		char m3;
	};

	struct s2
	{
		char m2;

		int m1;

		char m3;
	};

	struct s3
	{
		char m2;

		char m3;

		int m1;
	};

	printf("Sizeof(s1) = %d,Sizeof(s2) = %d,Sizeof(s3) = %d
",sizeof(s1),sizeof(s2),sizeof(s3));
回答:8,12,8
よんそうかんぶんせき
現代のコンピュータではメモリ空間はbyteによって区分されており、理論的には任意のタイプの変数へのアクセスは任意のアドレスから開始できるようだが、実際には特定のタイプの変数にアクセスする際に特定のタイプのメモリアドレスにアクセスすることがよくあり、これは様々なタイプのデータが一定の規則に従って空間に並ぶ必要がある.順番の次から次への排出ではなく、これが整列です.
各ハードウェアプラットフォームは、ストレージスペースの処理に大きな違いがあります.一部のプラットフォームでは、特定のタイプのデータに対して、特定のアドレスからのみアクセスできます.例えば、一部のアーキテクチャのCPUは、整列を行う変数にアクセスする際にエラーが発生する場合、このようなアーキテクチャの下でプログラミングするにはバイト整列を保証しなければならない.他のプラットフォームではこのような状況はないかもしれませんが、最も一般的なのは、プラットフォームに適した要件に従ってデータの保存を整列しないと、アクセス効率に損失をもたらすことです.例えば、一部のプラットフォームでは、毎回偶数アドレスから始まるプラットフォームがあり、int型(32ビットシステムと仮定)が偶数アドレスから始まる場所に格納されている場合、この32 bitは1リードサイクルで読み出すことができ、奇数アドレスから始まる場所に格納されている場合、2リードサイクルが必要であり、2回読み出した結果の高低バイトをパッチワークして32 bitデータを得ることができる.
実際には、バイト整列の詳細は、特定のコンパイラ実装と関連しているが、一般的には、3つの準則を満たす:1)構造体変数のヘッダアドレスは、その最も広い基本タイプのメンバーのサイズによって除去することができる;2)構造体の各メンバーの構造体ヘッダアドレスに対するオフセット量は、メンバーサイズの整数倍であり、必要に応じてコンパイラがメンバー間にバイトを追加する.例えば、上記の2番目の構造体変数のアドレス空間.3)構造体の合計サイズは、構造体の最も広い基本タイプのメンバーサイズの整数倍であり、必要に応じてコンパイラは最後のメンバーの後にパディングバイトを加算します.
五小結と悟り
バイト整列を行う理由は、プロセッサがメモリにアクセスするときに1バイト1バイトではなく、通常、4バイトなどの複数のバイトに一度にアクセスするからです.したがって、データを4バイト整列で格納することで、データアクセス効率を向上させることができる(具体的な格納と整列方式はそれほど簡単ではないのは言うまでもない).しかし、このようなデフォルトの最適化は私たちが望んでいるものではない場合があります.例えば、ネットワークプログラムを設計する際、一般的にアプリケーションプロトコルのプロトコルヘッダを表す構造体を定義し、通信双方のプログラムが異なるアーキテクチャのコンピュータでコンパイルされている場合(これは可能性が高い)、デフォルトの位置合わせ方式が異なる可能性があり、解析されたプロトコルヘッダは必然的に間違っている.また、ラッキーな位置合わせのように、プロトコルヘッダに関係のないバイトをいくつか挿入しても優雅ではありません.まして帯域幅を占有しています.
幸いなことに、この位置合わせは制御できます.一般的に、デフォルトの位置合わせは次の方法で変更できます.
・擬似命令#pragma pack(n)を使用すると、コンパイラはnバイトで整列する.
・擬似命令#pragma pack()を使用して、カスタムバイト整列方式をキャンセルします. 
注意:pragma pack(n)で指定したnが構造体の最大メンバーのsizeより大きい場合、構造体は機能せず、sizeの最大メンバーに従って境界を合わせます.