メモリ整列規則の私見

4809 ワード

メモリ整列規則の私見
2017年03月23日木曜日、武漢で発表
もしあなたが本文に対してどんな提案あるいは疑問があるならば、ここで私にIssuesを言うことができて、ありがとうございます!:)
メモリの位置合わせの原因と理由は言うまでもなく、主に性能とプラットフォームの移植などの要素のために、コンパイラはデータ構造に対してメモリの位置合わせを行った.
次の例を考慮します.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include
using namespace std;
struct A{
    char a;
    int b;
    short c;
};

struct B{
    short c;
    char a;
    int b;
};
int main(){
    cout< 

上の2つの構造体AとBのメンバー変数のタイプは同じですが、メモリ領域のサイズ(単位:バイト)は異なります.
​ sizeof(A) = 12
​ sizeof(B) = 8
この現象の原因を分析するために、メモリの位置合わせの3つのルールに言及しなければなりません.
  • 構造体の各メンバーについて、最初のメンバーのオフセット量は0であり、後ろに並ぶメンバーの現在のオフセット量は、現在のメンバータイプの整数倍
  • である必要がある.
  • 構造体内のすべてのデータメンバーがそれぞれメモリを揃えた後、構造体自体はメモリの整列を一度行い、構造体全体のメモリ占有サイズが構造体内の最大データメンバーの最小整数倍であることを保証する
  • .
  • プログラムにpragma pack(n)プリコンパイル命令がある場合、すべてのメンバーはnバイト(すなわちオフセット量がnの整数倍)に準拠し、現在のタイプおよび最大構造体内タイプ
  • を考慮する.
    上記構造体Aを例にとると、1番目のメンバaはcharタイプ、占有1バイト空間、オフセット量0、2番目のメンバbはintタイプ、占有4バイト空間、ルール1、bのオフセット量はintタイプの整数倍でなければならないので、コンパイラはa変数の後に3バイトバッファを挿入し、このときbのオフセット量(4バイト)がbタイプの整数倍(現在はちょうど1倍)であり、3番目のメンバーcがshortタイプであることを保証する.このときcのオフセット量はちょうど4+4=8バイトであり、すでにshortタイプの整数倍であるため、bとcの間にバッファバイトを充填する必要はない.ただし、この場合、構造体Aの大きさは8+2=10バイトである、ルール2に従って、構造体Aの大きさはその最大メンバータイプintの整数倍でなければならないので、10バイトの上にさらに2バイトを埋め込み、最後の構造体の大きさが12であることを保証し、ルール2に合致する.
    データ・メンバー-
    -前面オフセット-
    -メンバーの自己占有
    (char) a
    0
    1
    バッファ補完
    1
    3(ルール1)
    (int) b
    4
    4
    (short) c
    8
    2
    バッファ補完
    10
    2(ルール2)
    同様に、構造体Bメンバーの分析は以下の通りである.
    データ・メンバー-
    -前面オフセット-
    -メンバーの自己占有
    short c
    0
    2
    char a
    2
    1
    バッファ補完
    3
    1(ルール1)
    int b
    4
    4
    さらに複雑な例は次のとおりです.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    struct BU
    {
        int number;             //4  
        union UBffer
        {
            char buffer[13];    //  3  ,    16    
            int number;
        }ubuf;
        int aa;                 // 4    ,         20
        double dou;             // 8    
    }bu;
    

    sizeof(BU)=4+13+3(補完)+4+8=32と分析方法は似ていますが、aaのオフセット量を計算する際にはintタイプの整数倍であることは間違いありません.バッファ補正を一切しない場合、number+buffer=17バイトで、ルール1に合致するために3バイトを入力する必要があります.
    構造体BUがaaとdouのメンバー順を少し変えると、結果は大きく異なる.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    struct BC
    {
        int number;             //4  
        union UBffer
        {
            char buffer[13];    //  7  ,    20    
            int number;
        }ubuf;
        double dou;             // 8    ,         24
        int aa;                 // 4    ,      36  ,  double  ,       2  
    }bu;
    

    このときsizeof(BC)=4+13+7(ルール1補完)+8+4+4(ルール2補完)=40(8の整数倍)
    次の例を考慮すると、構造体クラスにunionタイプのメンバーが含まれていることに疑問を抱く可能性があります.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    struct BD
    {
        short number;
        union UBffer
        {
            char buffer[13];
            int number;
        }ubuf;
    }bc;
    

    実行結果はsizeof(BD)=2+2+13+3=20で、なぜ2+13+1=16ではないのかと聞かれるかもしれませんが、これはunionタイプが特殊なため、unionメンバーのオフセット量を計算する際にunion内部の最大メンバータイプに基づいてバッファ補正を行う必要があるため、オフセット量がunion最大メンバーintタイプの整数倍であることを保証するために、number(shortタイプ)の後に2バイトを入力する必要がありますが、前の例ではnumberがintタイプなので、この必要はありません.
    たとえば、
    1
    2
    3
    4
    5
    6
    7
    8
    9
    struct BE
    {
        short number;
        union UBffer
        {
            char buffer[13];
            double number;
        }ubuf;
    }bc;
    

    その実行結果はsizeof(BE)=2+6+13+3=24であり,numberの後にdoubleタイプと整列するために6バイトが整列し,最後にルール2に従って3バイトが整列した.
    考慮ルール3:
    例として、#pragma pack(1)の場合、1バイトで整列する場合が最も簡単な場合であり、構造体サイズはすべてのメンバーのタイプサイズの和である.したがって、sizeof(BU)=sizeof(BC)=29となり、メンバー変数の順序は関係なくなります.他の指定されたバイトの位置合わせも分析しやすい.一般に、奇数バイトの位置合わせは意味がなく、通常、コンパイラがメモリの位置合わせを行うことには関心がありません.
    上の例をすべて理解してから、メモリの位置合わせのルールは胸にはっきりしているはずです.:)
    PS:C言語のoffsetof()関数は、特定の構造体メンバーの構造体におけるオフセット量を表示するために使用でき、プログラミング時に上記の説明を検証するために使用できます.実装は次のようになります.
    1
    #define offsetof(type, member) (size_t)&(((type *)0)->member)
    

    原理は、構造体(タイプtype)の開始アドレスを0に強制し、そのメンバーのアドレスを出力することであり、このアドレスの大きさは、構造体におけるメンバーのオフセット量である.
    ルール以外の例
    C 99ではフレキシブル配列機構が定義されているので、1つの構造体について、最後のメンバーが配列である場合、構造体の大きさはそのメンバーがフレキシブル配列であるかどうかと密接に関係している.
    1
    2
    3
    4
    5
    struct sds{
        unsigned int len;
        unsigned int free;
        char buf[0];// char buf[]
    };
    

    構造体定義で最後のメンバーが配列であり、配列サイズが0またはマークされていない場合、このメンバー配列はフレキシブル配列であり、構造体サイズは計上されないためsizeof(sds)=8
    一方、次の構造体sdのsizeof(sd)=12は、最後の配列メンバーが通常の配列であるため、上記の補完規則に適用される.
    1
    2
    3
    4
    5
    struct sds{
        unsigned int len;
        unsigned int free;
        char buf[1];
    };
    

    C++はCと互換性を持つためにstructキーワードを保持していることを知っていますが、実際にはC++のstructはデフォルトのアクセス制御権限publicのclassです.C++標準では、1つの空のクラスのサイズは1バイトであるため、C++ではsizeof(空のクラスまたは空の構造体)=1、C言語ではsizeof(空の構造体)=0となる.