全面解析sizeof(下)

9119 ワード

以下のコード使用プラットフォームはWindows 7 64 bits+VS 2012です.
sizeofは基本データ型に作用し、特定のプラットフォームと特定のコンパイルでは結果が決定され、sizeofを使用して構造型:構造体、連合体、クラスの大きい時間を計算すると、状況は少し複雑になります.
1.sizeof計算構造体
次のコードについて考察します.
struct S1
{
    char c;
    int i;
};
cout<<”sizeof(S1)=”<<sizeof(S1)<<endl;

sizeof(S 1)の結果は8であり,想像していたsizeof(char)+sizeof(int)=5ではない.これは,構造体やクラスメンバー変数が異なるタイプの場合,プロセスメンバー変数の整列が必要であるためであり,コンピュータ構成原理では,整列の目的はアクセス命令周期を低減し,CPUの記憶速度を向上させることであると説明している.
1.1メモリ整列の原則
(1)構造体変数のヘッダアドレスは、その最も広い基本タイプのメンバーのサイズによって除去することができる.
(2)構造体の各メンバーの構造体ヘッダアドレスに対するオフセット量は、メンバーサイズの整数倍であり、必要に応じてコンパイラがメンバー間にバイトを追加する.
(3)構造体の合計サイズは、構造体の最も広い基本タイプのメンバーサイズの整数倍であり、必要に応じてコンパイラが最後のメンバーの後にパディングバイトを追加する.以上の3つのメモリ整列の原則があれば、ネストされた構造体タイプのメモリ整列に簡単に対応できる.以下のようにする.
struct S2
{
    char c1;
    S1 s;
    char c2
};

S 2の最も広い基本データ型を探す場合、ネストされた構造体のメンバーを含め、S 1から最も広い構造体のデータ型がintであることを探すので、S 2の最も広いデータ型はintである.S 1 sの構造体S 2における整列も、前述の3つの基準に従って行われるので、sizeof(S 2)=sizeof(char)+pad(3)+sizeof(S 1)+1+pad(3)=1+3+8+1+3=16バイトであり、pad(3)は3バイトの充填を示す.
構造体のあるメンバーの構造体ヘッダアドレスに対するオフセット量は、マクロoffsetof()によって得ることができ、このマクロもstddefである.hで定義すると、#define offsetof(s,m)(size_t)&(((s*)0)->m)例えば、S 2におけるcのオフセット量を得るには、size_t pos = offsetof(S2, c);//posは4に等しい
1.2前処理コンパイラ指導指令#pragma pack
#pragma pack(n)、nはバイト整列数であり、その値は1、2、4、8、16であり、デフォルトは8であり、最も広いデータ型が8を超えないことを示す.
設定された値nが構造体メンバーのsizeof値より小さい場合、そのメンバーのオフセット量はこの値に準じるべきである.
設定された値nが構造体メンバーの最大値より小さい場合、構造体のヘッダアドレスと合計サイズは、整列時にこの値に準じるべきである.
すなわち、構造体が整列する場合、(1)メンバーのオフセット量は、offsetof(item)=min(n,sizeof(item))のように、メンバー自体のサイズとnの最小値をとるべきである.(2)構造体変数のヘッダアドレスと構造体の最終的な総サイズは、構造体における最も広い基本タイプのメンバーサイズとnの両方の最小値であり、式は同じである.
次のコードについて考察します.
#pragma pack(push) //    pack      
#pragma pack(2) //             
struct S1
{
    char c;
    int i;
};
struct S2
{
    char c1;
    S1 s;
    char c2
};
#pragma pack(pop) //      pack  

//  
#pragma pack(2)
...
#pragma pack()

したがって、sizeof(S 2)=sizeof(char)+pad(1)+sizeof(S 1)+1+pad(1)=1+1+6+1=10バイトとなる.
1.3空の構造体
C/C++では、長さが0のデータ型は存在しない.データ・メンバーを含まないNull Structureのサイズは0ではなく1です.Null Structure変数も保存する必要があります.これにより、コンパイラは1バイトのスペースのみをプレースホルダに割り当てることができます.次のようになります.
struct S3 { };
sizeof( S3 ); //    1

1.4ビットドメイン構造体
一部の情報は、格納時に完全なバイトを占有する必要はなく、数または1つのバイナリビットを占有するだけです.例えば、1つのスイッチング量を格納する場合、0と1の2つの状態だけで、1ビットの2進位でよい.記憶領域を節約し、処理を簡便にするために、C言語はまた、「ビットドメイン」または「ビットセグメント」と呼ばれるデータ構造を提供する.ビットドメイン変数メンバー変数構造体をビットドメイン構造体と呼ぶ.ビットドメイン構造体の定義形式:structビットドメイン構造名{ビットドメインリスト};ビットドメインリストの形式は、タイプ説明子ビットドメイン名:ビットドメイン長です.ビットドメインを使用する主な目的は、(1)隣接するビットドメインフィールドのタイプが同じで、そのビット幅の和がタイプのsizeofサイズより小さい場合、後続のフィールドは前のフィールドに隣接して格納され、収容できないまで格納される.
(2)隣接するビットフィールドのタイプが同じであるが、そのビット幅の和がタイプのsizeofサイズより大きい場合、後続のフィールドは新しいメモリセルから始まり、そのオフセット量はタイプサイズの整数倍となる.
(3)隣接するビットフィールドのタイプが異なる場合、各コンパイラの具体的な実装に差があり、VC 6は非圧縮方式、Dev-C++は圧縮方式をとる.
(4)ビットフィールド間に非ビットフィールドが挿入されている場合、圧縮は行わない.
(5)構造体全体の合計サイズは、最も広いベースタイプのメンバーサイズの整数倍である.
(6)ビットドメインは、ビットドメイン名を持たなくてもよい.この場合、位置の入力や調整にのみ使用される.無名のビットドメインは使用できない.
例:
struct k { 
    int a:1 
    int :2 // 2     
    int b:3 int c:2
 };

次のコードについて考察します.
struct BFS1
{
    char f1 : 3;
    char f2 : 4;
    char f3 : 5;
};
struct BFS2
{
    char f1 : 3;
    int i : 4;
    char f2 : 5;
};
struct BFS3
{
    char f1 : 3;
    char f2;
    char f3 : 5;
};

以上のコードを考察すると,(1)sizeof(BFS 1)=2となる.隣接するビットドメインタイプが異なる場合、そのsizeof(BFS 2)=1+4+1+pad(2)=8はVSにおいて、ビットドメイン変数iのオフセット量が4の倍数である必要はなく、f 1の後バイトに続いて記憶されるが、ビットドメイン構造体BFS 2の合計サイズはsizeof(int)の整数倍である必要がある.Dev-C++ではsizeof(BFS 2)=2であり、隣接するビットフィールドのタイプが異なるため、圧縮メモリが採用されている.(2)sizeof(BFS 3)=3であり、非ビットフィールドが挿入されている場合、圧縮は発生せず、VSおよびDev-C++で得られるサイズはいずれも3である.
2.sizeof計算連合体
構造体はメモリ組織上で順序式であり、連合体は重畳式であり、各メンバーはメモリを共有するため、連合体全体のsizeof、すなわち各メンバーのsizeofの最大値である.構造体のメンバーは、構造タイプであってもよく、ここで、構造タイプメンバーは全体として考慮される.したがって、以下の例では、Uのsizeof値はsizeof(s)に等しい.
union U
{
    int i;
    char c;
    S1 s;
};

3.sizeof計算クラス
次のコードについて考察します.
#include <iostream>
using namespace std;

class Small{};

class LessFunc{
int num;
void func1(){};
};

class MoreFunc{
int num;
void func1(){};
int func2(){return 1;};
};

class NeedAlign{
char c;
double d;
int i;
};

class Virtual{
int num;
virtual void func(){};
};

int main(int argc,char* argv[])
{
    cout<<sizeof(Small)<<endl;   //  1
    cout<<sizeof(LessFunc)<<endl;//  4
    cout<<sizeof(MoreFunc)<<endl;//  4
    cout<<sizeof(NeedAlign)<<endl;//  24
    cout<<sizeof(Virtual)<<endl; //  8
    return 0;
}

注意して、C++の中で類同構造体は本質的な違いがなくて、構造体は同様にメンバー関数、構造関数、解析関数、虚関数と継承を含むことができて、しかし一般的にこのように使用しないで、Cの構造体の使用習慣に沿っています.クラスと構造体の唯一の違いは、構造体のメンバーのデフォルト権限がpublicであり、クラスがprivateであることです.以上の点に基づいて,プログラムからの出力結果を考察すると,(1)クラスは構造体と同様にC++に長さが0のデータ型が存在することは許されず,クラスには何のメンバーもいないが,そのクラスのオブジェクトは1バイトを占有している.(2)クラスのメンバー関数はクラスオブジェクトの占有空間に影響を及ぼさず,クラスオブジェクトのサイズはそのデータメンバーによって決定される.(3)クラスは構造体と同様に整列する必要があり,具体的な整列の規則は前述の構造体のメモリ整列を参照する.(4)クラスに虚関数が含まれている場合、コンパイラは、虚関数テーブルへのポインタをクラスオブジェクトに挿入し、虚関数の動的呼び出しを支援します.したがって、クラスのオブジェクトのサイズは、虚関数が含まれていない場合よりも少なくとも4バイト多くなります.メモリの位置合わせを考慮すると、より多くなります.データ・メンバー間の位置合わせを使用する場合、クラス・オブジェクトに少なくとも1つの数が含まれている場合メンバーによると、虚関数を持つ場合、オブジェクトのサイズは少なくとも8 Bであり、読者は自分で導くことができる.
参考文献
[1]陳剛.C++上級進級教程[M].武漢:武漢大学出版社、2008.[2]http://blog.csdn.net/freefalcon/article/details/54839