『Cプログラミング診断室』目次次(第5章 管理は複雑に 本当の恐怖)

第5章 管理は複雑に

バイト・オフセット


構造体をいっぱい使いだすと、構造体の各メンバーが、構造体の先頭から何 バイト離れているか(バイトオフセット)が欲しくなることがよくあります。 このとき、どのように苦労してバイトオフセットを求めたかを示す絶好のプロ グラム例があります。exdef.h(リスト5−10)が構造体宣言で、exdef2.h (リスト5−11)がバイトオフセットをマクロに求めるものです。実際のファ イルは構造体の宣言が延々と並んでいるので、先頭の2つの構造体の部分まで を示します。

構造体のメンバーの名前が全部大文字になっているのは、マクロみたいで気 味が悪いですね。できれば小文字で、もっと長い名前にしたいものです。まあ、 今回はその辺りの書き方には目をつむることにします。

リスト5−10 構造体の宣言

     1  /*------------------------------------------------------*/
     2  /* (1) CAD SYSTEM INTERFACE STRUCTURE  TYPE DEFINITION  */
     3  /* (2) CAD EXTERNAL COMMAND (FUNCTION CODE) DEFINITION  */
     4  /*------------------------------------------------------*/
     5  
     6  /* (1) */
     7  /* DATA STRUCTURE TYPE DEFINITION */
     8  
     9  struct  PATR_DEF{
    10          short   ID;             /* 1. ID CODE           */
    11          short   SID;            /* 2. ID SUBCODE        */
    12          short   LAY;            /* 3. LAYER             */
    13          short   COL;            /* 4. COLOR CODE        */
    14          short   LTP;            /* 5. LINE TYPE         */
    15          short   TTP;            /* 6. SENTAN TYPE       */
    16          short   CTYF;           /* 7. MOJI T/Y FLAG     */
    17          short   CARF;           /* 8. MOJI S/Z FLAG     */
    18          short   TAGS;           /* 9. TAGSTRING LENGTH  */
    19          short   MSLN;           /* 10. MOJI LENGTH      */
    20  };
    21  
    22  struct  OATR_DEF{
    23          short   CRVF;           /* 1. moji r/v flag     */
    24          short   CBCF;           /* 2. moji b/c flag     */
    25          unsigned short LDSP[17];/* 3. display layer     */
    26          unsigned short LACT[17];/* 4. active layer      */
    27          short   LINP;           /* 5. input layer       */
    28          short   COL;            /* 6. color             */
    29          short   LTP;            /* 7. line type         */
    30          short   TTP;            /* 8. sentan type       */
    31          double  DARA;           /* 9. detect range      */
    32          short   PTGN;           /* 10. yoso tag         */
    33          short   GTGN;           /* 11. group tag        */
    34          short   *PTGS_P;        /* 12. yoso tagstring   */
    35          short   *GTGS_P;        /* 13. group tagstring  */
    36          short   CTYF;           /* 14. moji t/y flag    */
    37          short   CARF;           /* 15. moji s/z flag    */
    38          double  CH;             /* 16. moji takasa      */
    39          double  CD;             /* 17. moji kankaku     */
    40          double  CT;             /* 18. moji katamuki    */
    41  };
    42  
    43  /*--------- 以下延々と続くが、ここまでで省略 -----------*/

リスト5−11 バイトオフセットの求め方

     1  #define adrlngth        4
     2  
     3  /*** PATR_DEF ***/
     4  #define PATR_ID      0                          /* 1. ID CODE           */
     5  #define PATR_SID     PATR_ID+sizeof(short)      /* 2. ID SUBCODE        */
     6  #define PATR_LAY     PATR_SID+sizeof(short)     /* 3. LAYER             */
     7  #define PATR_COL     PATR_LAY+sizeof(short)     /* 4. COLOR CODE        */
     8  #define PATR_LTP     PATR_COL+sizeof(short)     /* 5. LINE TYPE         */
     9  #define PATR_TTP     PATR_LTP+sizeof(short)     /* 6. SENTAN TYPE       */
    10  #define PATR_CTYF    PATR_TTP+sizeof(short)     /* 7. MOJI T/Y FLAG     */
    11  #define PATR_CARF    PATR_CTYF+sizeof(short)    /* 8. MOJI S/Z FLAG     */
    12  #define PATR_TAGS    PATR_CARF+sizeof(short)    /* 9. TAGSTRING LENGTH  */
    13  #define PATR_MSLN    PATR_TAGS+sizeof(short)    /* 10. MOJI LENGTH      */
    14  
    15  /*** OATR_DEF ***/
    16  #define OATR_CRVF    0                          /* 1. moji r/v flag     */
    17  #define OATR_CBCF    OATR_CRVF+sizeof(short)    /* 2. moji b/c flag     */
    18  #define OATR_LDSP    OATR_CBCF+sizeof(short)    /* 3. display layer     */
    19  #define OATR_LACT    OATR_LDSP+sizeof(short)*17 /* 4. active layer      */
    20  #define OATR_LINP    OATR_LACT+sizeof(short)*17 /* 5. input layer       */
    21  #define OATR_COL     OATR_LINP+sizeof(short)    /* 6. color             */
    22  #define OATR_LTP     OATR_COL+sizeof(short)     /* 7. line type         */
    23  #define OATR_TTP     OATR_LTP+sizeof(short)     /* 8. sentan type       */
    24  #define OATR_DARA    OATR_TTP+sizeof(short)     /* 9. detect range      */
    25  #define OATR_PTGN    OATR_DARA+sizeof(double)   /* 10. yoso tag         */
    26  #define OATR_GTGN    OATR_PTGN+sizeof(short)    /* 11. group tag        */
    27  #define OATR_P_P     OATR_GTGN+sizeof(short)    /* 12. yoso tagstring   */
    28  #define OATR_G_P     OATR_P_P+adrlngth          /* 13. group tagstring  */
    29  #define OATR_CTYF    OATR_G_P+adrlngth          /* 14. moji t/y flag    */
    30  #define OATR_CARF    OATR_CTYF+sizeof(short)    /* 15. moji s/z flag    */
    31  #define OATR_CH      OATR_CARF+sizeof(short)    /* 16. moji takasa      */
    32  #define OATR_CD      OATR_CH+sizeof(double)     /* 17. moji kankaku     */
    33  #define OATR_CT      OATR_CD+sizeof(double)     /* 18. moji katamuki    */
    34  
    35  /*----------------- 以下延々と続くが、ここまでで省略 -------------------*/

■加算により求める■

さて、構造体 PATR_DEF のメンバーのバイトオフセットを調べていきましょう。各バイトオフセッ トは、exdef2.hのファイルで、PATR_で始まるマクロに定義されます。先頭のメンバーのオフセッ トは当然0なので、
    #define PATR_ID   0
としています。確かに、たいていマシンでは、構造体の先頭メンバーのバイト・オフセットは0で すが、Cの言語仕様には明示されていないようなので、疑惑がわずかに残ります。

2番目のメンバーに対しては、1番目のメンバーのサイズ分を加えるため、

    #define PATR_SID  PATR_ID+sizeof(short)
で、PATR_IDに直前のメンバーのサイズをsizeof演算子で求めて加算しています。以下同様です。

2番目の構造体OATR_TBLに対しても同様です。配列に対しては、配列の要素数分だけ掛けていま す。

直前がポインタの場合である、GTGS_Pのバイトオフセットは、

  
    #define OATR_G_P   OATR_P_P+adrlngth
により、直前のメンバーのバイトオフセットにadrlngthを加えています。adrlngth自体は、ファイ ルの先頭で4にdefineされています。これは、ポインタのサイズが4バイトであることを示してい ます。やはり、定数を使うのは良くないので、
    #define  OATR_G_P  OATR_P_P+sizeof(short*)
とするのはどうでしょうか。こう書けば、ポインタが何バイトのマシンでも動くはずです。

さて、構造体のメンバーのバイトオフセットを求めるのに、先頭から順に加算していますが、こ れでいいのでしょうか。例えば、次の構造体の場合、どうなるでしょう。

        struct  mixed {
                char    code;
                double  value;
                short   datas[5];
                float   force;
                char    status;
                int     length;
                char    gomi;
        };
この人の考えに従うと、次のようになるでしょう。
                      サイズ   オフセット
    char    code         1         0
    double  value        8         1
    short   datas[5]   2×5     9
    float   force        4     19
    char    status       1     23
    int     length       4     24
    char    gomi         1     28
                                29
では、本当にそうなっているかどうかをテストしましょう。

■テスト■

この構造体の実体mxを作り、そのメンバーのアドレスを&演算子で求めて、本当のバイトオフセッ トを求めるプログラムがリスト5−12です。


Tシャツ製作ファクトリー

役に立つ!面白い!
ニュース&リンク
総合サイト Okazaki.gr.jp


リスト5−12 バイトオフセットのテスト(その1)

     1  /*------------------------------------------------------------------------------*/
     2  /*              構造体のバイト・オフセットのテスト(その1)                     */
     3  /*------------------------------------------------------------------------------*/
     4  
     5  struct   mixed {
     6          char    code;
     7          double  value;
     8          short   datas[5];
     9          float   force;
    10          char    status;
    11          int     length;
    12          char    gomi;
    13  };
    14  
    15  main()
    16  {
    17          struct  mixed   mx;
    18          int     base;
    19  
    20          base = (int)&mx;
    21          printf( "code    %2d\n", (int)&mx.code   - base );
    22          printf( "value   %2d\n", (int)&mx.value  - base );
    23          printf( "datas   %2d\n", (int) mx.datas  - base );
    24          printf( "force   %2d\n", (int)&mx.force  - base );
    25          printf( "status  %2d\n", (int)&mx.status - base );
    26          printf( "length  %2d\n", (int)&mx.length - base );
    27          printf( "gomi    %2d\n", (int)&mx.gomi   - base );
    28          printf( "構造体  %2d\n", sizeof(mx) );
    29  }

実行結果は、SUNのSPARCstationでは次のようになりました。

        code     0
        value    8
        datas   16
        force   28
        status  32
        length  36
        gomi    40
        構造体  48
ということは、予想とは全く異なった結果です。では、原因は何でしょう。とにかく、計算機上で 実行した結果が正しく、その意味を考えなければなりません。実行結果から、構造体の本当の構成 は図5−1のようになっています。

                図5−1構造体mixedの構成

                         +---+---+---+---+
            char code  --->  |///////////|
                         +---+-----------+
                         |///////////////|
                         +---------------+
                         |               |
                         + double value  +
                         |               |
                         +---------------+
                         | short         |
                         +      datas[5] +
                         |               |
                         +       +---+---+
                         |       |///////|
                         +---------------+
                         |  float force  |
                         +---+---+---+---+
           char status --->  |///////////|
                         +---+-----------+
                         |  int length   |
                         +---------------+
            char gomi  --->  |///////////|
                         +---+-----------+
                         |///////////////|
                         +---------------+

図から分かることは、構造体のメンバー間には「空き」があることです。こ の空きのことを、パディング(詰めもの)と呼びます。そして、パディングは コンピュータ、あるいはコンパイラにより異なります。

では、このパディングも考慮した、ちゃんとしたバイトオフセットを求める には、どうしたら良いでしょうか?リスト5−12のオフセットの計算方法で は、実体を宣言し、そのアドレスを直接使って計算していますが、実体がない と計算できないのでは、あまり便利とは言えませんね。どうすれば良いのでしょ う。

■正しい作法■

UNIXでX-Windowのプログラムを組んだりすると、バイトオフセットがよく 必要になるので、実は、この回答とも言えるマクロ(XtOffset)が標準ヘッダー ファイルに存在します。
    #define   XtOffset(p_type,field)     ((unsigned int)&(((p_type)NULL)->field))
p_typeは構造体へのポインタ型で、fieldにはメンバー名を与えます。

意味は、無理矢理NULLを指定した型へのポインタだと解釈させるべくキャス トし、そのポインタの指定メンバーのアドレスを&演算子で求めています。 NULL、つまり0のポインタに対してのメンバーのアドレスですから、構造体の 先頭(構造体の先頭メンバーではない)のアドレスは0なので、リスト5−1 2のような引き算は不要になります。最後に、オフセットですから、 (unsigned int)でキャストし、計算に使っても型の不一致でコンパイラに文句 を言われないように配慮しています。良くできたマクロですね。

リスト5−13に、このマクロを使ったテストプログラムを示します。色々 な型のメンバー含む構造体で試しています。


リスト5−13 バイトオフセットのテスト(その2)

     1  /*------------------------------------------------------------------------------*/
     2  /*              構造体のバイト・オフセットのテスト(その2)                     */
     3  /*------------------------------------------------------------------------------*/
     4  
     5  #define XtOffset(p_type,field)     ((unsigned int)&(((p_type)0)->field))
     6  
     7  typedef struct   mixed {
     8          char    code;
     9          double  value;
    10          short   datas[5];
    11          float   force;
    12          char    status;
    13          int     length;
    14          char    gomi;
    15  }Mixed, *Mixedptr;
    16  
    17  main()
    18  {
    19          struct  mixed   mx;
    20          int     base;
    21  
    22          base = (int)&mx;
    23          printf( "code    %2d\n", XtOffset(Mixedptr,code) );
    24          printf( "value   %2d\n", XtOffset(Mixedptr,value) );
    25          printf( "datas   %2d\n", XtOffset(Mixedptr,datas[0]) );
    26          printf( "force   %2d\n", XtOffset(Mixed*, force) );
    27          printf( "status  %2d\n", XtOffset(Mixed*, status) );
    28          printf( "length  %2d\n", XtOffset(struct mixed*,length) );
    29          printf( "gomi    %2d\n", XtOffset(struct mixed*,gomi) );
    30          printf( "構造体  %2d\n", sizeof(mx) );
    31  }

メンバーdatasのとき、datas[0]と配列の先頭要素を指定しています。これは、datasだけでは、 配列に&をつけたことになり、ワーニングになるので、それを避けるためです。もちろん、これか ら、datas[3]などのバイトオフセットも自由にできることが分かるでしょう。

XtOffsetについては、Xウィンドのマニュアルの第5巻(英文)に書かれていますが、#define XtOffsetの内容にはバグがある版があります。--(マイナス2個)は、- (マイナス1個)が正し いです。

■修正■

プログラム全体でインクルードしてしまう標準ファイル中に、XtOffsetのようなマクロを入れて おくと、もう個々の構造体のバイト・オフセットをマクロ定義する必要は全くなくなってしまいま す。

したがって、せっかく努力して書いたexdef2.hは不要ですね。いや、不要どころか、諸悪の根源 だったのです。なぜなら、今まで、偶然にもパディングの影響がなかったので、プログラムは奇跡 的に動作していただけでした。

では、機能拡張などで構造体に新しいメンバーが加わったとき、バイトオフセットのマクロの変 更を忘れたらどうなるでしょう。以前からあるメンバーのバイトオフセットも「うそ」の値になっ ているので、この値を使ってデータを書き込んだりしていたら、構造体の意図したメンバー以外の アドレス内容が破壊される危険があるのです。非常に恐いことです。

要するに、プログラムを延々と書く努力が、実は最悪の努力だったのです。バイトオフセットを 求めるマクロさえ知っていれば、努力の必要すら感じなかったでしょう。

という訳で、プログラムの修正は、諸悪の根源である、exdef2.hを「抹殺」してしまうことです。 そして、XtOffsetのようなマクロを入れ、バイトオフセットを示していたマクロを使っていた部分 を全部書き換えてください。まあ、書き換えせず、コンパイル、リンクをして、エラーメッセージ から書き換え個所を探すこともできます。プログラムの書き換えが極めて大変な場合は、exdef2.h のバイトオフセットのマクロ定義を、ちゃんとしたバイトオフセットを使った定義に直すことで対 応できなくもありません。

訂正(1996/11/27)

本文中にdatasというdataの複数形が出てきますが、dataはもともと複数形 であり、単数形はdatumです。ラテン語のまったく基本的な知識をつい忘れて しまい、お恥ずかしい。

なお、datasをdataに直すのは、修正によるバグの混入や、元のデータとの 整合性もあり、とりあえずそのままで発表しております。


Copyright1996 Hirofumi Fujiwara. No reproduction or republication without written permission
『Cプログラミング診断室』目次次(第5章 管理は複雑に 本当の恐怖)