使っ - みる会図書館


検索対象: 月刊 C MAGAZINE 1992年2月号
152件見つかりました。

1. 月刊 C MAGAZINE 1992年2月号

吾 形 山 三田典玄のランダムアクセス , の - つ 0 0 っ八。。 は PC コンパチ機の VME ポードなどという変 なものも出ている。普通のデスクトップの PC 上て、プログラムを開発し , 制御用の VM E*—ドのところに , て、きたプログラムをフ ロッヒ。ーて、持っていき動作させる , という ものて、ある。これならば , わざわざ高価な 開発専用パソコンを制御機器内に置いてお く必要もない とにかくパソコンは生き残るが , これか らも今と同じ形て、ということは絶封にない といってよいだろう。 こまて、書いて思い出したが , 実は UNIX 自身も PC 用の UNIX を FA 用のコント んて、みることをおすすめする。同じ C 言語て、 ローラに使っている例はかなりたくさんあ ほとんどキチガイというに等しい投資て、あ もこんなに違うものか , と , その環境の「差」 る。 UNIX て、もリアルタイム系と呼ばれる一 ると , 自分て、は思っている。懐古趣味はな 連の製品は , メモリ常駐プロセスが作れる によるプログラム開発の効率の違いや , そ いのだが , やはり指がよく動くほうがいい のて、 , 何らかのイベント ( 外部事象 ) が発生 のフェイルセーフのよさに驚くことだろう。 に決まっている。効率を重視するモノ書き したときに即座に反応することがて、きる。 こうて、なければ C 言語て、のプログラムの開発 などて、はなおさらだ。「慣れ」の問題はやは いまなら Sun などて、も同様のシステムコール なんて恐くてとても , と思うようになるは り大きい があるが , 昔 ( といっても数年前 ) まて、はリ ずて、ある。 UNIX なら EMS なんていらない。実メモ アルタイム系 UNIX とそうて、ない当たり前の 開発する側から見ると , 「もう MS-DOS に リをたくさんとっておけばよい。 OS が勝手 は戻れない」ということになるはずて、ある。 UNIX を分けていたものだ。 にアプリケーションのプログラムエリアや こう考えると UNIX もコントローラ系 UN データェリアに割り振ってくれる。通信ソ パソコンは生き残るか IX と OA 系 UNIX というものが出てくるだろ フトだって , もう割り込みがどうのこうの うことは十分予想される。何だかいつのま と気にする必要はない。メモリの制限だっ にか話を聞かなくなった某省庁の , どこか て , それによってアプリケーションが動か しかし , そうはいっても FA のコントロー の映画のタイトルのような名前をもったプ ない , なんてことは少なくともない。て、つ ラのような用途ならばパソコンは生き残る ロジェクトみたいだが , 事実そうなってい かいウインドウシステムは本当に「お利ロ」 だろう。あの CP / M80 なども用途を限った「専 くだろう。 だ。メモリをおおぐらいするだけの仕事は , 用機」として生き残っているくらいだから , やはり同じように PC も生き残る , というこ とりあえずちゃんとしてくれる。 マルチタスク , マルチューザ , LAN 接 とは十分考えられる。しかし OA 用途て、はや とくにプログラムを組むときには , MS- DOS ほどの職人芸は必要ない。とにかく書 という予想が立ってし 続 , 巨大なメモリ空間 , 速い CPUO パソコ はり生き残らない まう。 ンだ , ワークステーションだ , などと分け けば動くし , とんてもなくおかしなプログ て呼んて、いるといつのまにか時代遅れにな ゲームという用途に限っても , こちらは ラムはまず , OS がちゃんと止めてくれる。 専用ハードウェアのほうがいいに決まって ってしまう。いまや , どちらも同じものな C 言語なんて原始的な言語は , こういう「必 のだ。唯一 , 動いている OS が違うにすぎな 要なものが揃った」オペレーティングシステ ムてこそ , 開発されていなければ不安てし もちろん現実にパソコンのほうがいい 来年のいまごろ , ひょっとすると私はそ という用途もある。ハードウェアを直接い ようがない というのが実感なのだ。パソ ろそろこう言い出しているかもしれない じって接点の制御をする , とかいう用途に コンしかさわったことのないプログラマの 「さようなら。パソコンの時代」 方には , ぜひ UNIX 環境を手に入れて使い込 は PC は向いている。実にアメリカなどて 0 つ 0 0 0 0 0 NIX 三田典玄のランダムアクセス 101

2. 月刊 C MAGAZINE 1992年2月号

何ということだ ! 今回は数の問題だっ たにもかかわらず , 解答の応募が全部て 10 通だけとは ! しかも , その中て、不正解を 除くと 8 通しか残らないてはないか。 「正解者からのメッセージ」を含めると , 毎回掲載されるのは 4 ~ 5 人。ということは , 今回の正解者にとって掲載される可能性は 5 割以上ということになる。それて、も掲載さ れなかった方 , 運が悪かったと思ってメゲ ずにまた応募してください 今回の応募のほとんどが常連の方々て , こういったときにはとても頼りになり , う れしい限りて、ある。 【 1 1 月号のパズル】 次の式を見ると , 左辺はすべて素数で , 素因数分解になっている。左辺をもっとよ く見ると , 1 ~ 9 の数字を 1 度ずっすべて使っ て組み合わせてある。 2 X 7 X 853 X 6491 = 77515522 こで問題。上の例のように , 「 1 ~ 9 の 9 個の数字を 1 度ずっすべて使って組み合わせ て作った素数の積」カ撮小になる組み合わせ を求めてほしい。例では 4 個の素数の積にな っているが , 何個の積でもよい。ちなみに コ″は素数ではない。 ついでにもう 1 問。「 1 ~ 9 の 9 個」ではなく て , 「 0 ~ 9 の 10 個」ではどうなるだろうか。た だし , 0 は各素数の最上位桁にはできない。 1 1 月号の解答 正解は , 150 C MAGAZIN コ 992 2 第 11 回 最小の素数 ? 2X3X5X487X 1069 = 15618090 ・ 0 ~ 9 まての 10 種類の数字を使った場合 2X3X5X67X 1489 = 2992890 ・ 1 ~ 9 まての 9 種類の数字を使った場合 吉柄貴樹 2X3X5 = 30 の倍数だけを調べればよい つ数を求めればよいことになる。つまり , そこて , 2 , 3 , 5 をひとつずっ素因数に持 ずて、ある。 一桁の素数をてきるだけ多く持っているは がかかる。求める数は最小値てあるから , とつずっ調べるのては , 数が多すぎて時間 求めることがてきる。しかし , このままひ ひとつずっ素因数分解していくことにより て、ある。求める数は , これより大きい数を 2 x 3 x 4 X 5 X 6 x 7 x 8 x 19 = 766080 る最小の数は , 1 を 1 桁て使わないとすると , 1 ~ 9 まての 9 種類の数字て、 , かけ算て、てき ( プログラムは付録ディスク PUZZLEOI . C) 森公正さんの解答 井村清一さんの解答を紹介する。 グラムが読みやすいふたり , 森公正さんと は , コメントがていねいに入っていてプロ んのアルゴリズムが似ていた。そこて、今回 解き方は考えにくい問題だったのて , 皆さ 今回は応募が非常に少なく , また特殊な て、あった。 766080 十 30 = 25536 れは , ら ) てよいのて、 , ここては , る数の上限は適当に作った数 ( むろん素数か かどうかを調べることと同じてある。調べ 7 , 8 , 9 を 1 回ずっ使った素数に分解て、きる 数て、もなく 5 の倍数てもない数が 1 , 4 , 6 , なのて , これより大きい奇数て , かっ 3 の倍 2 x 3 x 5 x 41 x 67 x 89 = 7334490 を 30 て割った 244483 を使うことにした。 また , 0 ~ 9 まての 10 種類の数字の場合は 最小値が , 2 >< 3 >< 4 x 5 x 6 x 7 x 8 x 109 = 4394880 sqrt ( 763603 ) = 873.8 ・・・ とした。素因数分解に使う素数は , = 763603 2 x 3>< 5X409 x 1867 ( = 22908090 ) 十 30 より大きい奇数とし , 上限の数は , 4394880 十 30 = 146496 となるのて , 調べる数は , 計算時間はもっと短くなる。 くて済むのて、 , もっと小さい数を探せれば , 数は小さければ小さいほど計算時間は少な にした。なお , このプログラムては上限の 00 未満の素数を発生させ , それを使うこと より小さいものて、よいのだが , いちおう 10 使用機種 OS コン / ヾイラ 実行時間 株マイクロポード SC6000 / H40 ( 68030 / 68882 ) OS-9 68030 V2.5 # 5 Microware System Corp. OS-9 68030 C compiler V3.1 (-k2wf -j -s -f= /r0/facto rize -m= 5k) 1 ~ 9 まて、の 9 種類の数字を使った場合 約 8 秒 0 ~ 9 まて、の 10 種類の数字を使った場合 約 45 秒 参考文献とくになし 井村清ーさんの解答 ( 力ムま付録ディスク PUZZLE02. CPP) 今回は難しかった。

3. 月刊 C MAGAZINE 1992年2月号

質問て、す。なぜあなたは「文字列のコヒ。ー をせよ」という問題て、すぐに for 文や while 文 を使おうと思ったのて、しよう。問題文中に 「 for 文を使え」とか「 while 文を用いよ」と指示 されていたわけて、はないのに・ 。間題文 中に「繰り返し」という言葉が書かれている わけて、もないのになぜ繰り返しの構文を考 えつくのて、しよう。それって不思議じゃあ りませんか ? その秘密を解くために , そもそも文字列 とは何て、あるかを考えてみましよう。文字 列とは文字の集まり , 文字の並びて、す。と くに C 言語て、は連続したメモリ上に格納され た文字の並びを文字列として扱います。文 字列は文字の配列といってもいいわけて、す。 配列は同じ種類のデータが何個も連続して いるものて、す。文字列は文字という同じ種 類のデータが何個も並んて、いるわけて、す。 そう , 「同じ」種類のデータが「何個も」て、す ①「初めの 1 文字」を得る Fig. 3 次の一歩の下準備 得る こが「初め」 ②「初めの 1 文字」をコピーする △ ③次の一歩の下準備をする 旧 ' △ ここが次の「初め」 ね。 とにご注意ください ( Fig. 2 ) 。 こに「繰り返し」が現れてきているこ ログラムを組むことがて、きるようて、す。 識せず , さっと for 文や while 文を使ってプ 慣れている人はこういった思考をほとんど 理にかなったことなのて、す。プログラムに うのに繰り返しの構文を用いるのはとても 配列はデータの繰り返し。それを取り扱 ( 単ょ問題へーよ 文字の繰り返しである。 文字列は ように考えました。 コピーという仕事をさせるのに , 私は次の 先ほどの間題に戻りましよう。文字列の ↓ 文字列のコピーは・・・ ここにコピー △ 文字のコピーの繰り返しになる。 ここが次のコピー位置 △ コピーする 0 に還元するというのはとても大切な考え方 に着目して , 複雑な仕事をより簡単な仕事 こういう関係になるのて、す。データの性質 ピー」という仕事の繰り返して、実現て、きる。 コビー」という仕事はより簡単な「文字のコ て、きている。それと同じように , 「文字列の タて、ある「文字」の繰り返し ( 文字の配列 ) て、 「文字列」というデータはより簡単なデー 何繰り返すのか ? て、す。 て、はいったい何を繰り返えせばいいのて、し が出てくることは納得がいきました。それ あいください。文字列のコピーて、繰り返し さて , 文字列のコヒ。ーにもう少しおっき ・「初めの 1 文字」をコピーする。 ・文字列の「初めの 1 文字」を得る。 ようか。私はこう考えました。 プログラミングの工ッセンス 63

4. 月刊 C MAGAZINE 1992年2月号

五ロ グ ン ラ グ ロ プ 言語を作ることが工数の削減に関わってく に考える。しかし , BASIC て、プログラムを ードとソフトの区分けがて、きるようになる る。また , より使いやすい支援ツールを作 組むときによくつまずくのが , 「このデータ が , BASIC の場合ハードの知識を要求され 構造をどのようにプログラムて、実現しよう 成することによっても工数は減少する。前 ないために利用者はコンピュータというま の項目にあげた変更に柔軟に対処て、きると か」ということて、ある。 とまったカテゴリて、見ることが多かったよ いうことは以前作成したプログラムを生か C て、データの構造体をチェインしていくよ すことがて、きるということて , これて、もま うなプログラムを書いてもそれ程繁雑には た工数を削減て、きる。 ならないが , BASIC の場合はそうはいかな プログラムはスクリーンエデイタで作成す 。配列を利用しながらプログラムて、デー る。そのために行番号を使わない タ構造を実現するはめになる。そうすると CarrotBASIC はビジネスアプリケーショ プログラムは行番号を頼りにした BASIC いくつもの配列を使ってひとつのデータ構 ン開発を目的に作成した言語て、ある。 BAS のエデイタて作成するよりも , 強力なスク 造体を記述することになり , とても視認性 が悪くなる。ほかの言語から BASIC に移植 IC と名乗るにはあまりにも BASIC からかけ リーンエデイタて、作成したほうが遙かに速 するような場合にいちばん問題になるのは いと考えた。しかし , 従来の BASIC のまま 離れていて , 高尚なコンセプトを持って BA SIC を作った Kerz らが見たら怒りだすかもし このデータの扱いて、あろう。データ構造体 て、は行番号が邪魔て、 , 効率よい編集がて、き れない。なにせ , 言語仕様の元は確かに BA ない。そこて、行番号を完全に廃止し , ラベ とローカル変数が使えれば BASIC は見違え ルのみて、制御を行うことにする。 SIC だけど , BASIC とは大きく異なり , さら るほど使いやすくなると思う。 フィールド画面入出力命令のサポート に仕様の決定は完全に筆者の独断と偏見て、 AS ℃は BASIC て、アプリケーションを作るときに 凝り固まった言語になっているのだから もともと万人に理解て、きる言語を目 いちばん頭を抱えるのが入力系て、ある。 BA マシンの一部 ? 指して作られた BASIC とは基本的に違って SIC の INPUT 文て、は貧弱て、使いものになら 実際に使用しているかどうかを間題にし しまっている。開発を始めたときの制作方 ない。そこて , フィールド入力ルーチンを 作ったりするわけだが , これが意外に手間 なければ , かって BASIC ほど普及した言語 針は以下のようなものだった。見てもらえ はないと思う。なぜ , そんなに普及してい ると , いかに一般の BASIC と異なっている がかかり , その割に納得のいくスピードの ものはて、きない。ましてや , 編集っきのフ るのか ? 答えは明解だ。マシンと一緒に かわかるかと思う。 ィールド入力なんていったらたいへんな話 BASIC を供給してしまう形が一般的になっ BAS ℃の文法をベースにする これは筆者がインタブリタを作りやすい て、ある。しかし , これが命令として用意さ てしまったからて、ある。 PC ー 9801 て、もいまだ という実に単純な理由による。残念ながら れていれば余計な手間もかからないし , 編 に BASIC を ROM に乗せてユーザが必要だろ 開発当初は BASIC が気に入っているから B 集っきの入力だって可能になる。 うが不必要だろうが , 無条件に供給される。 ASIC にしたわけて、はない 旧 AM ファイルをサポート つまり , マシン 1 台につき 1 本必 $BASIC を アプリケーションの仕様変更に柔軟に対処 ビジネス系のアプリケーションは大量デ 買わされているわけて、 , 当然普及率は高く ータの扱いが必須て、ある。そこて、 , 効率の できる言語であること なる。 OS の普及した今て、こそあまり見かけなく とくにファイルレイアウトの変更に柔軟 よいファイル管理が必要になってくるのだ に対応てきることが必須条件と考えている。 が , それには ISAM をサポートするのがいち なったが , 昔は BASIC をソフトウェアだと ビジネス系のプログラマならファイルレイ は思わずに機械の一部だと思い込んて、いる ばん適当て、あろうと考えた。 アウトの変更のために全プログラムを書き ファイル項目の扱いが簡単であること 人がいたりした。笑い話のようだが , 考え 直さなければいけなくなったという経験を てみれば勘違いしても仕方ないところて、あ これは 2 番目の仕様変更に柔軟に対応する ろう。スイッチを入れればすぐに BASIC が 持つ人は多いはずて、ある。その作業内容は ことと重なるが , 簡単なファイルの仕様変 立ち上がってユーザはまったく BASIC の起 単純な書き換えの繰り返しになることが多 更 ( たとえば項目桁数の変更 ) のたびにプロ 動を意識しないていいのだから・・ く , 実にムダな作業てある。 グラムを書き換えていたのては本数の多い システムてはそれだけて、膨大な工数を要し これは , BASIC の基本コンセプトに「ハード 少ない工数でアプリケーションの開発が可 てしまう。そこて , それらを解決するため ウェアを意識させない」ということがあるた 能であること には個々のプログラムて、はファイノレの構造 めて、もある。ハードウェアの知識を要求さ これは , 今後も永遠に残る課題てはある に依存する記述をてきるだけ避け , ど れるような言語て、あったなら , おのずとハ こか と思う。より , 視認性が高くわかりやすい 特集プログラミング言語 A to Z 41 arrot BASIC

5. 月刊 C MAGAZINE 1992年2月号

Fig. 1 文字列、、 ST NG ' ′のコピー 犯したりして不都合が生じてきます。その う繰り返すのか」をコンピュータに伝えなく 果的に行うためには , 私たち人間が「何をど コンヒ。ュータが得意とする繰り返しを効 けて、す。 ための言葉がプログラミング言語というわ そしてコンヒ。ュータに人間の意思を伝える ん。それを教えるのはもちろん人間て、す。 返すのか」は教えてもらわなくてはなりませ ュータて、あっても , 「いったい何をどう繰り しかしながら , 繰り返しが得意なコンヒ。 す。 ヒ。ュータのもっとも得意とする分野なのて、 がて、きるわけて、す。本来「繰り返し」はコン 何回も何回も飽きすにミスせずに行うこと 点コンヒ。ュータは機械て、すから同じことを てはならないわけて、す。 列をコピーする関数 鳶 文字列をコピーする opy_string としましよう。文字列 ( ストリン つの C の関数にすることにします。名前を c まず , この問題を解くプログラムをひと 様を自分て考えてみます。 ありません。そこて、 , 妥当と考えられる仕 つの疑問に対して何も答えてくれそうには ついては何も述べていませんから , 上の三 間題文は非常に短く , また細かな仕様に だろう ? ( 3 ) コピーした文字列はどうやって返すの ( 2 ) 文字列のコピーはどうやるのだろう ? るのだろう ? ( 1 ) コピーする元の文字列はどうやって得 間の形て、まとめてみます。 つの点に注目しました。ひとつひとつを質 この間題を考えるとき , 私はます次の三 の体操て、すね。 することも勉強になるものて、す。一種の頭 うとしているか」というワンレベル高く思考 Fig. 2 文字列は文字の繰り返し グ ) をコヒ。ーする関数だからて、す。 コヒ。ーする元の文字列はこの関数に対す る引数とし , コヒ。ーした文字列を格納する メモリのアドレスも引数て、ケえられると決 めてしまいましよう。これて、先ほどの三つ の疑問のうち ( 1 ) と ( 3 ) は解決しました。残 る ( 2 ) の , 文字列をコヒ。ーする方法について 考えることにします。 「文字列をコヒ。ーせよ」といわれたら , C 言 語のプログラマならすぐに大きく分けて 2 通 りのプログラムを書くことがて、きると思い ます。ひとつは for 文を使ったもの , もうひ とつは while 文を使ったものて、す。わかって いる人はすぐにて、も書けてしまうプログラ ムて、すが , こて、はちょっと回り道をして , いろいろ考えをめぐらせてみましよう。 んな簡単な問題からて、も , もしかしたら , 何か新しい発見があるかもしれません。 ーま文繰り返し それて、は次の問題を考えてみてください 【問題 1 】 62 C MAGAZINE 1992 2 考えるとともに , 「自分がそれをどう考えよ こういう間題を考えるとき , 問題自体を されていなければなりません (Fig. 1) 。 列を格納するのに十分な量のメモリが確保 ろん , 移す先の変数として , 移したい文字 文字列をほかの変数に移し換えます。もち 字列がある変数に入っているとして , その ことて、す。たとえば , 、、 STRING 〃という文 文字列をコヒ。ーするというのはこういう 文字列をコピーする関数を作りなさい。 文字 文字 文字 文字列 文字 文字 ℃ ' 文字 文字

6. 月刊 C MAGAZINE 1992年2月号

算子を適用して , それへのポインタを作る チャの都合て、 , ワードデータはワード境界 て共通のメンバ名を使ってもよいとされて こと , ②メンバ選択演算子、 . ″を適用して いた。今にして思えばずいぶんな制限て、あ に整合 ( アライメント・・・・・たとえば偶数番地 メンバを選び出すことくらいしか許されな からデータを配置するとか , 4 の倍数番地か 最近て、はそのようなことはなくなったか ら配置するとかなど ) させたほうがアクセス かった。 これが ANSI て、は , 関数へ値渡しで渡すこ らよいのだが , だからといってこの例のよ が速かったり , なかには必ずそうしなけれ と , 関数の返り値として構造体を返すこと , ばいけなかったりするからてある。コンパ うなプログラムを書くのはもちろん感心し 構造体の代入などが許されるようになった。 イラによってはオプションスイッチなどて、 ない。これはあくまて、例て、あることをお忘 構造体のアライメントボリシーを変更て、き このうち , 構造体の値渡しを使うと , 従 れなく。 来の C て、は不可能て、あった配列全体の関数へ るようになっているもの (MS-C など ) もある 体の の値渡しを実現て、きる。 が , ソースレベルて、これを制御する手段は メモリアロケーション これまて、配列名を関数呼び出しの実引数 C には用意されていない。ある構造体に穴が として指定すると , ポインタジェネレーシ 存在するかどうかを調べるのは簡単てある。 構造体に関して規格上定められているの ョンのルールが適用されて先頭要素のアド は , 各メンバは宣言て、指定された順序て、並 構造体全体の sizeof と , 各メンバの sizeof の レスが渡され , 実質的に参照渡しとなって べられるということだけて、ある。最初に宣 合計とを比較して , 両者が一致するかどう しまうというのが C の常識て、あった。ところ 言したメンバがいちばん若いアドレスに配 かをテストすればよい 一致していなけれ がその配列全体をすつばりと構造体て、くる ばどこかに穴が存在する (List 2 ) 。 置され , 次に宣言したメンバがその次に若 こうすれば , 構 んて、しまうことがて、きる。 いアドレスに配置される。各メンバは必ず 構造体の技巧的利用方法 造体自身はどれ程サイズが大きくても値渡 しもメモリ上て、隣接した位置に配置される しが可能なのて、 , 実質的に配列をまるごと とくに , 最初に char などバ とは限らない イトサイズの小さいメンバを宣言し , その 構造体は K & R から ANSI に変わったとき 値渡して、渡せる。 typedef struct { 後に double などバイトサイズの大きいメン に取り扱いがかなり拡張された。もっとも int dat [100] ; 実際のコンパイラはすて、にかなり前から拡 バを宣言すると隙間があく場合が多い } f00 t ; の現象を穴空き ( h 。 le ) などと呼んて、いる。 張されているのが普通て、ある。 K & R の時代には , 構造体の変数に対して これはそのコンパイラのアライメントボ 行うことがて、きる操作は , ①単項のヾ & ″演 リシーによる。すなわち , CPU アーキテク 構造体に穴が存在するかどうかを調べる f00 t array , void bar(foo t) ・ List void zot(void) 1 : #include 2 : 3 : struct f00 { 4 : char a; 5 : long b; 6 : char c; double d; 9 : 10 : int main(void) 11 : { struct f00 f00 ; printf( ” sizeof(struct f00 ) printf("sizeof(all members Of struct f00 ) = Xd*n" sizeof(foo. a) + sizeof(foo. b) + sizeof(foo. c) + sizeof(foo. d)); 20 : return く stdio. h> bar(array) ・ / * a ay を値渡しする * / また , 構造体の代入が可能て、あることを 利用すると List 3 のプログラム例に示すよう な汎用の swap マクロを定義て、きる。ここて、 定義した swap には , オプジェクトに単項の 、、 & 〃が適用した形て、パラメータを当・える。そ れが可能なオプジェクトて、あれば , 事実上 何て、も swap て、きる ( もちろんふたつの引数は サイズが同じて、なければならない ) 。マクロ の内部て、は , 構造体の内側て、配列を宣言す sizeof(foo)); = Xd*n ” 112 C MAGAZINE 1992 2

7. 月刊 C MAGAZINE 1992年2月号

員 mo だが , 配列形式の場合にはそのような場合 にループを構成することて、コンパクトな表 現が可能となる。 一方 , 点ということて、全体をひとまとま りのデータとして捉えて , そのまま代入し たり関数に引数として値を渡したり , 関数 の値として返したりしたくなってくるが , そのような操作は配列て、は不可能て、 , 構造 体て、なければて、きない このため , データ 全体のハンドリングの容易さを考えると構 造体に軍配が上がる。 ときとして , 構造体て、取り敢えずデータ 型を定義しておき , その先頭メンバ ( この例 て、は x) のへのポインタを作り出し , それを 元に配列的にアクセスするというコーディ ングが行われているようだが , これは移植 性を考えるとあまり好ましくない というのも , コンパイラによっては構造 体には穴が存在するかもしれず , 連続的に 宀言したメンバが必ずしも配列と同様にメ モリ上に配置されるという保証はないから て、ある。このため , 構造体のあるメンバの いて三つの座標値を d 。 uble て、表すことを考 アドレスを手がかりにそのほかの要素を配 るというテクニックを使っていることに注 える。この場合単純に考えると配列と構造 列と同じ機構て、アクセスするのは , コンパ 意されたい ( List 3 ) 。 体と両方の選択肢が考えられる。 イラと環境に大幅に依存したコーディング 配歹に構造体の使い分け てある。 まず配列を用いることにすると , 次のよ うに書けるだろう。 それやこれやて、 , このケースて、のひとつ / * 配列の場合 * / の折衷案として , 配列を構造体の中に埋め 最初に述べたように , 配列と構造体は , enum{X axiS,Y axis Z axiS,N axes}; 込んて、しまうという手法がある。 こうすれ 前者は添字て、要素にアクセスし , 後者は名 typedef double point t [N axes] ; ば全体として値をやり取りすることもて、き 前て、要素 ( メンバ ) にアクセスするという差 るし , また各要素のアクセスに添字を用い そしてもうひとつの解として構造体を用 がある。あるデータ構造を表現したい場合 いる場合にはこのように書ける。 てループを利用することが可能となる。 に , 要素の型が異なっていれば構造体を採 / * 構造体の場合 * / / * 配列を構造体の中に埋め込む * / 用するしかなく , 選択の余地はない。しか typedef struct { enum{X axiS,Y axiS Z axiS,N axes}; し , たまたますべてが同じ型て、あった場合 typedef struct { はどうだろうか。そのような場合に配列を double x ; double data [N axes] ; 使うべきか構造体を使うべきかは非常に悩 double y ; } point t ; ましいところて、ある。 double z ; } point t ; たとえば , 以下のようなケースを考えて もっとも , 筆者は必ずしもこのようなコ ーディングを推奨するものて、はない。構造 みよう。 このふたつの技法の優劣を論じてみよう。 3 次元グラフィックスて、 , 空間内の点を表 座標データの場合には , x, y, z それぞれ 体を使って , いささか冗長になったとして も素直にメンバ名ごとの処理を書き下ろす す型として point t を typedef によって定義す の軸のデータに関して , まったく同一の操 ほうがいいかなと現時点て、は考えている。 るものとする。 x, y, z の三つの座標軸を用 作を施すケースが比較的多いと思われるの 汎用のマクロ定義例 List 1 : #include く stdio. h> 2 : 3 : #define swap(xp, (P) do { struct ー f00 ( char _bar[sizeof(*(xp))] ; } -t; 4 : *(struct ー f00 * ) (xp) ; 5 : *(struct ー f00 * ) (xp) = *(struct _foo*)(yp) ; 6 : *(struct ー f00 * ) (yp) -t; } while ( の ; 7 : 8 : 9 : #define writeln(x, fmt) printf()x ” 10 : int main(void) 12 : ( = 123 int X 14 : int y = 456 15 : double xd 2. 71827 ; yd = 3.14159 ; double xa[20] ニ ” He110 , world. 17 : Char yaC20] = ” Good-night baby. 18 : Char swap(&x, (Y) ; 20 : swap(&xd, &yd) ; 21 : swap(&xa, &ya) ; 22 : writeln(), Xi); 23 : writeln(), Xi); 24 : writeln(xd, % 7. 25 : writeln(yd, % 7. 26 : writeln(xa, (s) ; writeln(ya, (s) ; 28 : 29 : return 0 ; 30 : } ANSI C ー more 113

8. 月刊 C MAGAZINE 1992年2月号

①び釦 a Have StYIe List 1 て、は , 日付は構造体て、あり , べンダ 提供〔コンパイラに付随〕の #include ライプ ラリを利用して , コーディングを省力化し ています。日付の構造体は , コンパイラに よって違います。そこて、 , どちらのリスト て、も , 個々のフィールドには #define による 名前を使っています。私が使ったコンパイ ラ (BorIand 社の Turbo C 十十 Ver. 1 . 0 と Z ortechC 十十 Ver. 2.18 ) は , 日付用の構造 体のフィールドの数は同じて、すが , 名前は 違っています。 この記事を書きはじめた最初のころ , 何 人かの仕事仲間がプログラムを見て , それ ぞれの意見を言ってくれました。一人は , someday の値をわざわざ設定しているのは 必要ない と言いました。宣言時に値を初 期化した方が簡単だし , スッキリする , と いうのが彼の感じ方て、した。つまり , ーっ の定義て、もって , someday への三つの代入 文を置換て、きる , というのて、す。 date struct someday ( 8 , 9 , 10 ) ; こうやってコンストラクタを使う しかし , ときは , コンストラクタに与える引数の順 序に注意しなければなりません。その順序 は , 合衆国形式の ( MM , DD, YY ) て、す か , ョーロッパ形式の ( DD , MM, (Y) て、 すか , それとも ISO の (YY, MM, DD ) て、す これと対照的に もう一人は , わざわざ 代入文を書いた方が分かりやすいし , 代入 文はコンパイル時にエラーチェックされる し , ランタイムに値をチェックすることも て、きる , と言いました ( 元々 someday は日付 計算用の作業変数なのて、 , ランタイムに値 をチェックすることは妥当て、す ) 。 もうーっの問題点は , date struct のメン バへの無制御なアクセスて、す。これは C なら ば完全に合法的て、すが , C 十十のコードとし ては , あまり気持ち良くありません。しか も , date struct の各部分は , 名前もデータ 型もコンパイラにはって違います。つまり , それぞれの構造体に実際ストアされる値が たぶん一定て、はない ということなのて、す。 たとえば month フィールドに long を代入した ら , 切り捨てされるのか , あるいは符号拡 張されるのか ? それは , month のデータ型 が分かっていなければ , 分かりません。し かしそういう知識が必要となると , データ 抽象化とカプセル化によって実装詳細を隠 蔽する , という C 十十の基本原則に違反して しまいます。 2 種類目の感想意見は , orec の中身に関す るものて、す 0List 1 て、は , 複数のメンバ関数 を定義し , それらを使って , MyRec クラス のデータメンバに値を設定しています。私 は , X というデータメンバに値を設定するメ ンバ関数の名前は setX ( 値 ) にする , という 規約を自分に課しました。このテクニック によって , ある種の統一感は得られますが , メン八関数記法 て、きます。一般的に言って , orec の各フィ しかしこういう機能は別のやり方て、も実現 List 1 ールドへの値の設定に 5 行を費やしているこ とは , まあふつうて、あり , 最小限の行数て、 す これより少なくはて、きません。しか し , List 2 て、は , 同じことをより単純かっス ッキリと実現しています。 List 2 て、は , 誌面て、実装詳細は示しません が , orec を , 抽象コンテナクラスて、ある M yRec のインスタンスとして扱っています。 orec くく tOday という文は , これと同等の set.. ( ) 関数より も短くてシンプルて、す。この文は , Start D ate 型の変数を MyRec に代入します。この作 戦は , ストリーム I / O がやっているように , ぐ ( 演算子をオーバロードしています。ただし こて、は , 、《演算子を MyRec 用にオーバロ ードしているのて、す。二つ目の文 , orec くく (End Date) someday ; は , Date 型へのキャストによって End Dat 1 : 3 : 4 : 5 : 6 : 7 : 8 : 9 : 11 : 13 : 14 : 15 : 16 : 17 : 19 : 20 : 21 : 22 : 23 : 24 : 25 : 26 : 28 : 29 : 30 : 31 : 32 : 33 : 34 : 35 : 36 : 38 : 39 : 40 : 41 : 42 : 43 : 45 : 49 : class MyRec int date struct date s truct char char idnum; sd ; ed ・ name 20 ] ; / / メンバ関数 . ublic: int main() MyRec orec ; date_s truct someday : date s truct tOday ; getdate info(&töday) ; / / フィールド名を使って / / someday の成分をセットする s omeday. date_year s omeday. da te day someday. date-month ニ 10 ; orec. open("outfile ” , "ab") ; / / メンバ関数を使って / / orec の成分をセットする orec. setStart_Date(tcxiay) : orec. setEnd_Date(someday) ; orec. setIDNumber(1 の ; orec. setID( ” RC ” ) ; orec. setName("Reg Charney ” ) ; orec. write() ; / / 各フィールド用のメンパ関数を使って / / 。 rec の成分を取り出し / / プリントする くく cout ” ID Start End I D Name}n ” ” # # Date Date くく orec. getIDNumber() くく” くく orec. getStart_Date() くく " くく orec. getEnd_Date() くく” くく orec. getID() くく” くく orec. getName() return( の ; 50 : ) 20 C MAGAZINE 1992 2

9. 月刊 C MAGAZINE 1992年2月号

Char ← * / C return 0 ; putchar(c) ・ * bar(&c). p a よくながめるとわかるのだが , 要するに main の中て bar を呼び出している部分 ( 、、←つ は , main のローカル変数 c に、、 b 〃という値 を非常に迂遠な方法て、代入しているだけて、 ある。ヒ。リオドの左側は bar ( & c ) て、あり , 関 数が返してきた値て、ある。当然これは左辺 値て、はあり得ない。しかし , それに . p とし てメンバ p の値を取り出し , さらに間接演算 子の、、 * クを適用することがて、きるのて、あ る。なお , 結合順位はヒ。リオドのほうが高 いため , カッコは不要なのて、あるが , 間題 の代入の行をわかりやすく書き直せば次の ようになる。 この例は作意的なものて、あり、極めてト リッキーて、はある。しかし最近は標準ライ プラリにも構造体を返す関数がある。すな わち左辺値て、はない構造体の値 ( 非公式に は , 右辺値・・・・・・ rvalue などと呼ばれることが ある ) が今まて、よりもずっと頻繁に使われる 可能性が増えている。このため , そのよう な値に直接ヒ。リオドとメンバ名を適用して , 構造体の値の一部分だけを取り出すという 操作は決して特殊なものて、はなくなると考 えている。 次に , ポインタを用いたアクセスて、ある が , これはふたつの書き方がある。ひとつ は , まずポインタに間接演算子の、、 * 〃を 適用し , しかる後にヒ。リオドを適用する方 法て、ある。 ただしこの場合 , 先に述べたようにヒ。リ オドの優先順のほうが高いため , カッコて、 くくる必要がる。先ほどの例の変数 n と同様 に p がポイントする先の bar の値が取り出さ れ , x に代入される ( x はたとえば double の変 数て、あるとする ) 。同様に z 。 t に値 1 を代入す るには以下のように書けばよい ( * p ). f00 ; X ( * p). zot もうひとつは , 間接メンバ選択演算子て、 ある、、一 > クを使う方法て、ある。 p- > f00 ; X p- > zot この演算子は , 構造体へのポインタを用 いるときに , 通常どおりのヒ。リオド演算子 を使っているとカッコをつけることが不可 避なのて、 , その手間を省くための便宜的な 記法として作られたようて、ある。この演算 子もヒ。リオドと同様に両側に記せるものに 制限があり , 左のオペランドは構造体への ポインタを値とする式て、なければならず , 右のオペランドに記すことがて、きるのは左 オペランドに記されたポインタがポイント する構造体に属するメンバの名前て、なけれ ばならない 名とメンバ名の 則空間 どれまて、タグ名に関してはあまり説明し てこなかったが , タグ名というのは一般の 変数名や関数名とは異なる名前空間に存在 している。ただし , 構造体 , 共用体 , およ び列挙て、それぞれタグ名を使うが , それら の名前空間はすべて同じて、ある。同じて、あ る必然性はとくになく , それぞれに別々の 名前空間を用意してもよかったはずだが , 最終的には同じ名前空間になったと ANSI の RationaIe ( 理由書 ) に記されている。さらに これは ANSI からの規則なのだが , 構造体 ( や共用体 ) のメンバ名は , それぞれの構造体 ( および共用体 ) ごとに異なる名前空間が与 えられている。 こて、名前空間という用語は初耳かもし れない。要するに通常の変数名や関数名と 同じ名前を同一のスコープの内部て、 , タグ 名て、だぶって使うことがて、きるという意味 て、ある。また , 同じメンバ名を異なる構造 引 mo 体 ( や共用体 ) 間て、は , 異なる型やオフセ ット値を持つものとして宣言してかまわな いということて、ある。次の例を見てほし struct f00 { int f00 } f00 struct bar { int bar ; double f00 ・ } bar , void zot (void) bar. f00 f00. f00 ANSI C ー more 111 ただし型とオフセットが等しい場合に限っ クて、なければならないという制約があった。 用体を通じてメンバ名は原則としてユニ ンパイル単位の中て、は ) すべての構造体 / 共 ひとつだけ備えられていた。このため , ( コ 名前空間はすべての構造体 / 共用体に共通て、 かって , K & R 時代の C て、は , メンバ名の ある。 よび共用体 ) ごとに名前空間が異なるからて、 た間題にはならない。メンバ名は構造体 ( お セット ) も異なっている。しかし , これもま オフセット ( 構造体の先頭からのバイトオフ しかもこれは最初に宣言された f00 とは型も struct bar< も f00 というメンバが宣言され , の変数名と名前空間を共有している。次に 変数名としての f00 が , 一般の関数名やほか 間題も誤解も生じない。この中て、 , 3 番目の っとも異なる名前空間に属するため , 何の 変数として f00 を定義している。これらは三 f00 , メンバ名も f00 , そしてこの型を持っ まず最初の structfoo のほうは , タグ名が 避けるべきだろう ) 。 る ( 現実的には , このようなコーディングは これは文法的には正しいプログラムてあ

10. 月刊 C MAGAZINE 1992年2月号

List 十 = 演算子の実装 List ope 「 atO 「十 1 : HandIeEMS& dle 聞 S : : operato で + = ( 10 れ ) if ((pos の size) 3 : 4 : 00S Size 、 1 ・。 else if ( & くの言 5 : POS に 0 : 7 : 、 return *this; 1 : HandIeEMS HandIeEMS: :operator + (long れ ) const 日面 dle S temp = *this; 4 : temp 十龜 : 5 : e 加印 temp : / / 暗黙のうちに型変換される てアクセスするためのものてす。現在の注 は名前がありませんが , 名なしの権兵衛だ 目点をズラさずに , 新しい注目点を設定す のように , 通常のポインタ風のノリて、入出 と話がややこしくなるのて、 , とりあえず go る , 簡単なことてす。 力がてきます。 nbei と呼ぶことにします。 1 要素ずつアクセスするには , 新しく HandIeEMS 型のインスタンスを別 gonbei は , 関数 test のスタックフレーム上 に作って , そっちの注目点を操作すればよ * (char far*) (void far*)ems に確保され , スコープを抜ける ( test からリ いのて、す。 HandleEMS 型のテンボラリオプ ターンする ) 直前に , デストラクタが呼ばれ * (unsigned far*) (void far*)ems ジェクトを生成して , それを値返しすれば て消去されます。 のようにキャストすれば OK てす。もし 2 重 よいてしよう (List 6 ) 。 のキャストがめんどうだというなら , 主要 問題は , gonbei がそもそも ems をコピーし たものてある , というところにあります。 な型のポインタについて , おのおのへの型 「ク「第はどうするか ? gonbei. emm handle = = ems. emm handle 変換演算子を用意しておけばよいてしよう。 このために , HandleEMS クラスの宣言を なのてす。 gonbei のデストラクタによって C 言語においては , * ( p 十 100 ) と p [ 100 ] List 4 のように変えます。 gonbei. emm handle が解放されてしまう とは可換てあるし , このふたつの表現はし ここ <operator( ) ( ) は , ems ( 1000 ) のよ と , 同時に ems. emm handle も解放されてし ばしば混用されます。したがって , 混用て うにして , データ先頭からのオフセット指 まうのてす。これては , 軒先を貸して母屋 きるようにしておいたほうが無難て、しよう。 を破壊されるようなもの凄さて、す。恩を仇 定て、注目点を移動させるために使います。 [ ] の添字は , データの先頭位置からのオ て、返すのも , こまてくれば立派なものて、 了ほの演算子 フセットてはなく , 現在の注目点からのオ す。 仕方がない。コピーされたオプジェクト フセットを表すことになります (List 7 ) 。 をオリジナルよりも先にスコープから出さ こうなってくると , 先ほどの Huge クラス コビーされたオプジェクト ないように気をつければいいんだろ , とい に実装されていたような算術演算子をびし 第のテストラクタ ばし実装してみたくなってきます。 うと , そうは問屋がおろしません。 さて ,List 8 のような局面を考えてみまし たとえば十 = 演算子は , List 5 のように簡 実は , この問題は List 8 のような作為的な よう。関数 test の中て , 式 (ems 十 (0) が , H 例を引っ張り出してくるまてもなく , List 6 単に実装てきます。 andIeEMS 型のテンボラリオプジェクトを生 = 演算子は符号をひっくり返すだけて、 にも内包されているのてす。 成します。このテンボラリオプジェクトに ー演算子はもっと簡単て、す。 すし , 十十 , List 6 において , コンパイラの生成するコ 問題は , 十演算子やー演算子てす。これ List operatorC ] らの演算子は , たとえば , * (HandIeEMS 十 10 ) = 0 ; * (HandIeEMS 十 2 の = 0 ; のように , 注目点から離れたオフセットに アクセスするために用意されるものてす。 これらの演算子が注目点をズラしてはいけ ません。 一方 , * 演算子は「現在の注目点」に対し 1 char fart HandleEMS::operator ロ (iong 0 ) return *(char far*)(void far*)(*this 十れ ) : 3 128 C MAGAZINE 1992 2