List 1 が , GCC-C はビットテスト命令をけっこう 効果的に使ってくれるのて、 , sprite. h を変更 して 2 値しか取らないフラグワークはビット フィールドを用いるのも適切かもしれませ ん。 また「スピード命」のシューティングて、は , カウンタを定数て、割り算したり掛け算した りする場合は , 2 のべきの数値 2 , 4 , 8 , 16 ・・・を選んて、おくのが適切て、しよう。私の 移植した GCC て、は , 定数て、の掛け算は shif t, add, sub て、実行しますがそれて、も単なる シフトだけて、実行て、きるほうがより高速て、 す。固定的な配列て、多次元の場合は要素数 を 2 のべき数にしておくのも「スピード命」の 場合は効果的て、す。 関数ゞ敵キャラ発生移動〃が , ゲームの メインループから呼ばれる敵キャラ処理の 先頭関数て、す。コメントにあるように ては敵キャラクタの発生時間間隔を変数、、ラ ンク〃て変化させています。、、ランククは敵 キャラ破壊数に連動して変化する変数て、 , たくさん破壊していれば発生頻度が上昇し ます。一般的なシューティングゲームのラ ンク ( これはゲームの難易度が自機のパワー アップなどによって変化することをいうよ うてす ) は , こんな単純なパラメータて、はな いようて、すが , 今回は簡単な処理て、済ませ ています。本格的なシューティング作成を 目指す方は市販のゲームを研究して , この ランクの変動がどのように行われているか を知っておくのもゲーム作成の参考になる かと思います。 話が少しそれましたが , この関数、、敵キ ャラ発生移動クの内部処理はほとんど前回 の自機の発射した弾の処理と同じて、す。た だ , 敵キャラクタは 2 種類あるのて、 , 発生す るキャラクタを乱数て、 2 種類に分けて発生さ せているだけて、す。ほかに目立った違いは , 敵キャラクタのカタサ ( 何発弾を受けたら破 壊されてしまうかを決める値 ) を設定してい るのと , いわゆる「イタイ , イタイ」を表示 static void 114 : 115 : 敵キャラ発生移動 (void) 116 static int 前回時間 : 117 : Sprite 発生キャラ ; 118 : int キャラ数 , 119 : 120 : / * 敵の発生時間間隔をランクに応じて変える * / 121 : if ( 経過時間一前回時間〉 MAX-NUM + 2 ーランク ) 122 : 123 : 前回時間 = 経過時間 ; 124 : 125 : / * 敵の数が現在のランク以下ならキャラを発生させる * / 126 : if ( 敵数くランク ) 127 : 128 : 129 : int 空き ; 130 : Sprite type; 131 : for ( 空き = 0 ; 空きく MAX_NUM; 空き + + ) 132 : if ( 敵 [ 空き ] = 133 : break; 134 : if ( 空き = MAX_NUM) 135 : game-abort ( ”異常発生キャラクタ発生” ) ; 136 : 137 : 敵 [ 空き ] = 発生キャラー 138 : dup—sprite (type = (unsigned) rand ( ) く 0X2000 ? タイプ B : タイプ A); 139 : 140 : / * いい加減なランダム位置に初期化する * / 141 : move-sprite-abs ( 発生キャラ , (rand ( ) % 20 の + 16 , の : 142 : 143 : / * 敵キャラバターンをセレクト * / 144 : select—sprite—pcg ( 発生キャラ , の ; 145 : 146 : / * パレットを選ぶ * / 147 : select—sprite-color ( 発生キャラ , 3 ) : 148 : 149 : / * 表示を行う * / 150 : display-sprite ( 発生キャラ , 表示 ) ; 151 : 152 : / * カタサを設定する。大きくするとカタくなる * / 153 : SPD_REGIST ( 発生キャラ ) = type = = タイプ A ? 2 : 4 ; 154 : 155 : / * 寿命を初期化しておく * / 156 : SPD_LIFE ( 発生キャラ ) = 256 ; 157 : 158 : SPD_WORKO ( 発生キャラ ) ー ニタイプ A ? (int) A_type : (int) B_type; 159 : type ー SPD_WORKI ( 発生キャラ ) = 0 ; 160 : SPD_WORK2 ( 発生キャラ ) = 0 ; 161 : SPD_WORK3 ( 発生キャラ ) = 0 : 162 : 163 : 敵数 + + ; 164 : 165 : 166 : 167 : 168 : 169 : 170 : 171 : 172 : 173 : 174 : 175 : 176 : 177 : 178 : 179 : 180 : 181 : 182 : 183 : 184 : } / * 各敵について移動処理を行う * / for ( キャラ数 = 0 ; キャラ数く MAX_NUM; キャラ数 + + ) Sprite 対象 , if ( 対象 = 敵 [ キャラ数 ] ) if (SPD_WORKI( 対象 )) if (--SPD_WORKI( 対象 ) = = の select—sprite—color ( 対象 , 3 ) ; if ( 敵移動処理 ( 対象 ) ) 敵 [ キャラ数 ] = 0 ; 敵数 142 C MAGAZINE 1992 4
メモリ管理のメカニズムは , オペレーティ ングシステムやコンパイラ ( とそのライプラ リ ) て、サポートされていて , 表舞台には現れ ないからてす。 メモリの割り当てには , 静的割り当てと 動的割り当てがあります。静的割り当ては , 文字どおりプログラムを実行している間中 , 領域を割り当てておく方式てす。 C 言語て、い えば , 記憶クラスが extern と static の変数は 静的割り当てになります。静的割り当てさ れるデータは , プログラムをリンクする時 点てそのアドレスが確定します。静的割り 当てに関してはただこれだけのことて , メ モリ管理のための仕組みは必要としません。 動的割り当ては , 領域が必要になった時 点てメモリを割り当て , 不要になった時点 て解放するという方式て、す。 C 言語て、いえ ば , 記憶クラスが auto ( と register) の変数が 動的割り当てになっています。 auto 変数 は , その変数が定義されている関数に制御 が渡ったときにメモリが割り当てられます。 そして , その関数から戻る時点て、 aut 。変数 の領域は解放されます。 auto 変数の割り当 て / 解放は , 関数の呼び出し / 戻りに同期し ているのて , 通常はスタックを用いて実現 されます。スタックを使えば , 関数から戻 るときに確実にメモリが解放されるという メリットがあります。 こてデータの寿命について考えてみま しよう。データが割り当てられてから解放 されるまて、をデータの寿命といいます。静 的割り当てされたデータの寿命は , プログ ラムの実行開始から終了まて、て、す。 auto 変 数の場合は , その変数の定義を含む関数の 実行開始から終了まてが寿命になります。 ほとんどの場合はこれて、十分て、すが , 関 数の実行が終了してもメモリを解放してほ しくないというケースもあります。つまり , aut 。変数のように関数の実行に左右されず に , 任意のタイミングて、割り当て / 解放する ことがてきるデータがほしいわけて、す。 「メモリの動的割り当て』といえば , 76 C MAGAZINE 1 的 2 4 のように任意のタイミングて自由に割り当 て / 解放がてきる方式を指し , auto 変数のよ うなスタックを使ったものは除外するのが 普通てす。これ以降 , この記事ても「動的 割り当て』という用語を , スタックを除い た狭い意味て、使うことにします。 動的割り当ては , 連結リストや木構造な どを扱うときにも必要になります。これら のデータ構造は , あらかじめその形が決ま っていない上に実行中にも形が変化するの て、 , 必要に応じてメモリを自由に割り当て / 解放てきなければならないからてす。動的割 り当てをサポートするために , C 言語のライ プラリには malloc と free が用意されています。 、割り当ての分類 動的割り当てを行うには , あらかじめ大 きなメモリプロックを確保しておきます。 これをヒープ ( heap ) と呼びます。この「ヒー プ」という単語は , ヒープソートのヒープと 同じてすが , まったく別の概念なのて、注意 してください。メモリ割り当て要求がきた ら , ヒープの一部を切り出してそれを返し ます。また , 必要がなくなったメモリは , 解放されてヒープに戻されます。 動的割り当てには , いくっかバリエーシ ョンがあります (TabIe 1 ) 。まず , 割り当て るメモリプロックの大きさが一定てあるか どうかによって分類することがて、きます。 C の malloc / free は任意の大きさのプロック を割り当てることがて、きます。また BASI C, awk, perl て、は可変長の文字列を動的割 り当てします。これに対して Lisp ては , 常 に一定の大きさのセル (cell : またはコンス TabIe 1 動的割り当ての分類 セル conscell) 単位て、割り当てが行われます ( 近代的な Lisp には , このほかに可変長のデ ータを動的割り当てする機構が含まれてい る ) 。また , ファイルを管理するには固定長 のプロック ( クラスタなどとも呼ぶ ) を単位 に割り当てを行います。 もうひとつの分類は , 不要になったメモ リプロックをどのように解放するかという 点てす。これにはふたつの流儀があります。 まずひとつは , プロックが不要になった時 点て、明示的に解放する方法て、す。 C の ma110 c/free は , 明示的にプロックの解放を指定す る方式をとっています。つまり , malloc て、 割り当てたプロックが不要になったら , fre e を呼び出してそのプロックを解放しなけれ ばなりません。ファイルの管理ても , ファ イルを削除するときにプロックを解放する のて、 , 明示的に解放することになります。 もうひとつの方法は , システム側が不要 になったプロックを自動的に判断して回収 するものて、す。この作業をごみ集め ( garba gecollecting) といいます。またごみ集めを 行うルーチンを , ガべージコレクタ (garba ge collector) といいます。 ージコレクタ ガべージコレクタは , ヒープ中のプロッ クをチェックして , まだ使用中のプロック に印をつけます。そして印がついていない プロックを不要なものとして回収し , 再び 割り当てられるようにします。 Lisp て、はメ モリ管理に全面的にガべージコレクタを使 用しています。また , BASIC, awk, perl などの可変長の文字列を扱えるプログラミ 解放するタイミング 明示的に解放する ファイルの管理 ごみ集めを行う Lisp のセル領域 割り当てる プロックの 大きさ 一定である 一定でない BASIC, awk, pe などの 可変長文字列 C の malloc/free
特集℃プログラミンクの秘 語は , C のプログラムて、あっても使わない 界からは当然そんなことは知ったことて、は うわけて、す。 とをおすすめします。 ないのて、す。 ほかに , よく使われる伝統的な意味を持 たとえば , 学校の成績処理プログラムを このような , 自分の世界以外て、使われて つ変数を示しておきましよう。 書きたい人は , 次のような構造体を作りた いる名前というのは , 案外無頓着なものだ int C くなるかもしれません。 し , 実際 , それて、問題になることも極めて 文字を格納する使い捨ての変数て、す。 struct student { 希て、すが , それて、も知識として持っている れを char とする人がよくいますが , 伝統的 名前があれば , 考慮するに越したことはな に int て、す。また , int て、受けないとおかしく int grade int Class , いと思います。 なることもあります ogetc などはマニュアル ただし , これらの関数名を伝統的に使わ int number を見ればわかるように , int の値を戻します れている関数と同じ機能を模倣するために char *name , から , 当然 int て、受けなければなりません。 使う場合は別て、す。かえって変な名前をつ char *S , この構造体の欠点は , class という名前を けるとイメージがわかないからて、す。この 作業用の , 文字列へのポインタとして使 使っていることて、す。将来 C 十十に移植しよ ような場合て、も , printf て、はなく my printf います。 string の頭文字を連想させるためて、 うとした人が , びつくりするかもしれませ という関数名にしておく手があります。 ん。悲惨なことにならないことを祈ります。 C 言語からほかの言語へのプログラムを移植 これらは , 使い捨ての変数として使われ する機会は , 神経質になるほどのものて、は ます。 for て、ループさせる場合のカウンタに ないかもしれませんが , 一応 , C 十十への移 次のリストを見てください。少し C て、プロ よく使われます。これらにかぎらず , 1 文字 植というのは , もっともありそうなことて、 グラムを書いたことのある人が見ると , 「間 の変数は , おもに使い捨ての一時変数とし すから , C 十十の予約語は避けておくのがよ 違っているぞ」と指摘されるかもしれませ て使われるものと思えば , たいてい間違い いて、しよう。 ありません。 ん。 もうひとつの , 使ってはならない名前の void main(int argv, char *argc [ ] ) int n 同にく使 0 、捨、 0 変数て、す , 00000 , を 指針。 ANSI 以外の処理系における慣習的な 関数名は , ほかの用途に使わないほうがい 連想させるのて、 , 個数を意味することが多 いて、しよう。ただ , 私もそうて、すが , 多く いようて、す。 のプログラマは自分のかぎられた世界に閉 long に じこもることが多く , ほかの世界が見えな 使い捨ての変数て、も , 1 は 1 。 ng の変数とし くなってしまうことがしばしばあります。 て使われることが多いようて、す。 しかし , このプログラムは文法的には何 さすがに printf や fopen という名前を , 自分 も間違ってはいないのて、 , コンパイルすれ int len て、定義したまったく別の関数につけること 長さて、す。何の長さかは , 場合によりま ば , 予期したとおりに動作します。間違っ はありません。 す。 long のことがあるかもしれません。 10n ているのは , プログラムて、はなく , main の しかし , UNIX の世界しか知らない人は , g なら llen と書きたくなるところてすが。 引数に argv, argc という順序て名前をつけ findfirst という名前をまったく独自の関数の char buf [ ] るという感性て、す。この種の名前は , 伝統 名前として使ってしまうかもしれないのて、 作業用の文字列を格納するためのバッフ 的に命名されていて , プログラマが勝手に す。なぜなら ,DOS の世界に findfirst という アとして使われます。 buffer の後半分を略し 変えるのは望ましくありません。そういう 関数があるということを知らないからて、す。 意味て、はこのプログラムは確かに間違って たものて、しよう。 何か最初の要素を検索するような関数に いるわけて、す。 char *src , findfirst という名前をつけたくなるのは自然 伝統的に使われる名前は , て、きるだけ先 char *dst ; な発想なのてす。 達に従うのが得策て、す。 src は source, dst は destination を意味して 逆の例てすが , 昔 LHarc を UNIX に移植し argc, argv はそれぞれ関数 main に渡され います。文字列をコピーするような場合に , たとき , link という関数が独自に定義されて る引数て , コマンドラインのパラメータの src から dst に向かってコヒ。ーすることになり いたため , とんてもない目にあった経験が ます。 数と , 各パラメータの文字列へのポインタ ありました olink という関数があることは U を順に格納した配列を意味するのが伝統て FILE *fp , NIX の世界ては常識なのてすが , DOS の世 ファイルポインタとして使われます。 fil すから , 上のように書いたら目茶苦茶とい 65 特集 C プログラミングの秘訣 す。 int i, イ的に使われる名前 while (——argv > 0 ) { puts(*argc 十十 ) ;
List 4 : 5 : 6 : 7 : List Cha 「 A 「「 ayHo 旧 e 「クラスの派生 arrayholder. ” cout くく ans : ans=anArray. at(2); ArrayHo 1 der anArray : int ans : 2 : main() 1 : #include プログラムの再利用の例 char func(int Ⅱ ) ( return(charArrayCn]) : } publ ic: char charArrayC100]; pr i vate : class CharArrayHoIder:pubI ic ArrayHolder { arrayho 1 der. h ” #include して独立させ , List 3 のようにインクルード することて , このクラス ArrayHolder は , 再利用が可能になります。 これは , クラスをそのまま完結したもの として使っており , ューザプログラムては , まったく手を加えていません。つまり , 閉 じた再利用を行っているということになり ます ( 閉じた再利用については前号を参照 ) 。 C 十十におけるクラスとは , C 言語の関数 をクラスに拡張したプログラム群です。よ って , このように C 言語のライプラリと同様 の再利用が可能てす。 しかしこれては , C 十十やクラスをあえて 用いる必要はありません。この例ては , ク ラス ArrayHolder を用いているというより も , 関数 at( ) を使っているという感覚て す。いうなれば , こうした閉じた再利用は , C 十十のライプラリの使い方としては , 極め てつまらないものてす。 C 十十のクラスライプラリの利点は , この 閉じた再利用がてきるということだけては ありません。 C 十十は , クラスを用いて , 変 数の再利用もてきる , つまり開いた再利用 が容易にてきるのてす。 main() Char ans; CharArraYHOIder anArray; ans =anArray. func ( 1 ) ; C 十十のクラスは , 変数 ( = メンバ変数 ) と , その変数に対する操作関数 ( = メンバ関 数 ) を持ちます。クラスにおいて , もっとも 重要な機能は , クラスを派生して既存の変 数と関数に変数や関数を追加し , 新しいク ラスを定義てきるという機能てす。こうし て作られるクラスの連鎖をクラス階層と呼 びます。クラス階層においては , いくらク ラスを派生しても , 既存のクラス群には , まったく影響がありません。このクラスの 派生によって , 開いた再利用が可能となる のてす 0List 4 を見てください。これは , 先 ほどの ArrayHolder を基本クラスにして , メンバ変数として char の配列を持った , Ch arArrayHolder というクラスを派生させて います。そのために , 基本クラスの定義が 行われているヘッダファイルをインクルー ドしています。 ′クフスライプラリの実際 さてここて , 開発の現場て使われている クラスライプラリを例にして , クラスを用 いた再利用の実際について考えてみましよ スタートアップ C 十十 最終回 こて取り上げるのは , おもにワーク 義ファイルは , ヘッダファイル ( * * * . h) る必要があります。そこて , このクラス定 ように , このファイルがインクルードされ ログラムから再利用するためには , 前述の こて定義したクラスをユーザ作成のプ す。 り , 1 クラスが 1 ファイルに定義されていま れるクラスの数と同数分存在します。つま きます。これは , クラスライプラリに含ま フェイスを定義したものと考えることがて、 義したものて , オプジェクトの外部インタ 変数と , その変数に対する操作手続きを定 向の立場からみると , オプジェクトの内部 成されています。これは , オプジェクト指 トタイプ宣言したメンバ関数群によって構 体的には , クラスの内部変数の宣言とプロ ラリを構成するクラスが定義されます。具 まず , クラス定義ファイルては , ライプ 成り立っています。 ァイル ( * * * . o) の , 3 種類のファイルから ル ( * * * . c) , オプジェクトプログラムフ ァイル ( * * *. h) , メンバ関数定義ファイ このクラスライプラリは , クラス定義フ なります。 加える開いた再利用も , 可能ということに 用する閉じた再利用も , 派生クラスをつけ す。もともとは , UNIX のユーザ団体て、あ そのクラス階層の一部を Fig. 1 に示しま くつかの拡張を行ったものてす。 SmalltaIk ライクなクラスライプラリに ted Program Support (OOPS) と呼ばれる る , USENIX て、配布していた Object-Orien す。もともとは , Unix のユーザ団体て、あ SBN 0471 92346 X)<, 解説されていま ect-Oriented Programming in C 十十』 (I rlen らによる「 Data Abstraction and Obj す。その詳細については , 作者の K. E. Go ealth の略て , フリーソフトウェア扱いて、 変わった名前は , NationaI lnstitute of H 用いた経験のあるものて、す。 NIHCL という プラリて、 , 筆者らが実際の開発て、しばしば プラリのひとってある , NIHCL クラスライ ステーションて使われている C 十十用のライ として作成されています。 スタートアップ C 十十 107
C あ磁 り返しますと , 上記の① ~ ⑤は , 「宣言が定 のバグは , 上記の③と関係しています。繰 rlandC 十十が見逃さなかったソースコード そうすると , Turbo C 十十が見逃し , Bo ⑤その宣言が typedef 宣言であるとき き ④その宣言がクラス名の宣言であると データメンバの宣言であるとき ③その宣言がクラス宣言の中の static な 体でもないとき ていて , イニシャライザでも関数本 ②その宣言に extern という指定子力寸い 記述なしで宣言しているとき ①その宣言が関数を , 関数本体の明細 ある。 の各場合を除いては , 同時に定義でも プログラムに導入する。宣言は , 以下 宣言は , ーっまたはそれ以上の名前を レンジ ] 。 れています [ 以下 ARM の文を岩谷が若干ア e ManuaI) の 3 章 1 節は「宣言と定義」と題さ ARM (The Annotated C 十十 Referenc ことを踏まえて以下を読んてください す。今 , 話題はたまたま C 十十てすが , この は C に対しても妥当 , と考えることがてきま ら , C 十十のリファレンスマニュアルの一部 C は C 十十のサプセット ( 部分集合 ) て、すか C 十十は C のスーパーセット , だから逆に 義て、はないケース」てす。 ては , 定義とは何か [ 以下は ARM の 7. 1.1 の一部 ] 。 136 C MAGAZINE 1 2 4 ARM 3.1 によれば , Fig. 1 のようなもの 体 ) が生じていません。 に , 物理的な実体 ( すなわちメモリという肉 て登録するだけて、す。宣言て、は , まだ名前 ある名前をプログラムが使用する名前とし 宣言は , 上の引用文て、お分かりのように パイラに起こさせます。これと対照的に リ確保という , 物理的なアクションをコン すなわち定義は , その名前のためのメモ 適切な初期化が行なえるようにする。 定義は , 適切な量のメモリを確保させ , が定義だそうて、す。そして ,Fig. 2 のような ものは宣言だそうて、す。 クラスの static メンバの 論理的な意味 以下 , ARM には約 80K バイトの大きさの 正誤表があるそうてすし , 私はそれをまだ という前提て、読んてください 見ていない [ 以下は ARM 9.4 「 static メンバ」からの部 分引用 ] 。 クラスのデータメンバまたは関数メン バは , クラスの宣言の中で static と宣言 できる ostatic なデータメンバは実体が ーっしかなく , プログラム中のそのク ラスのすべてのオプジェクトが共有す る。グロー / ヾルなクラスの static メンバ は , 外部リンケージを持つ。クラス宣 言の中の static なデータメンバの宣言 は , 定義ではない。定義は , 別途必要 である。 ローカルなクラスの static メンバにはリ ンケージがなく , クラス宣言の外では 定義できない。したがって , ローカル なクラスは static なデータメンバを持て static なメンバ mem は , クラス cl のオプ ジェクトが一つも作られていなくても 存在する。 static なメンバは , それらを ( ファイル スコープ内で ) 初期化できる点を除いて は , 通常のクラスメンバのアクセス規 則に従う。 そろそろ , 読者各位の頭の中も , 私同 様 , ? ? ? してきたのて、はないてしようか。 上の , 「・・・・・・定義は , 別途必要てある」まて はまあよいて、しよう onantoka. h の中てく日 常言語の意味て、 > 定義〃するクラス ( C / C 十十語としては旦するクラス ) は , グ ローバルなクラスてす。 ところが , ローカルなクラスは , ローカ ル変数と似ていて , 関数定義の中て、宣言す るのてす。だから実体がすべてスタック上 に一時的に作られるのて、 , static なメンバを と言っているのて、しよう。嘘や 持てない , それは宣言やから , なんも実体はて、け へんはずやー C てプログラミングするとき , ある関数の 中のデータて、 , 複数回のコールに亙ってず っと存在してほしいもの ( その値の変化など を利用したいもの ) は , static と宣言しま す。そのデータは , 残念ながら関数スコー プにはならず , ふつうの static と同じファイ ルスコープにしかなりませんが , とにかく , auto 変数のようにコールのたびに新たにス タック上に作られるのて、はなく , プログラ ムの全寿命と等しい寿命を持ち , 値やその 変化を維持します。そういうデータの , い ちばん分かりやすい例は , 、、その関数がコー ルされた回数をカウントするカウンタ変数〃 て、す。 ローカルなクラスの , 宣言て、はなくて , 定義に関してはどうなるのて、しようね。 いずれにしても , グローバルなクラスの 場合と同じ意味合いの static を認めたってと くに問題ないし , 処理系に大きな負担には ならないのて、は , と思いますがね。 そして , そのローカルなクラスが , 別の グローノヾルなクラスを基本クラス ( べースク ラス ) としていて , 後者に static なメンバが あったときは , それに対して , どんな規則 が適用されるのて、しようか ( まあ常識的な勘 て、は , その static はサポートされると思いま すが , とにかく具体的な処理系を動かして みんと分からんて、す。今は , その暇なし ) 。 「 static なメンノヾ mem は , クラス cl のオプ ジェクトが一つも作られていなくても存在 する」というくだりも , プログラムの問題ド メインのタイプによっては , 面倒を引き起 こしそうて、す。 現実世界には , 複数の同種オプジェクト が共有する唯一の何かが , しかし , オプジ ェクトの存在以前にそれだけが存在してい ることは , 論理的に , または現実的に , お というものが多くあ かしい・ありえない
epointer だから , fp て、す。ただし , しばし ばファイルを扱うプログラムを書く場合に は , ふたつ以上のファイルを扱いたくなり ますから , fp だけを使っていては混乱するか もしれません。 fp in, fp out のような書き 方はよく見かけます。 int fd ; ファイルディスクリプタの意味て、しよう。 int * p , 使い捨てのポインタとして pointer を連想 させる p を使います。これがたとえば long へ のポインタだと , lp となることが多いようて す。 Size t Size , 文字どおりサイズて、す。何のサイズかと いうと , おもに malloc のようにメモリを獲 得する関数への引数として使われますが , それ以外の用途にもよく使われるようてす。 int status , ステータス , すなわち状態て、すが , 何の 状態かというと , しばしば関数の戻す値 , もしくは exit の引数として使われます。 int temp , 次に問題になるのは , て、は , 自分が勝手 述べました。 のように使うのがよい」という方針に関して 自分勝手に使わないほうがよい」 , または「こ というわけて , 今まて、 , 「こういう名前は 印象的に登場するこど あるのて , 参考にするとよいと思います。 と , 引数の説明として使われている名前が このほかに , C の関数マニュアルを見る て , しばしば用いられます。 ムの中て、はなく , 説明のための関数名とし これらの名前は , 実際のソースプログラ intfoo(), bar() ; これは明らかに一時変数て、しよう。 66 C MAGAZINE 1992 4 単て、すが , 「 ~ するのがよい」というのは説 一般に「 ~ してはいけない」というのは簡 るのか , ということて、す。 に定義したい名前について , 何か指針があ 明困難になりがちて、す。あえて , どのよう な名前をつけるべきかという指針について 考えてみたいと思います。 言て、説明すれば , 「わかりやすい名前を つける」が大原則て、す。これだけて、も本質的 に理解していれば , あとは感性に正直に名 前をつけても , 相当変な人て、ないかぎり , 何も問題になりません。ただ , 驚いた に , わかりやすい名前にしようとさえ夢に も思わない人が結構いるように思えるのて、 すが。もうひとつは驚くには値しません。 どうすればわかりやすい名前になるのか , 理解て、きない場合て、す。 て、は , いったいどのような名前をつける とわかりやすいのて、しようか ? これは , 経験を積むに従って , 自然に身についてく るものだと思います。 まず考えておきたいのは , その名前を見 ただけて、 , 働きが一目瞭然て、あることて、す。 xc という名前が何を意味するのか , わかる 人はほとんどいないて、しよう。これが , tra nsfer count という名前だったら , 「何か転送 するとき回数が入っているのだな」程度は想 像て、きるのて、す。よくいわれることて、すが , こて、重要なのは , その意味を的確に表現 する名前を用いるということて、す。これは もっともなことてあり , 探せば多くの本に 書いてあるはずて、す。 フィンローダ流に関しては , もうひとつ 重要なことて、 , かっ , ほかの本にはあまり 書かれていないことがあります。それは , 「印象的な名前をつける」ということて、す。 ばっと一目て、気に入ってしまうような名前 がついたら成功て、す。なぜそれが本に書か れていないかというと , じゃあどうすれば 印象的な名前がつけられるんだ , といわれ たとき , そんなことは言葉て、説明がて、きな いからて、す。 名前き方 現状て、は , 日本語の名前が使える C コンパ イラは , あまり見かけないし , ANSI の規格 どおりなら , 当然日本語の変数名など考慮 されているわけがないから , いわゆるメジ ャーなコンパイラについては , 英数のみ使 えると考えておきます。 まず , 英単語を名前に使うとして , 省略 するか , 省略しないかという選択がありま す。例を見るのが手つ取り早いて、しよう。 char * file name ; / * 省略しない * / char *fn / * 省略する * / 省略しないメリットは , 意味を誤解する 確率が小さくなることて、す。 fn と書くと関数 名のことかと誤解するかもしれませんが , file name を見て関数名かと思う人はまずい ませんし , これを見た人は必ずファイル名 て、あることを理解て、きます。 て、は , 省略するメリットは何て、しようか。 ひとつは , あまりにもしばしば使われる変 数に長い名前をつけてしまうと , 入力する のがたいへんだということて、す。 ある程度長い名前になると , 開発環境に よっては cut&paste のような方法て、 , 文字列 コヒ。ーがて、きるのて、 , そのほうが間違いが 少なくなります。長い名前はタイプ時に間 違いがちなのて、嫌だという人がいますが , これはある意味て、危険て、す。タイプ時に間 違う確率は , 何文字あたり 1 回というものて、 あって , 短い変数て、も間違えるときは間違 えるものて、す。間違えたとき , どの程度の 影響があるかということも検討しておくべ きて、す。短い変数をいくつも使っていると , タイプし間違えたとき , 偶然別の変数にな ってしまうことがあります。この原因によ るバグは , 一度コンパイルに通ってしまう となかなか発見て、きません。長い名前の場 合 , 間違えてタイプしたら , そんな変数は ないとコンパイル時にはじかれる確率が高 くなり , その意味て、は安全かもしれません。 しかし , 長い名前にもデメリットがあり ます。あまり長い名前だと , 肝心の処理の 流れを理解するための思考を妨げる危険も あります。名前を追いかけて最後まて、読ん て、 , ああこれはあの変数だな , と思ったと きは , 今 while のループ条件を調べていたの か , if の判断の途中だったのか忘れてしまう かもしれません。という例はさすがに大袈
C プログラミング * / の中には何を書いてもか は , こうして用意した変数に 123 ー 45 ー 67 十 ては , / * 言算のルール 89 の計算結果を代入する命令て、す ( 式そのも まいません。普通はプログラムの名前や解 のを代入するのて、なく , 計算結果 100 を代入 説などを書いておきます。コンパイラはこ C 言語て、は , 足し算 ( + ) と引き算 ( ー ) は通 します ) 。 の部分を無視します。注釈はプログラムの 常の数式どおりの記号を使いますが , かけ算 その次の printf て、 x の値を出力していま 途中にあってもかまいませんし , 複数の行 ( x ) と割り算 ( + ) は記号がキーポードにな にわたってもかまいません。 す。 いのて , 「 x 」は「 * 」 ( 星印 , アスタリスク ) , printf( … ) の中身カ市 d10. c のときと少し変 数の範囲に注意しよう 「 + 」は「 / 」 ( 斜線 , スラッシュ ) て代用しま わっていますね。コンピュータはこの中の 123 ー 45 ー 67 十 89 たとえば 〃の中 数学て、いう整数は , どんなに大きくても をまず計算し , その答え 100 を かまいませんが , コンヒ。ュータて、は , 扱え の「 % d 」の部分と置き換えて出力します。 7 十 6 ー 5X4 十 3 を計算して出力する命令は このように , printf の中の「 % d 」は特別な る数の範囲にかぎりがあります。 printf("%d*n", 7 十 6 ー 5 * 4 / 3 ) ; 意味を持っています。「 %d 」の d は decimal 先ほどのプログラムて、使った int 型の変数 に代入て、きる値の範囲は , LSI C ー 86 を始 て、す。 ( 10 進法 ) の頭文字て , 整数の値を 10 進法 ( 通 計算は , 通常の数式と同様に , かけ算と め , 多くのパソコンの C 言語て、は一 32768 以 常の数の記法 ) て、出力することを意味しま 割り算を先に , 足し算と引き算を後に行い 上 32767 以下に限られています。 す。 ます。したがって , 上の計算はまず 5 * 4 / 3 もっと大きい整数を入れる変数を作るた なお , 「整数」とは , 小数点以下のない数 を行い , その結果を 7 十 6 から引き算します。 めには , int の代わりに long ( または long て、 , たとえば 12345 やー 321 や 0 が整数てす。 C 言語て、は , 足し算・引き算・かけ算は算 int) としなければなりません。 これに対して , 3.14 のように小数点以下の long 型の整数の範囲は , 通常ー 21474836 数と同じルールて、行いますが , 整数同士の ある数は , 実数といいます。 48 以上 , 2147483647 以下て、す。 割り算に限って , 小数点以下を切り捨てて 変数を使ってみよう long 型の値を printf て出力するには , 「 % 整数て、答えを出してしまいます。したがっ て , 5 * 4 / 3 すなわち 20 / 3 は 6.66666 ・・・てはな d 」の代わりに「 % ld 」を使わなければなりませ く 6 となり , 数値は printf て出力するだけて、なく , 変数 ん。 printf ("%d*n", 7 十 6 ー 5 * 4 / 3 ) ; このへんの事情は次号以降て詳しく説明 というものに蓄えておくことがてきます。 の出力する答えは 7 て、す。 します。 この変数は , 電卓のメモリ機能のような ものてす。数値や計算結果を変数に入れて 計算した結果を出力するプログラム ( keisanl . c) おけば , あとてまた表示したり別の計算に 使ったりすることがてきます。 変数を使えば , 先ほどの 123 ー 45 ー 67 十 8 9 を求めるプログラムは List 4 のように書け ます。 最初の intx; は「整数を蓄えておくための 変数 x を用意せよ」という意味の命令てす。 int は整数を意味する英語 integer を略した語 てす。こうして用意した x を「 int 型の変数」と 呼びます。変数を用意するための命令は中 カッコ { } の最初に書かなければなりませ ん。プログラムを読みやすくするために 変数を用意する命令の後には , 空の行をひ とつ入れる習慣をつけましよう。 次の 123 ー 45 ー 67 十 89 ; X す。 List ・・注釈 ・・・入出力の準備 ・・・以下がプログラムの主要部分 、・・ 123 ー 45 ー 67 十 89 の計算結果を「 % 山 の部分に出力 ・・正常に終了したという合図 1 : / * keisanl. c * / 2 : #include く stdiO. h> 3 : main() printf(" 答えは Xd です。 *n", 123 ー 45 ー 67 + 89 ) ; 5 : return 0 : 6 : keisanl . c を変数を使って書き直した例 ( keisan2. c ) List ・・注釈 ・・・入出力の準備 ・・・以下がプログラムの主要部分 ・・・ int 型 ( 整数 ) の変数 x を用意 ・・・ 123 ー 45 ー 67 十 89 の値を x に代入 ・・・ x の値を「 % d 」の部分に出力 ・・正常に終了したという合図 1 : / * keisan2. c * / 2 : #include く stdio. h> 3 : main() int x; 5 : 6 : = 123 ー 45 ー 67 + 89 : X printf( ”答えは Xd です。 }n", x) : 8 : 9 : return 0 ; 実践 C プログラミング入門 103
アレゴ Fig. 1 可変長文字列の管理 丿ズ ァータ構造入門 (a) 変数 a には、、 h 0 " 00 変数 b には、、 wo 日 d " がセットされている free ptr 0 W 0 「 (b) 変数 a に。 byebye " を代入し , 以前の値、、 hello " は不要となる 00 00 0 W 0 「 d b y e b y e freeptr (c) ゴミ集めを行い , 最初の a の値。 h 0 " は不要なので削除された ング言語の処理系て、も , W 0 「 d b y e ごみ集めによって 文字列領域を管理します。 セルが「使用中』てあると定められていま Lisp< は , 大もとのセルから到達可能な isp のセルは , 二分木のセルと同じ形てす ) 。 に再びセルへのポインタになっています (L こて , ふたつのポインタ car, cdr がとも struct cell * cdr , struct cell * car : struct cell { C て表現すると次のようになります。 からなるレコードになっています。これを Lisp の場合には , セルはふたつのポインタ かの見分けがつけられなければなりません。 の方法て、メモリ領域が使用中て、あるかどう ごみ集めを実現するためには , なんらか free ptr おくという考えもあります。しかしほとん て , そのぶんのメモリを変数に割り当てて きません。もちろん , 最大長を決めておい からないのて , そのまま格納することはて これに対して可変長の文字列は , 長さがわ は , 変数にそのまま格納することカイきます。 浮動小数点数などの長さが固定長のデータ の扱いについて考えてみましよう。整数や 次に BASIC, awk, perl の可変長文字列 して再利用します。 ないことになるのて , これらのセルを回収 点て , 印のついていないセルは使われてい いきます。セルをたどる処理が終了した時 て、す。このとき訪問したセルに印をつけて ての到達可能なセルを訪問することが可能 car と cdr を再帰的にたどっていけば , すべ す。したがって , 大もとのセルからメンバ どの場合 , 文字列の長さは最大長と比べて かなり短かいのて、 , ムダになる部分が多く なります。たとえば , BASIC て、は最大長 25 5 文字て、すが , 平均的な長さはせいぜい 2 ~ 30 文字くらいて、しよう。また , 仮にムダに目 をつむったとしても , perl のような長さ無制 限の文字列を実現することは不可能てす。 可変長の文字列を表現するには , 文字列 の実体をヒープの中に置いて , 変数には実 体を指すポインタを入れることにします。 これは , 文字列に限らず , 可変長のデータ アルゴリズムとデータ構造入門 77 ち , Fig. 1 ( b ) の hell のように , 使われて こて , ごみ集めを行って , ヒープ領域のう プへコピーてきなくなってしまいます。そ て消費してしまい , これ以上文字列をヒー にしておくと , いつかはヒープ領域をすへ さて , このようにヒープ領域を使い捨て 放置しておきます。 いたヾ he110 〃は不要になりますが , そのまま bye 〃の次を指します。変数 a が以前に指して すようになります。ポインタ freeptr はヾ bye ピーされ , 変数 a のポインタが、、 byebye 〃を指 す。ヒープの最後尾にヾ byebye クの 6 文字がコ う値を代入すると Fig. 1 (b) のようになりま しています。ここて , 変数 a にヾ byebye クとい tr は , ヒープのうち未使用の部分の先頭を指 長さがセットされます。またポインタ freep 変数には文字列の実体へのポインタとその 長さを陽に持つようにしています。つまり , が , ここて、はヌル文字を使わずに文字列の 字 ( \ 0 ) を置いて文字列の最後を示します がセットされています。 C て、は末尾にヌル文 b にそれぞれ he110 ク , 、、 world クという文字列 たとえば Fig. 1 ( a ) ては , ふたつの変数 a と 捨て』にするわけて、す。 にします。つまり , 文字列の実体は「使い 中に文字列の実体を作ってそれを指すよう を書き換えるのて、はなく , 新たにヒープの 数に値を代入するときには , 文字列の実体 順に文字列の実体を格納していきます。変 列になっていて , 先頭から末尾に向かって を扱うときの定石てす。ヒープは大きな配
N mo 化 あれば , その領域を一気にフロッピーディ されている。そして「現在宣言中の構造体は void clear x tO z(void) スクに書き込んてやるだけてよく , コーデ incomplete type て、ある」とされている ( とも イングが楽になるからてある。 に ANSI 3.5.2.1 ) 。宣言中の構造体は , 対 memset ( 0 , (size t) ()w ー ) ) ; ところが , 残念ながら C て、は関数の外部て 応する閉じプレースリクが出現して初めて不 宣言された静的変数のメモリ上の配置に関 完全型て、はなくなる。このため , 現在宣一 この問題を回避するひとつの手段が , 連 しての規定は何もなく , 隣あって宣言され 中の構造体て、ある「自分自身」は incomplete 続したメモリ上に配置されることが要求さ type て、あるから , 必然的にメンバとして含 た変数が実際にメモリ上に隣あって配置さ れるすべての変数をひとつの構造体にまと れる保証はない。 めてしまうというコーディング技法て、ある。 むことはて、きない 前回も述べたが , 構造体の内側て、宣言され なお , 自分自身と同じ型の構造体へのポ int X ; int y [ 20 ] ; インタはメンバに含めることがてきる。ポ たメンバは宣言順に順次アドレスの昇順に int z [ 10 ] [ 5 ] ; インタてあれば , そのポイント先の型は不 メモリに割り付けられていくことが保証さ れている。またビットフィールドの場合に 完全型て、あっても ( 取りあえずポインタ変数 int W ・ このようにして , 宣言した 4 個の静的変数 を宣言するには ) , 問題はない ( この例は前 は各ビットフィールドを割り付けた記憶単 x, Y, z, w があった場合 , これらがメモリ 位を基準として , やはり全体としては宣言 回示した ) 。もちろん , そのポインタを用い 上にこの順序て配置されるとはかぎらない 順にメモリに割り付けられていく (ANSI て実際にアクセスを行うステップがソース 中て、登場する時点まて、には , 具体的な型の 実際に , かなりの数のコンパイラは配列 3.5.2.1 ) 。 このようにすればソースレベルて、メモリ 変数と非配列変数をそれぞれグルーヒ。ング 情報が与えられていなければならない への割り付け順序を制御可能になる。これ してメモリに割り付けようとする。そのよ び構造体の うなコンパイラて、は , x と w は直接隣あって がいちばん移植性の高い解決方法て、あると メモリ配置に関して メモリに割り付けられる。それにも関わら 思われる。 こていう移植性とは , 別なハードウェ ず , x, y, z を一括してゼロクリアするため 機器組み込みのプログラムなど , グロー アの上て動作させる , という意味て、はない に次のような乱暴な手段をとっているのを バルな変数をメモリの特定の番地から特定 組み込みプログラムては , 小さなモジュー たまに見かける。これはたいへん危険なコ の順序て割り付けたいなどという要望が出 ルなら話は別だが , ハードウェア構成の異 ーディングてある。うまく動くコンパイラ ることがある。たとえば , あらかじめ定め なる機器にそのままソースレベルて、移植す もあるかもしれないが , コンパイラが変わ られた複数の変数の値を , フロッビーディ こては , む ったらとたんにバグを生み出す原因になる。 る可能性は低いからてある。 スクなどに記録して , 別なマシンて読み出 しろ使用しているコンパイラのべンダが変 #include く string. h > したりしたい場合に , 複数の変数の割り付 更になったり , コンパイラのバーションア けアドレスがメモリ空間の連続した領域に ップが行われたりして , メモリ割り付けに 変数をひとつの構造体にまとめる 関するポリシーが変更になった場合への防 衛策てあると考えている。ただし , この場 合ても構造体の内側に穴がてきるかもしれ ことは記憶しておくべきてある。 この技法を使うと , 先のシーケンスは Li st 5 のように書き換えられる。なお , この例 にかぎっていえば , offsetof (GlobaIs t, x) は 0 てあるから , memset の第三引数の部分 は単に offsetof (GIobals t, w) てもかまわな い。けれども , 後々のことを考えれば引き 算の形て書いておくほうが安全てあろう。 List 1 : #include く string. h> 2 : #include く stddef. h 〉 3 : 4 : typedef struct g10 ls { int x; int y [ 20 ] : int z [ 10 ] [ 5 ] : 7 : 8 : int 響 ; 9 : ) G10 ls ー t : 11 : G10 ls ー t G; 13 : void clear—x—to—z(void) 14 : { memset(&G, 0 , 0ffset0f(GlobaIs—t, の - offsetof(Globals_t, 16 : } ANSI C ー more 125
特集℃プロクラミンクの秘 裟すぎますが , 名前が長すぎると , かえっ といえばそうかもしれません。 て全体が見にくくなる可能性があるのは確 しかし , フィンローダ流は②て、す。②の かて、す。そこて、 , 妥協案として , あまり長 長所はふたつあります。 ここに出てくる c という非常に短い名前を い場合には省略する , という方法が考えら まず , すべて小文字て書かれていること 持った変数は , 伝統的に getchar のような 1 て、す。①の場合 , どこを大文字にしたか忘 れます。これも悪くはありません。 文字入力の結果を保存するために使われま れることがあるかもしれませんが , ②て、は す。 c という文字が , character を連想させる char * fname , / * ちょっとだけ省略する * / それはありません。ただし , FileName と書 のて , この場合 c という名前が使われるのて、 この方法の場合 , 長すぎもなく , 全然わ いたか FiIename と書いたかわからなくなる しよう。ただし , c の型は int< あるのが普通 からないほど省略するのて、もない , ように , filename と書いたか file name と書 なのて、 , 注意してください。初心者の中に 加減に成功すれば , かなり効果があると思 いたかわからなくなることはあるのて、 , ② は , これをうつかり char にしてしまうこと います。ただ , ひとつ気になるのは , 経験 のように書いたほうが混乱しないと一概に が多いようて、す。この変数は , 伝統的なの 的に , どの程度省略したのか , しばしば忘 はいえないかもしれません。 て、 , 短いながらもまったく無意味て、あるわ れてしまうということて、す。たとえば , 私が②の書き方を気に入っている理由は , けて、はありませんが , この種の使い捨て変 時ファイルの名称を保存したい変数の名前 単語と単語の間が , アンダースコアという , 数は , その場かぎりて、処理が完結するとい を , tempfile としたのか , tmpfile としたの 見た目がかなり空白に近い文字て、区切られ う意味て、 , 変数名に意味を持たせなくても , か , プログラムを書いている途中て、すら忘 ているため , ばっと見たとき読みやすいと 理解の妨げにはならないのて、す。これに対 れてしまうことがよくあります。 田われることて、す。①の書き方は , 確かに して , 複雑な処理を行う関数の名前や , 大 フィンローダ流は , 慣習として省略した 大文字を認識して単語を分解することが可 局的に使われている変数の名前は , て、きる 書き方が固定したものて、なければ , て、きる 能て、すが , ばっと見たとき大文字小文字を かぎりその意味を表すような名前をつける , かぎり省略しない , というものて、す。 区別するほうが , アンダースコアよりも一 というのがフィンローダ流て、す。要するに ただし , 以上の議論は , 継続的に意味を 瞬手間がかかるように感じます。 長さにはまるて、無頓着なのて、す。 持ち得る変数の場合のことて、あり , 使い捨 とくに , 後者は , わかりやすさに影響し さて , て、きるかぎり省略しないとなると , ての変数はこのかぎりて、はありません。そ 場合によっては異様に長い名前になってし てくると思いますのて、 , フィンローダ流と の中にも , ある程度は慣習的に決まった名 まうことがあります。この名前が , 頻繁に しては②を選択することにしました。 前がありますのて、 , それらが使える範囲に ただ , 場合によっては , ②の短所はもう 使われるものて、あると , かえって名前を読 おいては , ありがちな名前を使っておくの むのに労力を使ってしまうことも考えられ ひとつあります。アンダースコアが比較的 が無難な選択だといえましよう。 入力しにくいキーに割り当てられているキ ます。また , プログラムの見栄えも超高層 ーポードがあることて、す。 ビルが乱立したかのごとくなってしまい , あ スタイル まりおもしろくないかもしれません。 長い関数名について , 本誌 ' 90 年 8 月号の 「恥ずかしながらドジりました」に興味ある 流儀としては , 大きく分けてふたつに分 変数には , 固定した意味が与えられ , プ かれます。ここて、は , 英単語を組み合わせ ことが書かれていたのて , これについて意 ることによって名前をつけることにします。 ログラム , または関数の処理が終わるまて、 見を書いておきたいと思います。手元に 8 月 ① SendMessage その意味を持ち続けるものと , いわゆる使 号をお持ちて、ない方のために , 岩谷氏の主 い捨ての一時的な変数とがあります。一時 このスタイルは , 単語の先頭の文字だけ 張を簡単に説明しておきますと , を大文字にして , 単語をびったりとつけた 的変数としては , 関数の戻り値を取り敢え 「 GU 扱うプログラマは , ものすごく長 ものて、す。 ず入れるとか , ループのカウントに使われ い名前の関数を大量に覚えなければなら ( ② )send message るものがあります。 ない。たとえば , AppJugemJugemuG0k getchar( ) ) ! = EOF) { こちらは , 小文字て、書いた単語をアンダ while ( (c oNoSurikirePaipoPaipo( ) が何をすみゆ ースコアて、くつつけるというものてす。 X 、 Mndow 八ンドブックでみつけた関数 私の経験て、は , ①を好む人が多いような 気がします。理由は , ②のほうが , 名前が 長くなってしまうからて、はないかと思いま す。長い文字をタイプするほうがめんどう 名前さ List XSetWindowBackgroundPixmap(display, window, background—pixmap) : 特集 c プログラミングの秘訣 67