struct - みる会図書館


検索対象: 月刊 C MAGAZINE 2001年11月号
15件見つかりました。

1. 月刊 C MAGAZINE 2001年11月号

ズは必ずしもメンバのサイズの合計にはな らない」ということです。構造体のサイズ が同じ定義でも , プラットホーム , 処理系 , コンパイル時のオプションにより異なりま す。 short 型や 1 。 ng 型変数は偶数番地 , お よび 4 の倍数番地にしか置けない CPU や , 置けても処理速度が遅くなる CPU が存在す るためです。必ず sizeof 演算子を使用して 構造体のサイズを求めるようにしてくださ ◆構造体型変数の宣言 次に , 実際に使用するためには先ほど宣 言した構造体型の変数を宣言することが必 要です。通常の変数と同様に , 関数の引数 や戻り値として利用することもできます。 書式は次のようになります。 ・構造体型データの宣 [ 記憶クラス ] struct タグ名 変数名 ( 構造体名 ) ; この書式を実際に先ほどの例を当てはめ てみると , 次のような記述になります。 struct c lient groupl [ 100 ] , group2; 以上のように構造体 client 型の変数を複 数宣言する場合には , 通常の変数を宣言す る際と同様に , ( カンマ ) で区切ることによ り宣言することが可能です。また , 1 つ目 の変数のように構造体の配列を宣言するこ とも可能です。この場合 , 同じテンプレー トの構造体の記憶領域が配列数ぶん準備さ れることになります。 ◆そのほかの宣言方法 タグ名をその後使用しない場合 , 宣言時 にタグ名を省略することができます。先ほ どの例を使用すると以下のようになりま す。その場でしか使用されない限定的な型 を持つ変数を定義する場合に使用します。 'B', 11 , ・初期化の例その 2 ( 1 要素ごとに { } で区切ってもよい ) 10 : 13 : ( 24 : { char char char cname[ 20 ctel[ 15 cadr[ 180 嵭 じ型の変数を定義することができます。 } groupl; また , 構造の宣言と構造体型変数の宣言 を同時に行うこともできます。この場合タ グ名 client を利用して , ほかの場所でも同 Fig. 12 構造体型変数 ( 配列 ) の初期化の例 struct XX x2[ ] ・初期化の例その 1 'c', 12 struct 罧 x3 [ ] = { {'D', 13 } , {'F', 15 } TabIe 4 構造体メンバの参照演算子 struct char char Char 土 n し client { CCOde ー cadr[ 180 ctel[ 15 嵭 cname [ 20 groupl ー 参照演算子 . ( ピリオド ) - > ( マイナス記号 + 不等号 ) #include く stdio. h> 1 : / * 構造体の使用例 * / 構造体の使用例 構造体変数実体のメンバの参照 構造体変数のポインタ表現時 , メンバ参照 struct { int CCOde ー Fig. 11 構造体型変数の初期化の例 Char c; struct XX { struct XX xl = {'A', 10 2 : 4 : 5 : 6 : 7 : 8 : 9 : 11 : 14 : 15 : 16 : 17 : 18 : 19 : 20 : 22 : 25 : 26 : 28 : 29 : 30 : 31 : 33 : 34 : 35 : struct c ー ient { 3 : void SetRecord( struct client *ps, 土北 icode, char *pname, char *ptel, char *padr ); char cadr[1801 char c セ e Ⅱ 15 ] char cname[20] int ccode 12 : void main( ) struct client groupl; ps->cadr[i] = 0 ' while (*padr) ps->cadr[i 十十 ] = *padr 十十・ ps->ctel[i] = 0 ' while (*ptel) ps->ctel[i 十十 ] = * pt 引十十・ ps->cname[il = 0 ' while (*pname) ps->cname[i 十十 ] = *pname 十十 ps->ccode = 土 COd int 土 = 23 : void SetRecord( struct client *ps, int icode, char *pname, char *ptel, char *padr ) groupl ・ ccode, groupl ・ cname, groupl ・ ctel' groupl. cadr); printf(" CODE:%d NAME:%s TEL:%S ADRESS:%s%n" printf( " 構造 0 は 00t 型のサイズ = 宅 d 蕚 0 " , ~ of ( st て uc し client) SetRecord( & oupl, 1234 , "Nakai" , ” 03-1111-1111 ” , "xxxxxxxxxxx" 66 C MAGAZINE 2001 11

2. 月刊 C MAGAZINE 2001年11月号

構造体型変数の初期化 構造体の初期化をする場合には , 配列の 初期化と同様にⅡで定数式を囲みます ( Fi g. 11 , 12 ) 。 構造体メンバの参照 構造体のそれぞれのメンバを参照する際 に使用する演算子が 2 種類用意されていま す (TabIe 4 ) 。 ◆ . ( ピリオド ) 演算子によるメンバの参照 構造体の実体のメンバを参照する際に使 用します。書式は次のようになります。 構造体名 . メンバ名 たとえば , 先ほど使用した例の「構造体 c lient 型の変数 group2 の ccode 」に値を設定す る場合 , 次のような記述になります。 group2. ccode = 12 ◆ - > ( アロー ) 演算子によるメンバの参照 構造体のポインタ型変数のメンバを参照 する際に使用します。書式は次のようにな ります。 て参照を行う方法が一般的です。構造体の ける手間を省いて , アロー演算子を使用し す。このような理由から ( ) をそのつど付 いか , もしくはコンパイルエラーになりま しまうと , 正しい結果を得ることができな ります。 ( ) を取って * group3. ccode として よりも優先順位が高いため , ( ) が必要にな ピリオド演算子 (. ) がアスタリスク ( * ) ( * 鱈 oup3 ) . ccode = 12 子を使って表現すると次のようになります です。たとえば上記の例を , ピリオド演算 ともできます。しかし , 十分な注意が必要 この場合 , ピリオド演算子を使用するこ group3 ー > ccode = 12 参照する際は次のような記述になります。 struct c lient *group3; 型変数を宣言した場合 たとえば , 次のように構造体のポインタ 構造体名 - > メンバ名 ・参照 構造体へのポインタ型変数 ) struct 構造体タグ名 * 構造体名 ( 6 ロロロロ Fig. 13 プリプロセッサのまとめ #include : ファイルの読み込み ・システムより提供されるファイルを読み込む場合は , #inc lude く s セ d 土 0 . h> ・現在のディレクトリより読み込む場合は , #inc lude nmyheader. h ” ・ヘッダファイル内からさらにヘッダファイルを読み込むことができる #define : マクロの定義 ・書式は , 「 typedef 文字列もしくはマクロ名 ( 文字列 1) 展開語 ( 文字列 2 ) 」 ・文字列 1 に対して文字列 2 が展開される ・特殊なマクロとして 5 種類のマクロが準備されている ・これらのマクロは # define , # undef の対象にはならない #if, #else, #elif, #endif : コンパイル時の条件判定 標準マクロ ー F に E ーマクロとーー凵 NE_ マクロの値を設定する ・マクロ定義における矛盾をチェックするために使用する ・エラーメッセージを出力して , コンバイルを停止する : 工ラーメッセーシ ・ # ifndef は定義されていないときに真となる ・ # ifdef は指定のマクロが定義済みか否かを判定する #ifdef, #ifndef : 定義済みマクロの判定 ・プリプロセッサでコンパイル時に条件判定を行うことができる #line #prag m a ・プラグマ指令と呼ばれ処理系定義のコンバイラオプションなどを指定する #u ndef ・ # define により定義されたマクロを無効にする 構造体メンバの参照 構造体を初期化する場合 , 配列の初期化と同様ロ } で定数式を囲む 構造体型変数の初期化 を使用してサイズを求めること 構造体のサイズは必ずしもメンバのサイズの合計にはならないため , 必 3ösizeof 演算子 struct { int n; short s ー } v3 ー ( 4 ) 変数 v8 のみを定義する } v2; struct s2 { 土 n セ n; long 嵭 ( 3 ) タク名 s2 を宣言すると同時に変数 v2 も定義する struct sl vl; ( 2 ) タグ名 sl に対応する変数ⅵを定義する struct sl { 土 n セ n; char c; (1) タグ名 sl のみを宣言する ・定義方法は以下の 4 種類 Fig. 14 構造体のまとめ 構造体の実体を参照する場合は . ( ピリオド ) 演算子で , 構造体のポインタ型変数を参照す ( 書式 2 ) 構造体名 - > メンバ名 構造体のポインタ型変数のメンバを参照する際には , - > ( アロー ) 演算子を使用する ( 書式 D 構造体名 . メンバ名 ・通常構造体のメンバの実体を参照する際には , . ( ピリオド ) 演算子を使用する る場合は - > ( アロー ) 演算子と覚える 実体を参照する場合は . ( ピリオド ) 演算子 , 構造体のポインタ型変数を参照する場合 は - > ( アロー ) 演算子と覚えてください。 では , 構造体を使用した簡単なサンプル プログラムを List2 に紹介します。 List 2 は SetRecord という関数により構造 体にデータをセットし , その内容を画面 に表示するというプログラムです。 19 行 Getlnto C world ! ! 6 /

3. 月刊 C MAGAZINE 2001年11月号

C プログラマのための C + + 入門 実践 C + + ゼミナール ので , C + + でもポインタを理解することは やはり C 同様に重要です。 C ではポインタが理解できないというの は , 80X80 , Pentium ?? 系 CPU でインテル 簡易表記の mov eax, (esi) , 680X0 系 CPU で モトローラ表記の move. 1 (a0) , d0 が理解で きないのと同じだとまでいわれるほどハー ドウェアに近い低レベルの概念です。実際 , コンパイラはポインタによるアクセスはこ のような機械語を生成して実行させます。 C + + でも同様の破壊的なまでに強力なポイ ンタは使うことはできますが , C に比べる とキャストによる強力な低レベル操作より はより高級言語らしいスマートな使い方が 主流になります。注意深く設計されたクラ スは C の持つ破滅的なポインタ操作を巧妙 に封じて実用的で安全なポインタ操作を提 供します。このようなクラスはすべてのポ インタ操作の演算子をオーバロードしてい て不正な操作は防いでおり , 型変換につい 代入テストのポインタ版② 0 ー Base int baseVar; で lass De て ive ー : 2 は 0 Base 加セ DeriveVar ー C / C + + における構造体の値渡し 構造化 BASIC などで「型定義」ができ , 参 照渡しを基本とする言語でプログラムを学 この例は典型的な「勘違い」の例です。 C んだほうが陥りやすいワナが「構造体を引 は値渡しなので , func ( i ) 呼び出しでは変 数にする関数」と「構造体へのポインタ」を 数 i をまずスタック上にコビーします。そ 引数とする関数の違いです。「構造体を引 してん nc を呼び出します。血 nc に渡された 数にする関数」は文字どおり構造体そのも のは変数 i のコヒ。ーなので func に実装され のを引数として受け取るのですが , C では ている機能が読み出しだけなら正しく動き この引数はスタック上に通常は確保されま ますが , ただこの過程はたいていの場合は す。構造体は時として非常に巨大なメモリ プログラマが意図した動きではないのです。 の塊であることがあります。構造体を引数 こういった場合はたいてい i 自体が操作対 にする関数はこのメモリの塊を引数として 象物であってスタック上の i のコピーでは 受け取ります。 ないはずです。構造体のサイズが小さい場 struct VAR { 合は大きな問題はありませんが , グローバ ルな変数を状態再現のために構造体として struct VAR 土ー まとめて保持するような場合には構造体自 VOid func (struct VAR x) 体のサイズが非常に巨大になる場合があっ て , その場合にこの例のような処理がある とスタックを破壊して暴走することがあり ます。コンパイラが警告できるような要素 ではないので見落としてしまうこともあり ます。まずは構造体自体を引数にする必要 があるかを検討してみる必要があります。 ても C + + の基本的な型からの変換はすべて 実際は型に別名を付けるだけで , 代入ある サポートされていることがほとんどです。 いはポインタ同士の代入はその型本体同士 それでは C + + では暗黙のポインタの変換は が同じかどうかで決定されます。 List 8 を どのような規則で行われるのでしようか ? 見てください。このような代入は警告にも List6, 7 は代入テストのポインタ版です。 工ラーにもなりません。筆者はこういった クラスへのポインタの変換規則は代入と同 C および C + + の性質には慣れていて違和感 じになっています。派生クラスのポインタ はないのですが , 見かけ上異なった名前の はキャストなしでその基底クラスへのポイ 型を持つ変数同士が何のエラーおよび警告 ンタに代入できます。代入のときと同様に もなく代入できてしまうのは理解に苦しむ 基底クラスが p ⅱ vate に継承されている場合 かもしれません。ですが , typedef は決し はエラーになるのも同じです。 て新しい型を定義するものではないことを C + + ではポインタの代入はキャストなし 忘れなければ大丈夫です。 で以下の場合だけ許されます。 次回 ①同じ型同士のポインタ代入 ②派生クラスへのポインタからその基底 今回は型変換とクラスへのポインタの変 クラスへのポインタの代入 換について勉強しました。 C のポインタを これ以外のポインタの代入はキャストな 理解している人には平易な内容だったと思 しではすべてエラーになります。ここで注 います。次回はクラスメンバのⅵれ ual な関 意しなければならないのは呼 pedef です。 C 数について扱ってみます。最近 , とみに でもそうですが typedef はその読み方から 般的な「バーチャル」という言葉に困惑され 「新しい型を定義」するように聞こえますが , ないように ! void ca 日 e て (void) func ( i 潺 Base を *v0; Derive *vl ー void test (void) v0 = vl; vl = v0i 型本体同士が同じなら代入できる typedef 土 n し INT; typedef int 工 nteger; v 引の IN で lnteger *ptr—va 10 INT lnteger * p にて一 v 1 void test( void ) va10 言 v 引 vall = v 引 ptr—va10 ptr—vall; ptr—vall ptr—va10; / 2 C MAGAZINE 2001 11

4. 月刊 C MAGAZINE 2001年11月号

オプジェク 第 8 回 ポインタの利用を抑える C / C + + でもっとも怖いのがポインタに起因するバグで , 気をつけていてもバ グを発生させてしまうものです。ポインタをまったく使わずにプログラムを作 成することは不可能なので , できる限りポインタを使わずに済ませる手段を披 露します。 もないことが起こります。導出クラスの配 列を基底クラス配列引数に渡すことができ てしまいます。これは " 配列をポインタと みなす " 規則と " 基底クラスへのポインタに 導出クラスのポインタを代入できる " 規則 が適用できるからです。結果として導出ク ラスの配列を基底クラスの配列と勘違い し , 配列の n 番目のアクセスがズレてしま います。 ・初期化を忘れた char * p; * p = / / どこに書いたの ? ! ・範囲外のアクセス char * p = new char [ 5 P [ 10 ] = 'A'; / / おいおい ・解放忘れ void f ( ) char * p = new char [ 5 Palm このところ毎回マクラに登場する PaIm なお話。 CodeWarrior 7 がばくの手元に届 きました。さっそくパッケージひっちゃぶ いてインストールしました。見かけは Rele ase 6 と変わらないですねえ。大きな変更 といえば PalmOS 4.0 対応でしようか。も ちろん従来の PaImOS3.5 にも対応してい return; 〃借りたものは返せ ! ますけど。ともあれこれでばくの m505 で 動くコードが書けるようになりました。新 ・・・まだいくらでもあります。とくに文字 しいオモチャを手にした子供みたいにはし 列の表現に char * を使っていたりすると , ゃいでいます。 palm で C + + プログラミング " 終端記号 ' \ 0 ' を忘れていたためにゴミが残 という , いささか無謀なお遊びに突入しよ る " とか " 確保された領域より長い文字列を うというところです。何がどう無謀かとい コピーした " などもインタのしわざとい ・・いつか必ず書くので , イライラし うと・・ えるでしよう。 C / C + + では配列とポインタ ながら待っててくださいませ。 >PaImOS を同じものとして扱う ( 厳密にはちょっと プログラマ各位様。 違うけど ) ので , 配列に関しても同じこと ポインタは怖い・・・ です。領域を越えたアクセスなんてのは日 常茶飯事 ( では困るんだけど ) ですよね。 C / C + + はプログラマは悪いことをしないと C / C + + において , ポインタは鬼門とまで 信じているので , 配列の範囲外をアクセス いわれています ( たぶん ) 。星の数ほど発生 しても文句ひとついいません。また , 関数 するバグのほとんどが , ポインタの誤った の引数に配列を渡したように見えても , 実 使い方に起因するといわれています ( いい は配列の先頭 ( ポインタ ) が渡されるだけで すぎ ? ) 。ポインタにまつわるバグで思い つくものをざっと並べると , す。さらに C + + では List 1 のようなとんで C + + でのポインタのしわざ #include く土 os セて eam > / / 基底クラス base struct base { int / / 導出クラス derived struct derived : base ( int d ー / / basep[5] の内容をプリントする void print-base(base p[51 ) { fo て ( size—t 土 = の土く + 十土 ) { std::cout くく p[il . b くく ' std: :cout くく std: :endl; int main( ) { derived x [ 5 for ( size—t 土 = i く十十土 ) { / / x[i] . b : 0 , 1 , 2 , 3 , 4 x[i] . d= 9- / / x[i] . d : 9 , 8 , 7 , 6 , 5 print—base(x); 〃 derived[ ] を渡しても合法 て eturn 0 ー / * 実行結果 ... なんてこった . 0 9 1 8 2 82 C MAGAZINE 2001 11

5. 月刊 C MAGAZINE 2001年11月号

どうでしようか ? ・ dawn ・・・ 1 6 ・ date ・ dime ・ draw d から始まる単語がすべて同じ値になっ てしまうことは避けられましたが , まだ "d ime" と "draw ' が同じ値になっています。そ こで , ハッシュ表のサイズを 17 ( 約数は 1 , 17 の 2 つだけ = 素数 ) にしてみましよう。 ・ dawn ・・・ 12 ・ date ・ dime ・ draw ・・・ 15 今度は無事にバラバラなハッシュ値を得 ることができました。このようにハッシュ 表のサイズを素数に設定することが , ラン ダムな ( 偏りのない ) ハッシュ値を得るため のもう 1 つの工夫になります。疑り深い方 は「説明に都合のいい文字列を選んだので は ? 」と思うかもしれませんが , そんなこ ハッシュ値が同じデータを 2 分木で連結 ハッシュマップの例 / * ハッシュマップのプログラム例 * / #include く stdio . h> #include く string. h> #include く stdlib. h> / * 英単語 & 和訳を保持する構造体 * / typedef struct char *eng は s char *japanese; } WORDSET ー / * ハッシュ表 * / typedef struct WORDSET **data; unsigned int size; } HASHTABLE; / * 文字列の ASCII コードに重率を掛けてハッシュ値を生成 * / unsigned int MakeHash2 ( char* strt unsigned int hashmax ) unsigned int length, hash, weight; length = strlen(str); for(n = weight = hash = n く length; 十十 n , 十十 weight) if(weight > 7 ) / * 重率が 256 勹まで到達したら , 一巡して再び元に戻す * / weight / * 「くく ( 4 * weight) 」は「 256 、 e 土 ght 」と同じ意味 * / hash 十 = (int)str[nl くく ( 4 * weight); return hash 宅 hashmax ・ / * firsthash の位置がが埋まっていた場合に再ハッシュを行うための関数 * / unsigned int ReHash ( HASHTABLE *hashtab 厄 , unsigned 土 n セ f irsthash ) unsigned int hashval , 新 firsthash; hashval / * f 近 stv 引から kA2 だけ後ろにある空き位置を探す。 「 k> ハッシュ表サイズの半分」となったら , それ以降の探索は無駄。 ー十十 k ) for(k = k く = hashtable->size / 2 (firsthash 十 k * k) 宅 k; hashval if(hashtable->data[hashvall ! = NULL) return hashva 嵭 / * 空き位置が見付からなかったら一 1 を返す * / return -1 ー / * newdata を hashtable に追加する * / void AddDataTOMap(HASHTABLE *hashtable, WORDSET *newdata) unsigned int hashva 嵭 / * 英単語を元にハッシュ値を生成 * / hashval = MakeHash2(newdata->engIish, hashtable->size); / * もしも hash の位置が既に埋まっていたら , 再ハッシュを行う * / if(hashtable->data[hashval ] ! = NULL) hashval = ReHash(hashtable, hashval / * 再ハッシュ結果が一 1 であれば , 空き位置が見付からなかった ( マップが満杯 ) * / if(hashval = = ー 1 ) p て土北 f ( s をマップに挿入しようと試みましたが , 空き位置がありませんでした。 基 n ” newdata->english); return ー ハッシュ表 Fig. 7 k 。番目の位置に再ハッシュ 新しいデータ 1 番目 / * hashval の位置に newdata へのポインタを格納 * / hashtable->data[hashval] = newdata; / * 英単語 key に対応する和訳を hashtab から探し出して返す * / char *GetDataFromMap(HASHTABLE *hashtable, char *key) unsigned int hashval, WORDSET *word; / * 探索を開始するハッシュ値を求める * / hashval = MakeHash2(key, hashtable->size); / * その位置から順番に , key と同じ値を持つデータが現れるまで探索を行う。 * / for(k = k く = hashtable->size / 十十 k ) 4 番目 9 番目 ここに挿入 ハッシュ表 0 105 プログラミングの宝箱

6. 月刊 C MAGAZINE 2001年11月号

Borland C + + Compiler 5.5 より提供され るヘッダファイルを覗いてみると , 多くの ヘッダファイルでは以下のような定義が施 されています。 を stdio. h の場合 #ifndef *—STDIO—H / * ——STDIO—H が未定義の場合に以下が有効 * / #define —STDIO—H / * ——STDIO—H を定義する * / ( 各種定義 ) #endif ヘッダファイルから別のヘッダファイル を include すると , ソースプログラムの数が 豆 CO mn2 #ifdef, #ifndef の利用方法 ァータ型 ァータ型 Fig. 9 #ifdef # て ro て #endif 6 ロロロロ # e 「「 O 「の使用例 —cp lusplus ” C 十十のソースではありません ! ″ 多くなった場合に , 同じへッダファイルが 複数回 include されるという現象が発生し ます。これによって各種変数や定義が重複 定義となり , コンパイルエラーが発生しま す。 このような状況を回避するために、前述 のような定義が施されています。最初に st diO. h が include された場合には _—STDIO_H は定義されていないので、各種定義が有効 こで __STDIO_H を定義し になります。 ています。 2 度目に include された場合には , 最初の include 時に一 STDIO H が定義され ているため当何も定義されずに終了します。 TabIe 3 # ⅱ ne の書式 定義 #line 行番号 #line 行番号 " ファイル名 " Fig. 10 #undef の使用例 #define DEBUG, #undef DEBUG 処理 凵 NE ーマクロの値を変更する 凵 NE と一日 LE ーマクロの値を変更する 複数のデータを 1 つの塊としてタグ名を付 けることができ , 関連性の深いデータをま とめて処理することができます。実際に使 用する際には , タグ名に対応する変数を宣 言します。とくにこの構造体の変数名は , 構造体名と呼ばれることもあります。 ◆構造の宣言 構造体を使用する際には , まず構造体の 持つデータの構成を決めなくてはなりませ ん。構造体を構成する要素であるそれぞれ の変数をメンバといいます。構造体のメン バには各基本型 , 配列 , ポインタ変数が定 義できます。また , すでに定義済みの構造 体 , および構造体のポインタも指定するこ とができます。構造を定義する基本的な書 式は次のようになります。 ・構造の宣 / * これ以降 DEBUG マクロは定義されなかったものとしてコンパイルされる * / 構造体 (Part1 ) が隠されています ( 豆 Column3) 。 豆 CO 旧 mn3 構造体は , 複数のさまざまなデータ型のユーザ定義型という概念に近いものです。 VisuaI Basic の Type ステートメントによる す。 COBOL の集団項目 , Pascal の Record , のであり , 新しいデータ型として扱われま データを使いやすく 1 つに組み合わせたも プリプロセッサ行の終わり struct タグ名 { メンバ変数名 1 , メンバ変数名 2 ; これまで紹介したプリプロセッサの指令 行は , 行の最後までが論理行となっていま す。 コンパイラ本体とプリプロセッサでは , 行の概念が異なります。コンパイラ ( C 言 語のソースプログラム ) では物改行や空白 は単なる区切り文字としての意味しかな く , ; ( セミコロン ) までが論理行となりま す。 これに対して , # から始まるプリプロセ ッサの指令行では , 改行コード ( 行の最後 ) までに定義を完了しなければなりません。 ただし行の最後を次の例のように¥ ( 円サ インまたはバックスラッシュ ) で終了させ ることにより , 一次の行を継続行とするこ とが可能です。 #define MIN(a,b) こで注意することは , 構造体のメンバ を引までの内側に宣言することと , 最 後に ; ( セミコロン ) が必要だということで す。この書式を実際に先ほどの例に当ては cadr [ 180 c し引 [ 15 嵭 cname [ 20 嵭 CCOde ー client { めてみると , 次のような記述になります。 char char char int struct Ge 目猷 0 C world ! ! 65 こで注意することは , 「構造体のサイ 存在する変数ということになります。 ます。つまり client というグループの中に e 」「 client の cname 」というように認識され このときそれぞれの変数は「 client の ccod

7. 月刊 C MAGAZINE 2001年11月号

TabIe 2 今回利用した主な A 曰 int gethostname (char FAR * name. int namelen); 内容コンピュータ名を取得する name 引数 namelen ホスト名を格納するバッファ 上記バッフアサイズ 戻り値成功時は 0 , 失敗時は SOCKET ー E 日日 O 日のコード struct HOSTENT FAR *gethostbyname (const char FAR * name) : 内容ホストデータベースからホスト名の情報を取得する 引数 name ホスト名の文字列へのポインタ 成功時は HOSTENT 構造体へのポインタ , 失敗時は NULL0 HOSTENT 構造体は以下のとおり struct hostent ー cha 「 FAR * h_name; char FAR * FAR * h_aliases; short h—addrtype, short h 」 ength; char FAR * FAR * h_addr 」 ist; 戻り値 正式なホスト名 ホストの別名リスト アドレスの種類 アドレス長 アドレスのリスト int WSAStartup (WORD wVersionRequested, LPWSADATA lpWSAData) : 内容 WinSock の初期化。プロセスによって WinSock DLL の使用を始める wVersionRequested その呼び出し側が使うことができる WinSock のバ - ジョン lpWSAData 引数 WinSock の詳細を受け取る WSADATA 構造へのポインタ。 WSADATA 構造体は以下のとおり struct WSAData ー WORD wVersion; WORD wHighVersion; Cha 「 * szDesc 「 iption; cha 「 * szSystemStatus; unsigned short iMaxSockets; unsigned short iMaxUdpDg, char *IpVendorInfo; ー WSADATA, WinSock DLL で使用する WinSock のバージョン サポートする WinSock のバージョン番号 WinSock に関する情報の文字列 WinSock のステータス 1 つのプロセスで開けるソケットの最大数 送信可能なデータグラムの最大バイトサイズ WinSock のべンダ情報 返り値成功時は O , 失敗時は 0 以外 int WSACleanup (void) : 内容現在のスレッドのユーザ名を取得する BOOL GetUserName (LPTSTR lpBuffer, LPDWORD nSize) : 返り値成功時は 0 , 失敗時は SOCKET_ERROR 内容 WinSock のリソースを解放し WinSock DLL の使用を終了する lpBuffe 「 引数 nSize ユーザ名を格納するバッファ 上記バッフアサイズ 戻り値成功時は 0 以外の値 ( T 日 UE ) , 失敗時は 0 (FALSE) BOOL GetDiskFreeSpaceEx (LPCTSTR lpDi 「 ectoryName, PULARGE 」 NTEGER lpF 「 eeBytesAvailabIeTOCalIer. PULARGE 」 NTEGER lpTotalNumberOfBytes. PULARGE 」 NTEGER lpTotalNumberOfF 「 eeBytes) : 内容ディスク容量に関する情報を取得する IpDirectoryName 情報を取得するディスクのディレクトリ名。 UNC を使うこともできる IpFreeBytesAvaiIabIeTOCaIIer カレントユーザが利用できるディスクの空き容量を表す , PULARGE 」 NTEGER 型の IpT0talNumbe 「 OfBytes 引数 変数へのポインタ ティスクの総容量を表す . ULA 日 GE 」 NTEGE 日型の変数へのポインタ IpTotaINumbe 「 OfF 「 eeBytes ディスクの空き容量を表す , ULA 日 GE 」 NTEGE 日型の変数へのポインタ 戻り値成功時は 0 以外の値 ( T 日 UE ) , 失敗時は 0 (FALSE) BOOL GetVoIumelnformation (LPCTSTR lpRootPathName, LPTSTR lpVoIumeNameBuffe 「 , DWORD nVolumeNameSize, LPDWORD lpVolumeSeriaINumbe 「 , LPDWORD lpMaximumComponentLength. LPDWORD lpFileSystemFlags. LPTSTR lpFileSystemNameBuffer. DWORD nFileSystemNameSize) : 指定されたルートティレクトリのファイルシステムとボリュームの情報を取得する 内容 戻り値 引数 lpRootPathName IpVoIumeNameBuffe 「 nVolumeNameSize lpVoIumeSerialNumbe 「 ルートディレクトリ名の文字列へのポインタ ボリューム名を格納するバッフアへのポインタ IpVOIumeNameBuffe 「バッフアのサイズ ボリュームシリアル番号を格納する文字列へのポインタ lpMaximumComponentLength ファイルシステムがサポートするファイル名の最大長を格納する変数へのポインタ lpFiIeSystemFIags IpFiIeSystemNameBuffe 「 nFiIeSystemNameSize ファイルシステムのフラクを格納する変数へのポインタ。以下の値の組み合わせ FS_CASE 」 S_PRESERVED ディスクにファイル名を記録するときに大文字と 小文字を区別して保存する FS_CASE_SENSITIVE ファイル名の大文字と小文字の区別をサポートしている FS_UN ℃ ODE_STORED_ON_DISK UN ℃ ODE のファイル名をサポートしてい て , ディスク上でも正しく表示される FS_PERSISTENT_ACLS アクセス制御リストの保存と適用を行う FS_FILE_COMPRESSION ファイルべースの圧縮をサポートしている FS_VOL 」 S_COMPRESSED 指定されたボリュームが圧縮ボリューム FILE_NAMED_STREAMS 名前付きストリームをサポートしている FILE_SUPPORTS_ENCRYPTION 暗号化ファイルシステムをサポートしている FILE_SUPPORTS_OBJECT 」 DS オブジェクト識別子をサポートしている FILE_SUPPORTS_REPARSE_POINTS 再解析ポイントをサポートしている FILE_SUPPORTS_SPARSE_FILES スパースファイルをサポートしている FILE_VOLUME_QUOTAS ディスククオータをサポートしている ファイルシステム名を格納する文字列へのポインタ IpFileSystemNameBuffer バッフアのサイズ P 『 m 加 ります。工ラーの場合は , IP アドレスが割 り振られていないか DHCP サーバによって IP アドレスを割り振られる設定になってい ます。 ディスク情報の取得 ディスク情報は , 次のような流れで取得 します。 ①チェックしたいドライブ名を取得 ② GetDiskFreeSpaceEx API で空き容量 などを取得 ほかにも GetVolumeInformation API でポ リューム名やシリアル番号などを取得でき ます。また , GetDiskFreeSpace API でクラ スタ情報などを取得できます。今回のプロ グラムでは , List 1- ⑥のように C ドライプ に限定してディスク情報を取得しています。 返される値の単位はバイトです。 M バイ トや G ノヾイトなどで表示する場合は , 1024 で割らなければなりません。 シリアル番号は , List 1- ⑦のように GetV olumelnformation API から得ることができ ます。 メモリ情報の取得 メモリは , ストレージに比べると普及は 遅いのですが , 128M バイトあたりが標準 になってきています。メモリ情報は , List 1- ⑧のように GlobalMemoryStatus API で取 得できます。 おわりに 今回利用した主な API を Table 2 にあげて おきます。今回は , ューザ環境を取得する プログラムを取り上げました。プロードバ ンドという言葉が花盛りです。 ADSL の普 及によって , メガ単位でのデータ転送が実 現してきました。しかし皮肉にも自身のパ ソコンが旧型すぎてその速さを感じること のできないユーザも多数います。ネットワ ークという分野は , 全体のバランスが整わ ないと , 思ったような効果が得られない分 野の代表例になるかもしれません。今回の 情報を活用して , ューザ側に立ったアプリ 成功時は 0 以外の値 ( T 日 UE ) , 失敗時は 0 (FALSE) ケーションを開発していきましよう。 Windows Programming Tips 1 2 /

8. 月刊 C MAGAZINE 2001年11月号

set-up で初期化 , tea 「一 down で終了処理を行う # 以下に自分で作ったスクリプトを入れる use StriCt ー sub add { return $z ー # 以下がテスト用のコード use Test: :Unit; my $datafile = ”セ es しヒ x し″ # テストごとの初期化 sub set—up { open(FILE, ″ > $datafile" 0 て die• print FILE くく”を OD ” 1 滝 , 3 3 .1415 , 9 . 2653 , 12.4068 EOD close(FILE); open(FILE, $datafile) # テストごとの終了処理 sub tear—down { close(FILE); unlink($datafile); # テスト用の関数は tes しではじめる sub test—add { while ( く FILE>) { assert()a = = $z, "$datafile の $. 行目″ それぞれは , 必要なテストを行った後 , create—suite ( assert という Test::Unit の関数で正しい結 によってテストスイートを作成します。引 果になったことを確認しています。 数に何も指定していないときには , 現在の ートとなります。 ノヾッケージがテストスイ 具体的には , 現在のスクリプトの中にある test という関数が自動的に Test::Unit によっ てスキャンされて集められるのです。 そして最後に run—suite ( でテストスイートを実行します。ここでは , 先ほど create_suite によって集められた test で始まる関数が実行されます。そしてその 中の assert によって正しい結果になったか どうかが確認されるわけです。 実行結果は Fig. 1 のようになります。 こで , ピリオド (. ) は成功したテストを表 します。すべてのテストが成功すると , O K と表示されます。 ずいぶん静かなメッセージですが , これ でいいのです。 こではテストは 3 つだけ ですが , これが何十 , 何百となったときに も画面がうるさくならないからです。淡々 とピリオドが表示されて , 最後に OK が出 る , というのが Test::Unit のインタフェイス であり , これは JUnit など , ほかのユニット テストのテキスト版 UI の形式でもあります。 わざと失敗させてみよう テストをテストするためには , 成功して いる例だけではなく失敗する例も試してお かなければまずいですね。 List 2 は 1 つだけ 誤り (test-double) を含んだテストプログラ ムです。 実行結果 ( Fig. 2 ) を見るとわかるように テストの進捗を表すピリオド (. ) の後ろに F という文字が表示されています。これは そのテストが失敗 (Failure) したことを表現 しています。 そして , Run: 3 Fai lures : 1 E てて 0 て s : 0 と結果を表示した後 , 失敗の詳細を表示し List List テスト対象となるモジュール ( Add. pm ) # 自分で作ったモジュール package Add; use strict ー use vars qw(@ISA @EXPORT); require Exporter ー @ISA = qw(Exporter); @EXPORT = qw(add); sub add ( return $ z ー Add. pm をテストするテストコード (testmod. pl) List use StriCt ー use Test: :Unit; # テストする対象のモジュール use Add; # テスト用の関数は tes セではじめる sub test—positive—int { my$a= &add(), 2 assert ( $a = = 3 , ”正の整数の加算” # テストは複数個書いてもよい sub test—negative—int { my $a = &add(-l, -2 -3 , ″負の整数の加算” assert($a # テストは複数個書いてもよい sub test—doub { my $a = & add ( 3.1415 , 9.2653 = 12.4068 , ″浮動小数点数の加算″ assert()a = # テストは複数個書いてもよい sub test—sub { while (<FILE>) { "$datafile の $. 行目″ assert()a = # テストの実行 create—suite ( 馮 run—suite( ) ー # テストの実行 create—sui te ( run—suite( TabIe 3 Goog ⅱ ze ! の概要 項目 説明 ・ CGI にアクセスするとドメイン名を入力するフォームを表示する ・ドメイン名を入力するとそれを織り込んだ検索ボックスを表示する ・ mycmd の値が未指定の場合には , 入力フォームを表示する (do_show) ・ mycmd=googlize の場合には , 検索ボックスを表示する (do-googlize) ・検索ボックスは以下の 2 形式で表示する ( 1 ) Copy&Paste に便利なように texta 「 ea 内に文字列として表示する ( 2 ) 試しに実行できるようにフォームとして表示する ・ goog ⅱ ze. cgi の 1 行目を設置するサーバの Perl 処理系に合わせて変更する ・ googlize. cgi のバ ーノンヨンを実行可能にする ・ CGI.pm や jcode. pl, Jcode. pm は不要 能力カ 機入出 う又ーー . 90 C MAGAZINE 2001 11

9. 月刊 C MAGAZINE 2001年11月号

特集 2 自在に操る S 器Ⅷ頂日 TabIe 16 List 2 の HA_Unique の内容 のコマンドバラメータに対して目的のコマ 値 サイズ ンドコードを書き込んでおき , そのコマン 内容 ドに対応するパラメータを SRB に書き , そ 読み書きを行うバッフアに要求されるアラインメントマスク設定 0 WORD ( 0 : 任意 , I:WORD, 2:DWORD, 7:QWORD) れを ASPI に渡します。なお , 基本的に使用 しないメンバの値は 0 で初期化する必要が このテータのビット 0 が 1 のときホストアダブタのデータ転送レポート機能がある BYTE ありますので , パラメータをセットする前 SCSI アダブタがサポートする最大のターゲット数。ただし , ここが 0 のときはデフ に memset() などを使用して SRB をゼロクリ BYTE オルトで 8 つまで ( SCS Ⅱ D が 0 ~ 7 ) の機器をサポートすることを意味する アしておくのが確実でしよう。 4 DWORD 一度に転送可能な最大のデータバイト数 こうして SC_HA INQUIRY を SRB Cmd に SRB HaId に対して情報のほしいアダブタ TabIe 17 List 2 の S 日 B ー status で返される値 ( 全コマンドバケット共通 ) 番号を書き込んで SendASP132Command A 内容 PI を呼び出すと , その SRB のメンバにパソ 実行中 SS_PENDING 0x00 コンの中に存在するホストアダブタ数 ( HA 正常終了 SS_COMP 0x01 Count) と , アダブタのサポートする最大の 実行が中断された SS ABORTED 0x02 SCSI ID 数 (HA-Unique [ 2 ] ) を返してくれま 実行の中断に失敗した SS_ABORT_FAIL 0x03 す (List 6 ) 。 SCSIZ マンド実行がエラーで終了した SS_ERR 0x04 後は , この最大 ID 数のぶんだけ SC ー GET 指定されたホストアダブタ値が不正 SS INVALID HA 0x81 DEV_NPE コマンドを使用して , 各 ID と各 S 日 B で指定された値が不正 SS 」 NVALID_SRB 0xE0 LUN にあるデバイスをすべて探索していき SC_HA 」 NQU 旧 Y コマンドで取得される HA ー Unique メンバで要求され SS_BUFFER_ALIGN ます (List7)0 コマンドの実行結果は呼び 0xE1 るバッフアのアラインメント指定と指定されたバッフアが合わない 出し時にセットした SRB の SRB_Status に入 AS 曰が指定された命令を現状受け付けることができない ( 後で再実行す SS_ASP 凵 S_BUSY 0xE5 ること ) っています。この値が SS_COMP であれば S SS_BUFFER_TO_BIG 指定された転送量が大きすきる 0xE6 RB-DeviceType に正常に対象となるデノヾイ SC_EXEC_SCSI_CMD コマンドバケット 2 3 4 SRB の構造 4 1 struct 1 1 / / ASPI コマンドコード (SC-EXEC_. SCSI-CMD) SRB—Cmd; / / ASPI コマンドステータス ( R) SRB—Status ー / / ホストアダブタ番号 SRB—HaId; リクエストフラグ SRB—Flags; / / Reserved(=0) SRB—Hdr—Rsvd; DWORD 3 / / 実行するターゲットの SCSI ID SRB—Target; 〃実行するターゲット論理ユニット番号 SRB—Lun; / / Reserved(=0) SRB—Rsvd1; 〃データ用確保バッファ長 SRB—BufLen; DWORD データバッフアアドレス *SRB—BufPointer ー / / センステータ用確保バッファ長 SRB-. SenseLen ・ / / CDB の長さ SRB—CDBLen ・ 〃ホストアダブタステータス (R) SRB—HaStat; 1 〃ターゲットステータス (R) SRB—TargetStat ー (*SRB-PostProc) ( void / / ポストルーチン 1 1 / / ( あるいはイベントハンドル ) / / Reserved(=0) void *SRB—Rsvd2; / / Reserved(=ALL 0 ) SRB—Rsvd3[16]; / / CDB の内容 CDBByte[16]; SenseArea[SENSE—LEN] ー / / センステータを格納する領域 } SRB—ExecSCSICmd, *PSRB—ExecSCSICmd; SC_ABORT_SRB コマンドバケット 5 / / ASPI コマンドコード (SC—HA—INQUIRY) / / ASPI コマンドステータス (R) / / ASPI ホストアダブタ番号 / / ASPI リクエストフラグ / / Reserved 〃ホストアダブタの存在数 ( R ) / / ホストアダブタの SCSI ID(R) / / 円マネージャ名称 ( R ) / / ホストアダブタ名称 ( R ) / / ホストアダブタ固有パラメータ (R) / / Reserved SRB—Cmd; SRB—Statug ー SRB—HaId; SRB—FIags; SRB—Hdr—Rsvd; DWORD HA—Count; HA—SCSI—ID; HA—ManagerId[16]; HA—Identifier[16]; HA-Uniquet16J; HA—Rsvd1; WOD } SRB—HAInquiry, *PSRB—HAInquiry; List SC GET DEV_TYPE コマンドバケット 3 typedef struct SRB_Cmd; SRB—Status ー SRB—HaId; SRB—Flags; SRB—Hdr—Rsvd; DWORD SRB—Target; SRB—Lun; SRB-Rgvd1; ー } SRB—GDEVBlock, *PSRB—GDEVBlOCk; / / ASPI コマンドコード (SC—GET—DEV—TYPE) / / ASPI コマンドステータス (R) / / ホストアダブタ番号 / / Reserved(=0) / / Reserved(=0) / / 取得するテパイスのターゲットの SCSI ID / / 取得するテパイスの論理ユニット番号 / / ターゲットのデバイスタイプ (R) 1 typedef 8 セて uc セ SRB—Cmd; SRB—Status ー SRB—HaId; BYTE SRB—FIags; SRB—Hdr—Rsvd; DWORD void SRB—Amrt. , *PSRB—Amrt; / / ASPI コマンドコード (SC—ABORT_SRB) / / ASPI コマンドステータス (R) / / ホストアダブタ番号 / / Rese ( = 0 ) / / Reserved(=0) / / アポートを行う S へのポインタ 特集 2 自在に操る SCSI/ATAPI

10. 月刊 C MAGAZINE 2001年11月号

ュールです。ユニットテストはオプジェク ト指向のプログラムを考え , 継承などをう まく使ったテスティングフレームワークに なっています。 perldoc によれば , Test::Unit はオプジェ クト指向的にテストするのではなく , 普通 の PerI の手続き的な方法でテストするため のものです。よりオプジェクト指向よりの テストについては , Test::Unit::TestCase を 参照してください。このモジュールも Test : : Unit の配布物の中に含まれています。 Test::Unit は以下から入手できます。 200 1 年 9 月の段階では Ver. 0.14 が最新でした。 PPM を使っても入手できますが , バージ ョンが少し古い ( 0.12 ) ようです。 まずは簡単な例で Test : : Unit を試してみ 簡単なサンプル Test::Unit を動かしてみよう 数は Table 2 です。 Test::Unit で工クスポートされている関 より ) /by-module/Test/Test-Unit-O. 14. tar. gz ( 入手は http://www.cpan.org/modules ule/Test/Test-Unit-0.14. readme http://www cpan.org/modules/by-mod PerI by Christian Lemburg ・ Test::Unit - a unit testing interface for その後には , テスト用の関数 ( 関数の名 前は test で始まらなければなりません ) が 3 つ続きます。 もっとも簡単な Test : : Unit のサンプル List # 以下に自分で作ったスクリプトを入れる (samplel . pl) my $a = & add ( 3.1415 , 9.2653 sub test—double { # テストは複数個書いてもよい assert ( $a ー ー 3 , ″負の整数の加算″ my $a = &add(-l, sub test—negat ive—int { # テストは複数個書いてもよい assert( $a = = 3 , ”正の整数の加算” my $a = &add(), 2 sub test—positive—int { # テスト用の関数は tes セではじめる use Test: :Unit; # 以下がテスト用のコード return $z ー sub add { use strict; Fig. 1 List 1 (samplel . (l) の実行結果 run—suite ( create—suite ( ) ー # テストの実行 assert ( $a = = 12.4068 , ”浮動小数点数の加算″ Time : 0 wa Ⅱ cl ock secs ( 0 . 01 usr 十 OK ( 3 tests ) Time : 0 wa Ⅱ ock secs ( 0 . 01 usr 十 Fig. 2 List 2 ( sam e2. (l) の実行結果 EnjOY PerI Programmmg モジュールを活用はう test—positive—int test—negative—int test-doub 厄 失敗するテストの例 (sampIe2. pl) # 以下に自分で作ったスクリプトを入れる use StriCt ー sub add { return $z; # 以下がテスト用のコード use Test: :Unit; # テスト用の関数は test ではじめる sub test—pos itive—int { my$a= &add(), 2 assert()a = = 3 , ”正の整数の加算” # テストは複数個書いてもよい sub test—negative—int { my $a = &add(-l, assert()a = ー 3 , ”負の整数の加算” # わざと誤ったテストを書いてみる sub test—doub 1 e { my $a = & add ( 3.1415 , 9.2653 潺 assert()a = = 12.4067 , ″浮動小数点数の加算″ List run—suite ( create—suite ( ); # テストの実行 ましよう (List 1 ) 。 こでは , プログラマ 0 .00 sys 0 . 00 sys 0 . 01 CPU) 0 . 01 CPU) が作った add という関数 ( 2 数を加算する ) を テストしてみましよう。あまりにも単純な 例ですが , ご了承ください。 ListI では , add という関数のスクリプト と同じファイルにテスト用コードも書いて います ( これは単純化のためにこう書いて います。こうでなければならないというわ けではありません ) 。次のようにして , Te st : : Unit を使うことを宣言しています。 use Test: :Unit; ! ! !FAILURES! ! ! Test Results : Run : 3 Fai ー ures : 1 Errors : 0 There was 1 fail ure : 1 ) test-double(Test: :Unit: : TestCase : Test was no し successful. 浮動小数点数の加算 Test : : Unit : : ExceptionFail ure : : Load1—Anonymous3 ) Enjoy PerI Programming モシュ ールを活用しよう 89