構造体をいっぱい使いだすと、構造体の各メンバーが、構造体の先頭から何 バイト離れているか(バイトオフセット)が欲しくなることがよくあります。 このとき、どのように苦労してバイトオフセットを求めたかを示す絶好のプロ グラム例があります。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です。
|
リスト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個)が正し いです。
したがって、せっかく努力して書いたexdef2.hは不要ですね。いや、不要どころか、諸悪の根源 だったのです。なぜなら、今まで、偶然にもパディングの影響がなかったので、プログラムは奇跡 的に動作していただけでした。
では、機能拡張などで構造体に新しいメンバーが加わったとき、バイトオフセットのマクロの変 更を忘れたらどうなるでしょう。以前からあるメンバーのバイトオフセットも「うそ」の値になっ ているので、この値を使ってデータを書き込んだりしていたら、構造体の意図したメンバー以外の アドレス内容が破壊される危険があるのです。非常に恐いことです。
要するに、プログラムを延々と書く努力が、実は最悪の努力だったのです。バイトオフセットを 求めるマクロさえ知っていれば、努力の必要すら感じなかったでしょう。
という訳で、プログラムの修正は、諸悪の根源である、exdef2.hを「抹殺」してしまうことです。 そして、XtOffsetのようなマクロを入れ、バイトオフセットを示していたマクロを使っていた部分 を全部書き換えてください。まあ、書き換えせず、コンパイル、リンクをして、エラーメッセージ から書き換え個所を探すこともできます。プログラムの書き換えが極めて大変な場合は、exdef2.h のバイトオフセットのマクロ定義を、ちゃんとしたバイトオフセットを使った定義に直すことで対 応できなくもありません。
訂正(1996/11/27) |
本文中にdatasというdataの複数形が出てきますが、dataはもともと複数形 であり、単数形はdatumです。ラテン語のまったく基本的な知識をつい忘れて しまい、お恥ずかしい。 なお、datasをdataに直すのは、修正によるバグの混入や、元のデータとの 整合性もあり、とりあえずそのままで発表しております。
|