拡張に取り組むべきだったのて、しよう。と 文字などを含み , ライプラリ関数 isspace( ) 己録する 非 0 を返す文字 ) は , トークン ( 個々の名前 ) にかく , 原作者はドキュメント整備を意図 プリプロセッサディレクテイプ行はすべ デリミタ以外については , すべて無視され て無視するのて、 , たとえば , ListI- ①という し , 私はプログラムを理解するためのツー マクロ定義は記録されません。したがって ます。たとえば C 本体ては , ルを意図した。この微妙な目的意識のくい 関数定義のなかにたとえば , List1 ー②のよう 違いが , 私を 2 週間あまりにわたる地獄に引 int salt ・ な , 関数コールと外見的に同じ行があると , きずりこんだのて、した。 welldone( ) はライプラリ関数てある , と判 N さんと W さんによるプログラムは , ソー ス中て定義されている関数と , 個々の定義 断されて出力されます。そこて、私はこの点 関数のなかて、コールされている関数を , メ を改良し , マクロをマクロとして捕捉て、き が同じて、す。後者て、は改行文字はトークン モリ上のデータベース ( たんなる配列 ) にす るようにしようと考えました。 デリミタになってるだけて、 , 2 行が意味的に べて拾い上げます。 マクロを捕捉して記録する処理を , 現存 はひとつの単位てす。 しかしプリプロセッサディレクテイプ行 そして出力時に , コールされている関数 する関数名捕捉ルーチンの拡張 ( 判断文など 名と定義されている関数名との対照を行い , の部分的追加 ) によって実現しようとズボラ て、は , 改行文字は ( その直前に 1 個の、、 \ 〃が 前者が後者のなかに見つからなければ , 「こ ないかぎり ) ディレクテイプ行の終わり , と をしたのが , 泥沼へ転落するもとてした。 の被コール関数はライプラリ関数て、ある」と 今 , 冷静に考えてみると , C のソースファ いう機能的に明確な意味をもちます。 判断します。見つかれば , その関数が定義 イルのプログラム本体部分と , プリプロセ C 本体ては , (tomato, salt, されている場所 ( ファイル名 ) を併記して出 、、 juice(tomato, ッサディレクテイプ行は , それぞれ処理主 tabasco)" と書いても , 力しますが , ライプラリ関数だと判断する salt, tabasco)" と書いても , 意味は同じて、 体と処理時間が違うのてす。 す。トークンデリミタてはないスペース文 と , 関数名の下に ( library ) というマークを ディレクテイプ行はプリプロセッサが処 つけるだけてす。 字は無視されるからてす。 理し , プログラム本体部分はコンパイラが このプログラムの / ヾーシングアルゴリズ ところがプリプロセッサディレクテイプ 処理します。そしてコンパイラがプログラ ム本体のパースを開始するときには , ディ ムは , 以下のようなものてした。 行て、は # define VOMIT ( ・・・ ①コメント , 文字列定数 , 文字定数 , およ レクテイプ行はすて、に処理ずみて、 , ソース 〃はまったく違いま define VOMIT( ・・ す。前者ては、、 VOMIT" が定義シンポル , びプリプロセッサディレクテイプ行はす 中には存在しません。したがって , その時 ・〃以降が定義内容 ( 置換リスト ) て、す。 点ては , コンパイラにとって文法的に正し べて無視する 後者て、は、、 VOM 工 T ( ・・・・・・〃が定義シンポルてす。 ② C のシンポルとして有効な文字列を拾う。 いソース行だけがソース上に存在します。 それはプレース当クのなかか ? ちなみに , マクロ定義を捕捉するための こうして列挙していくとキリがないのて、 パーシングアルゴリズムは , ③その文字列の直後の最初の非空白文字が すが , 要するに , C ソース本体とディレクテ ①、、 # 〃を読んだら「マクロかもしれないフ イプ行ては , パーシングの原理原則が相当 ℃なら , 関数名かも・・・・と判断する ラグ」を 0 から 1 にセットする ④上記の開きパーレンが最終的に閉じると に違うのてす。したがって , マクロを拾っ ②その直後に捕捉した文字列が "define" ころまて先読みする て記録するルーチンを , 新たに作るべきだ なら「マクロかもしれないフラグ」を 1 から ⑤開きパーレンが閉じた直後の最初の非空 ったのて、す。約 60K バイトのソースを , PC 〃などて、 , かっプレ 白文字が・ 2 にセットする ー 9801 シリーズおよび TurboC 用にアレンジ ③その直後に捕捉した文字列が , さらその ースの外なら , 関数定義ても関数コール しながら移植するのは約 4 日ててきたのてす 直後に℃をもっていれば , 「マクロかも て、もなく , 関数宣言て、あると判断して , が , その後の機能拡張に伴うデバッグてま しれないフラグ」を 1 にセットし , その文 無視する る 2 週間以上苦しみました。 そこて、教訓 ! ひとつのルーチンを機能拡張 ⑥関数宣言てはなくてプレースの外なら , 字列をマクロとして記録する して , コードがごたっき , デバッグに苦し それは関数定義て、ある。そのファイル名 と書くと簡単てすが , この判断と処理を , むようなら , ヾ機能拡張〃からヾ機能追加〃 とともに定義関数のデータベースに記録 C の関数名を捕捉するルーチンのなかて行 に改めよ。新規のルーチンやモジュールの う , という一石二鳥をねらったのてす。 する 開発に取り組むほうが , 結局 , 早道てある ! ⑦プレースのなかなら関数コール ( = 関数定 C のソース本体において , 空白文字 ( 改行 義本体内 ) てある。ただし定義本体内に は , if(, for(, while( などの関数コール とまぎらわしい形態のキーワード行があ るのて , その点をチェックする。直前に 己録した定義関数中のコール関数として 1 三ロ と int salt ; List 1 (RIGHT TIME * gram * cut * 1.5 ) ・ #define 響 el ldone(cut, gram) 響 el Idone(PRIME, 28 の : C00 k_t i me 1 三ロ C 言語フォーラム 119
く、ノ , N SI C 」の概要 ENV 旧 ONMENT( 環境 ) これは , C 言語の処理系を目的のマシンに インプリメントするための詳細を明らかに するもので , 同じ C 言語でありながら , 互換 性のない処理系が氾濫する状況を避けるた Special Group on SpeciaI Working Group on Registration Authorities SpeciaI Working Group on Procedu 「 SpeciaI Ⅶ′0 「 king Group on Strategic PIanning SpeciaI Working Group on Netherlands Functional Standardization めのものである。したがって , こに C のインプリメンタたちが移植の際に必要と する情報が定義されている。環境は , 翻訳環境 (Translation Environment) 実行環境 (Execution Environment) に分けられる。翻訳環境は , プリプロセッ シング処理を含めソーステキストをトーク ンに翻訳する一連の作業環境を意味し , プ ログラムの構造 , 翻訳の諸段階が定義され ている。実行環境ではプログラムのスター TSG- 1 特集 AN 0 の現状 ト ( スタートアッププログラム ) , 引数のや り取りなどホスト (OS) との関係 , プログラ ム ( 式 , 文 ) の実行と終了などの一連の動作 ( 行為 , behavior ) が定義されている。たと えば , プログラムのスタートは main 関数で はしまり , List1 のように定義されることが 示されている。もちろん , 引数 argc, argv の意味も明確に定義されている。以下 , 詳 細な定義が展開されていく。 また , 文字集合 (Character sets) や 3 文字 Tech. Study Group (IAP) Japan MSG-I Mgmt. Study Group SC6 Telcom & fo. Exchng. Btw. Sys. U SA EDI SC 1 8 Text & Office Sys. U SA SC21 infO. Retrv. Transf. Mgmt. fo 「 0 U SA SC26 Microproc. Sys. Japan SC2 Cha 「 . Sets & 旧 fo. Coding France Germ. SC24 Comp. Graph. SC25 not yet decided (former SCI 3 and SC83) SYSTEMS SYSTEMS SUPPORT SC27 Secu rity Techniques (tentative) Germ. 特集 ANSI C の現状 47
扱えるような機能を取り入れようというこ とで , ワイド文字を導入した。 ま $,typedef で wchar t という型を定義 する。これは , 整数型のひとっとして , 処 理系ごとに定義できるようになっている。 たとえば , ワイド文字が 10 文字入る配列を 定義するには , 処理系で typedef された Fig. 2 コード整合性の保証 型 wchar t を使い , wchar t wca IO) のようにする。また , これに対応して , ワイド文字定数 ワイド文字リテラル を使えるようにしている。 これらは , それ ぞれ L というプリフィックスをつけて , L' 漢 ' ( 型は wchar t) L ” This is ワイド文字列 " ( 型は wchar t の配列 ) ー① とする。 大文字の L は , バイト単位で扱うときと区 別するためである。①は L がないと , この文 字が 1 バイト単位で扱われてしまう。 wca "This is 漢字 string' initial shift state shift state 1 sh ift state n 入出力 多バイト文字集合 Char wchar t L" 54 概 CMAGAZINE 19 4 図 基本文字集合 引 SX0208 State-dependent encoding initial shift State 処理系定義・ mbtowc m bstowcs wctomb WCStO m bS 中 国 き五 wchar t 型集合
み合わせて、あれば , 暗黙の型変換が自動的に行われ ます。しかし , 可能な変換手順が複数ある場合 , ど の手順を使うかを明示的に指示する必要があります。 次にコンストラクタの逆の働きをする , あるクラ スのインスタンスからほかの型への変換を考えます。 これは演算子関数のひとっとして定義されます。 List7 に intgstack を int の配列に変換する型変換の演算子 関数を示します。このように演算子関数は , 変換後 の型名が TYPE ( この場合 int * ) て、あれば , 関数名 operator TYPE をもちます。 ■演算子関数 演算子関数は , 関数名のつけ方が少し変わってい るほかは , 通常の関数と同じて、す。それらの演算子 そのときに具体的な例をあまり取り上げていなかっ 関数は言語組み込みの型の演算子と同様に中置表記 たのて , 今回 , いくつかの定義例を示しながら , そ が可能て、あり , 新しい型を C 十十既存の型体系に混ぜ の用途の説明をします。 合わせることがて、きます。ここて、 ,intgstack インス タンスの加算のための関数の例を考えましよう。普 ■算子 通の関数として定義する関数 add は List8 のようにな さて , コンストラクタの働きはインスタンスの生 り , それは以下のようにして呼び出されます ( 注 9 ) 。 成てした。しかし , それは型変換というもうひとつ の役割をもちます。それは , 代入あるいは関数呼び 演算子関数は , 演算子@の働きに相当する関数を 出しのときに使われます。以下に int の配列を intg 関数名 operator@て、定義することによって用意され stack 変数に代入するときの型変換の例を示します ( is ます。関数 add と同機能の演算子関数 operator + を は intgstak 変数 ,ia は int の配列へのポインタとしま List9 に示します。これを利用すれば , 先ほどの式は 以下のような構文て、書くことがて、きます ( 注 10 ) is intgstack(ia); a = b 十 c ・ なお , この int 配列からのコンストラクタは List6 に このように 組み込みの型同様に , ューザが ー「 4 ロロ 示します。 定義した抽象データ型を被演算数 ( オペランド ) にも こては関数呼び出し形式の型変換を使っていま った演算子を中置表記の構文て、書けることは , たい すが , C 言語から譲り受けたキャスト形式も使うこと へん便利て、すが , いくっかの制限があります。しか がて、きます。複数の初期値をもった型変換には , 前 し演算子関数は , C の型体系を基本的に引き継いて、い 者の関数呼び出し形式を , ポインタなどを含んだ複 るため , それらは組み込みの演算子と同じ被演算数 雑な型への型変換には , 後者の型変換を使う必要が をもち , 同じ優先順位をもち , そして同じ結合規則 あります。 をもちます。それらを変更することも , 新たに演算 この例て、は , 明示的な変換の指定を行いました。 子を定義することもて、きません。たとえば , 指数演 変換前から変換後の型への変換手順が , 言語組み込 算のために , その機能を記号 * * にオーバーロード みの型変換ひとっとユーザ定義の型変換ひとつの組 することはて、きませんし , 記号・にオーバーロードす C 十十 プログラミング 入門 List 6 intgstack: : intgstack(int* (i) { while (pi[p + + ] ! = new int[size = P * 2 ] : S int(p * sizeof(int))); memcpy(), pi, ・ 1 り 0 っ 0 -4 -0 6 List 7 intgstack: :operator int* (void) { = new int[p] : int* pi memcpy(pi, s, int(p * sizeof(int))); return PI : 、 1 っ 0 っ 0 -4 L.n 注 9 ここての関数 add およ び operator 十は , クラス intgstack のフレンドてなけ ればならない。 注 10 演算子関数は , 無理 に中置表記構文て書く必要 はない。以下のような関数 呼び出してもかまわない。 operator 十 (), c); List 8 intgstack& add(intgstack& a, intgstack& b) { new intgstack(). size 十 b. size) : intgstack* ps return *ps; 1 り 0 っ 0 ・ 4 0 6 a 104 CMAGAZINE 19 4
やすい名前て、要約すればよい。例として , すべての 80 という数字を NCOLS ( 訳注 Number of Columns) て、置き換えることが 考えられる。アセンプラには EQU, Pascal とそのファミリーには CONST , C には #define がそれぞれある。そのメカニズムが何て、あ れ ( メカニズムのない言語は滅多にない ) , おのおのの数字をその名前に結びつけるこ とがて、きる。そして簡略化したストーリー をコメントの形式て、定義に付加しておくと よい。このことによって , あなたもしくは その仕事を引き継いだ第三者にとっても判 定しやすくなる。 当たり前のことと思うだろうか ? しか し , 公にされたコードの多くの部分に , な ぜ依然としておかしな数字が入っているの だろうか ? 80 という数字が散らばってい るだけて、も困りものなのて、はあるが , 数字 の一部が 79 , 81 , 40 などになっていおらも う最悪て、ある。そのような場合 , 80 が 120 に 変わったとしたら , ソフトウェアをメンテ ナンスする者にとって , もっとも強力なソ フトウェアツールの grep さえあまり役に立 たない て、 FOUR を 5 に K1024 を 4096 に変更した。思 ろうか ? このプログラムの新バ、一ション 時間のムダにすぎないだけだと思われるだ したプログラムを見たことがあるが , 単に FOUR を 4 と定義したり K1024 を 1024 と定義 いのは , 名前に数字そのものを使うことだ。 数字に名前をつけないことよりもっと悪 数が 120 になることだってあるのだ。 ことがあるくらいなのて、 , カードのカラム のだった。大地にある大きな穴がなくなる したときのプログラマのあわてぶりは見も の幹部がひとつの炭坑を閉鎖する指令を出 ていた ( 炭坑の数を意味する 2 と 4 も ) 。会社 その会社のプログラムすべてに 3 が散らばっ 会社のコンサルタントをしたことがあり , とんて、もないこと。私は 3 つの炭坑をもった 80 という数字が絶対に変わらないなんて CMAGAZINE 19 4 かさは簡単にひねくれに変質するのだ。 18 アと型の関係 2 番目のガイドラインは , データすべてに 独特の型をつけることだ。データの演算型 を決定するときにすて、にエントロヒ。ーを上 げている。少なくともりんごを数えるロン グの整数は , オレンジを数えるロングの整 数とは違うということをドキュメントに残 せる。いつかどちらかいつほ。うだけを変え たくなるかもしれない 言語の設計者はデータ宣言をよりうまく 作れるように多くの方法を試みてきた。必 要最小限の整数表現を選択するより , サプ レンジを宣言したほうがもっと多くの情報 を獲得て、きる。列挙 (enumeration) を宣言す れば値に名前をつけられるし , ( 今述べたよう な ) こった値を選択する誘惑から解放され る。パワーセット ( 訳注 : 下記参照 ) を宣一 すれば , ビットをひとつひとつ操作したい という意志が明確になる。 2 のベキ乗の定義セット 例 #define B 工 TO #define BITI #define BIT2 #define BIT3 0X0001 Ox0002 Ox0004 Ox0008 これらの機構はすべてたいへん役に立つ ものばかりだ。唯一の間題点は , 私がいつ もこれらを複合して使用したくなることて、 , 古典的な例て、は標準 C 関数の getchar があ る。 getchar は unsigned char というサプ レンジて、表現可能な値 , またはまったく別 の EOF というコードをリターンする。これ は C プログラマが楽しめる便利な動作だ。 んな使い方を宣言て、きる言語は見たことが ない。そこて、 , 私は適切な表現形式を選択 し , その形式に対する意味上の制限を型定 義中に書いておくようにしている。 ある演算の型を選択する際の要素のひと つは , 当然のことながら , 言語がその型に どんな演算をサポートしているかて、ある。 つまり , りんごを数えるには整数を選択し , アップルソースの重量を測るには浮動小数 点数を選択する。このような言語て、は , り んごとオレンジの加算は許可するが , 標準 的な経験則にてらして , りんごからアップ ルソースへの暗黙の変換は許可しない れがどう約束しようが , 自分自身のドキュ メントや規律を越える言語はないのだ また , これらのことをほとんど何も継承 しない型を自前のデータ用に宣言すること も可能て、ある。そのためには新しい (new) 型 を作成し , その型のためのオペレーション のうち定義したいものだけを記述すればよ い ( ほとんどの型定義を既存の型と同じもの として扱う C 言語においてさえ , 構造体はす
コードとなっています。 ( 2 ) DOS ファンクションを呼ぶ方法 DOS は , 押された内部コード ( BIOS レ ベルのコード ) を DOS が割りつけているキ ーコードに変換して返しています。 また , 日本語 FEP は , 内部コードから 漢字コードを生成して DOS へ引き渡すの て , 日本語 FEP て、変換した漢字コードを 入力することがてきます。 一般に , 返ってくるコードは , ASC118 ビットコード (TbI. 1 ) か , シフト JIS コー ド (MS-DOS の場合 ) となっています。 ( 3 ) キー入力関数を呼ぶ方法 C てキー入力を行う場合の一般的な方法 てす。一般にライプラリの中て、は , DOS のファンクションを呼び出しているのて、 , 機能的には , ②と同じてす。 今回作成する関数も , このキー入力関 数を使用しています。 一般に , 返ってくるコードは , ASC118 ビットコードまたは , シフト JIS コード ( MS ー DOS の場合 ) となっています。 ま字コードのスカについて 漢字コードは , 2 バイトから構成されてお り , MS-DOS て、はシフト JIS コード ()S 漢字 コード ) が採用されています。 このため漢字コードを入力するためには , 1 文字入力関数を使用する場合 , 2 回呼び出 す必要があります。 1 回目の呼び出して、第 1 バイト , 2 回目の呼び出して、第 2 バイトが返 ってきます。 したがって , ASCII コードと漢字を混在し て入力する場合には , 1 バイト入力されてき たところて , 必ずコードの体系 ( 1 バイトコ ードか 2 バイトコードか ) を判断しなければ なりません。幸いにして , シフト JIS コード の第 1 バイトと ASCII コードとは , 区分けさ れているのてこれて判断します ( Tbl. 1 ) 。 同様に , ファンクションキーなどのよう に ESC シーケンス ( ESC コードて始まる文字 列 ) が入力されてくる場合にも , 同様に制御 します。ただし , ESC コード単独て、特殊な 意味 ( たとえばキャンセルなど ) として使用 112 CMAGAZINE 19N 4 key-asgn List 1 1 : #include く dos. h> 2 : 3 : 4 : / * key_asgn 5 : key-asgn ( int mode) 6 : VO i d keytbl-save [ 2 ] [ 6 ] : 8 : static char far keytbl[2] [ 6 ] = { 9 : static char fa r 12 : 13 : REGS umon 14 : struct SREGS 15 : Char fa r if (node== の / * キーポード定義 * / 17 : reg. h. cl=OxOc; reg. x. ax = 0X0017 : tbI=&keYtbI-save[0] [ 0 ] : reg. x. dx=FP-0PP(tbl) : 22 : sreg. ds=FP-SEG ()b I) : int86x (0xdc, ®. &outreg, &sreg) : 24 : 25 : reg. h. cl=0x0c; 26 : reg. x. ax ニ 0X0018 : tbl=&keytbl-save[l] [ 0 ] : reg. x. dx=FP-0PF(tbl) : 28 : 29 : sreg. ds=FP-SEG(tbl) : int86x (0xdc, ®, &outreg, &sreg) : 30 : 31 : 32 : reg. h. cl=0x0d; 33 : reg. x. ax = 0X0017 : tbl=&keytb Ⅱ 0 ] [ 0 ] : 34 : reg. x. dx=FP-OFP(tbl) : 35 : sreg. ds=FP-SEG(tbl) : 36 : int86x (0xdc, ®, &outreg, &sreg) : 38 : reg. h. cl=0x0d; 39 : reg. x. ax = 0X0018 : tbl=&keytbl[l] [ 0 ] : 40 : reg. x. dx=PP_0FF(tbl) : sreg. ds=FP-SEG(tbl) : 42 : 43 : int86x(0xdc,®, &outreg, &sreg) : 44 : 46 : reg. h. cl=0x0d; reg. x. ax = 0X0017 : tbl=&keytbl-save [ 0 ] [ 0 ] : reg. x. dx=FP-0PF(tbl) : sreg. ds=FP-SEG(tkl 、 : 50 : int86x(0xdc, ®, åoutreg, &sreg) : 52 : reg. h. cI=0x0d; 53 : reg. x. ax = 0X0018 ; 54 : tbl=&keytbl _save [ 1 ] [ 0 ] : 55 : reg. x. dx=FP_0FP(tbl) : 56 : sreg. ds=FP_SEG(tbl) : 57 : int86x(0xdc, ®, &outreg, &sreg) : 58 : return; 60 : } / * キーコード退避域 * / / * キーコード定義 * / { 0X12 , 0 , 0 , 0 , 0 , の {0x7f, 0 , 0 , 0 , 0 , の reg, outreg, sreg: *tb に / * I NS キー定義値の取得 * / / * PC -9801 拡張割り込み * / / * DE しキー定義値の取得 * / / * I NS キーの定義 * / / * DE しキーの定義 * / e I se / * INS キー定義の復旧 * / / * DEL キー定義の復旧 * / get- 1 char List 2 1 : #include く stdio. h> 2 : #include く dos. h> 3 : 4 : #define KHEADI 5 : #define KHEAD2 6 : #define KHEAD3 7 : #def ine KHEAD4 8 : #define HANKJI 9 : #define HANKJ2 10 : #define HANKJ3 11 : #define HANKJ4 * 頭終頭終 先最先最 頭終頭終 先最先最界界界界 境境境境 界界界界 境境境境 イイイをづ ' づイイ ' づ 2 2 ワ】 2 ノ第第第第 1 1 1 1 字字字字 第第第第 漢漢漢漢 字字字字角角角角 漢漢漢漢半半半半 0X80 0x9f 0xe0 0xfe 0x3f 0xde 0x7f 0x9e
i09 0055 薈 0 「 ( 十十 A Debug\: List 3 ている。 assertSet, traceSet, error Set, inf0Set はアクセス関数て、ある。オプ ジェクト指向プログラマにとって , アクセ ス関数を使用するかしないかという問題は , 宗教的な問題 (religious matter) て、ある。あ るものは信じて使用し (swear (y) , またあ るものはまったく同調しない (others (t) 。 この問題に対してプログラマはどのような 立場をとるかを決心しなければならない 私といえばアクセス関数を使用するほ うの立場だが , 読者のみなさんを同じ立場 に転向させようとまて、は思っていない assert マクロは Trace クラスのあとに定 義している。これは簡単なマクロて、あるが , パラメータ condition を文字列に変換する文 字列化オペレータ ( # ) を含んている。ただた んにマクロバラメータを引用符て、囲むとい う古い方法は , より新しいコンパイラて、は 動作しないものがあるかもしれない ヘッダファイルの残りは , クラスのイン プリメントの始めの部分て、ある。可能なか ぎりの関数をインライン関数として作成し たのて、 , デバッグコードは最小のオーバー ヘッドをもつだけて、すんだ。トレースのオ ーバーヘッドは , トレース出力が禁止され , コンノヾイラが inline 孑旨令に従っているとき , ほとんどのトレース呼び出しの AND 演算や 分岐命令からなる。 ファイル trace. cc(List4) には , スタート アッブオプジェクト initTraceMode の宣言 を含んて、いる。これは , どのプログラムて、 もこのモジュール以外て、は必要て、なく , 外 部シンポルテープルに存在している必要が ないのて、 , スタティックとして定義してい スタートアッブオプジェクトのクラスの コンストラクタは , operator( ) の 2 番目の こて、定義され バーション定義とともに る。以前にも述べたとおり , このバージョ ンは可変個数の引数をとるためインライン 関数として定義していない。したがって , #condition) : \ い NE trace. d0Assert(--FILE 32 : 33 : } 34 : 35 : static const int incr 37 : enum { 38 : 39 : 40 : errorMask 44 : 45 : i n ⅱ ne 46 : Trace: :Trace(const char *name) if (traceSet()) { indent() : fputs (name, stderr) : fputc ('Yn' stderr) : 52 : I eve ー十ニ i ncr : 56 : i n ⅱ ne 57 : Trace: : Trace (void) 58 : { 59 : lncr; 62 : inline Trace& 63 : Trace: :operator() (const char *str) i f (traceSet ( ) ) { indent() : 66 : fputs (str, stderr) : fputc ( ・ Yn' stderr) : 70 : return *this; 73 : i n 1 i ne vo i d 74 : Trace: :doAssert(const char *fi le, int line, const char *msg) if (assertSet() ) { indent() : fprintf(stderr, "Xs(Xd) : assert Y"XsY" failedYn" file, ー ine, msg) : 86 : inline void 87 : Trace: : indent (void) for (int i level; fputc ( ' stderr) : 94 : i n ⅱ ne i nt 95 : Trace : : assertSet ()o id) return traceMode & assertMask : assertMask = 0X01 , traceMask = 0X02. = 0X04 , infohask = 0X08 40 CMAGAZINE 19 4
恥すかしな力あドジりました 忙しくて , プログラミングから 1 か月近く も遠ざかっているときの欲求不満をどう解 消するかというと , 自分のオリジナルな構 想からスタートて、きるほどの十分な時間は ないのて、 , 「 DDJ 』などの雑誌に載っている リストの、、手移植クをします。 「 DDJ 』などの米誌て、は , たんなるルーチン の例示やアルゴリズム解説のためのコード 片てはなく , ひとつの実用プログラムの完 全なリストが , それを解説する楽しくてタ メになる記事とともに載っています。 そういうソースリストは , もちろんディ スクて、入手することもて、きるし , BBS から ダウンロードすることもて、きますが , こん な方法て、入手しても , おもしろくもおかし くもないし , 自分のプログラミング能力の 向上には , まったく寄与しません。 リストを自分の手て打ち込みながら , 「な るほど ! 」と感心したり , 「ここはまずいな」と 批判したりすることが , 血となり肉となり , 身についていくのて、す。 そういうときに , 丸写しということはま ずありません。 IBM ー PC が前提となってい る部分は PC ー 9801 シリーズ対応に , あるいは MS ー C 用に書かれているリストは , 私の常用 処理系てある TurboC 用に書き換えます。と きには , PascaI や BASIC< 書かれているリ ストを C に書き換えることもあります。 上記のような書き換えばかりてはなく , 原作者のバグフィクスや , プログラムの機 能の拡張や変更を意図しながら手移植を進 めることも多いのてすが , このちょっと した機能拡張〃意図によって , 泥沼にひき ずりこまれてしまうことがあるのて、す。 118 CMAGAZINE 19 4 第 5 回 ズボラへの罰 岩谷宏 DDJ 誌は昨年の終わり , 読者サービスの 増刊号として「 C 特集号」を出しました。その 中に、℃ Printer" というプログラムがあり ました。これは ' 88 年 8 月号に N さんが発表し たプログラム ( 私の友人の訳により「 I / O 』誌 の本年 2 月号にリストとともに掲載 ) を , W さんが拡張・改良したものて、す。 これは C プログラムのソースファイルをス キャンして , プログラムの構造をツリー図 て、出力するものてす。関数の定義やコール に関する統計資料も作成して出力します。 それぞれの関数がどのファイルて、定義され ているかというデータとともに , Fig. 1 のよ うなツリー図にこて、は概念化しています ) を出力するのて、す。 この図から , プログラムの構造を展望て、 きます。 Fig. 1 て、は , main( ) は 4 つの関数を コールしている , そのなかの onion ( ) はふた つの関数をコールしている , などがわかり ます。そしてそれぞれの関数が , どのファ イルにあるかもわかるのて、す。 原作者の N さんは , 自社て、つくるソフトウ ェアのドキュメント制作の自動化・省力化 を目指して , このプログラムを作ったそう て、す。 N さんの原作には , C のソースを読んて、パ ースしていくときのアルゴリズムに ノヾグ が 2 , 3 か所ありました。また外部関数と同 名の関数が , static 関数として定義されてい る場合に対する適切な措置が欠けていまし た。 W さんはこれらを改良したのて、す。 私がこのプログラムに関心をもったのは , ドキュメント制作云々てはなく , プログラ ムの理解を支援するツールとして使えると 思ったからて、す。複数ファイルから成る , 長めのソースを PDS などとして貰った場 合 , その長大なソースを直接眺めて全貎を 理解することはなかなか困難て、す。 以前に , [ 各ファイルの一各定義関数と一 そのなかて、コールされている関数 ] を , 覧表て、出力するプログラムを作ったことが あります。十分な時間があれば , こちらの main( ) garlic( ) onion( ) beef( ) eat( ) bufter( ) soysauce( ) oven( ) lemon( ) honey( )
List 9 intgstack& operator 十 (intgstack& intgstack& b) { new intgstack(). size 十 b. size); intgstack* ps return *ps : 1 の乙っ 0 4 戸 0 6 List 1 0 intgstack& intgstack: :operator 十 (intgstack& b) { new intgstack(size 十 b. size); intgstack* ps return *ps; 1 つなっ 0 -4 ド 0 6 べきてもありません ( 注 11) ーおよび + + て、は , 組み ます。 また , この逆の場合も考えられます。たとえば , 込みの型に対するように接頭形式と接尾形式を区別 代入演算子などては左辺項が型変換されると困るた することはてきません。また , a = a + b と a 十 = b, c. d と (&c)—>d, e Cf] と * ( e + f ) という関係が自 め , 演算子関数をメンバとして実装します ointgstack 動的に同じ意味になることはありません ( 同等の意味 の演算子 + = は , 以下のように使われないようにメ ンバ関数として実装されています。 に定義することはてきます ) 。 int aC5] ■演算子関数におけるメンノ哽数と intgstack b; フレンド関数い a 十 = b; このような式は意味がありません。優れたインタ メンバ関数もフレンド関数もあるクラスの非公開 こういうことにも注意 部のメンバにアクセスて、きる特権をもった関数て、す。 フェイスの設計のためには , する必要があります。 こまて、に述べた演算子関数も ( ) やロなどの演算子 以外て、あれば , メンバ関数あるいはフレンド関数の どちらにもなることがてきます。しかし , どちらて 第終わりに 定義されるかて若干動作が異なる場合があります。 クラス intgstack の演算子十は , List9 のようにフ 今回は , クラスのインタフェイスてあるメンバ関 こて , この演算 レンド関数として定義しました。 数 , フレンド関数について解説しました。多少話が 子関数をあえてメンバ関数として実装することにし 細かくなってしまいましたが , 正しく動作するイン ます (ListI の。 タフェイスの設計および実装するために , これらの メンバ関数の演算子関数は , 隠れた第 1 引数 this を 知識は必要だと思われます。次回はクラスの継承に もちます。この this に対して型変換がされないため 関する設計と実装の話をしたいと思っています。 に , このメンバ演算子関数は , 左辺項は intgstack イ ンスタンスそのものてなければなりません。たとえ 文献 ば , a および b が intgstack, c が int 型へのポインタて Stroustrup , B. , $The C 十十 Programming Lan ある場合 , a = b 十 c; guage" , Addison-Wesley , 1986. 注 1 1 FORTRAN などの という式ては , c に型変換が適用され , 処理が行われ Lippman , S. B. , ヾ C 十十 Primer" Addison ー Wes 指数演算子のような , 新た な演算子 * * は , 定義する ますが , 以下の式はエラーになります。 ley , 1989. ことはてきない。また演算 子・は , ほかの言語 (BASIC Aho A. V. and UIIman,J. D. , "Principles 0f c 十 b; a など ) の指数演算子の優先 度とは異なるため , そうす Compiler Design" , Addison-Wesley , 1977. ふつうの演算子十はこのどちらの記述も許すため , れば混乱は避けられない。 たいてい operator 十はフレンド関数として実装され C 十十プログラミング入門 105
五ロ はじめて学ぶプログラミンク double asin( double x ) ; double 型の引数 x を受け取り , arcsin( x ) の主値を返します。したがって , 引数 x の定義域は一 1 坙 x 坙 1 て、あり , asin ( x ) の値域は一 2 坙 asin ( x ) 坙な / 2 とな っています。 ( ⑤ acos 逆余弦関数て、す。書式は以下のようにな ります。 double acos( double x ) ; double 型の引数 x を受け取り , arccos( x ) の主値を返します。したがって , 引数 x の定義域は一 1 坙 x 坙 1 て、あり , asin ( x ) の値域は 0 坙 acos ( x ) ミとなっていま す。 ( atan 逆正接関数て、す。書式は以下のようにな ります。 double atan( double x ) ; double 型の引数 x を受け取り , arctan( x ) の主値を返します。したがって , atan( x ) の値域は一 2 く atan( x ) < / 2 とな っています。 double cosh( double x ) ; のようになります。 double 型の引数 x を受け取り ,cosh( x ) double 10g ( double x ) ; ⑦ atan2 を返します。 double 型の引数 x を受け取り , ln( x ) を 引数がふたつの逆正接関数て、す。書式は 返します。 x の定義域は 0 < x となっていま ⑩ tanh List1- ⑧のようになります。 す。 双曲線正接関数て、す。書式は以下のよう double 型の引数 x と y を受け取り , ar ctan( Y/ x ) を返します。 atan2( x ) の値 ⑩ Iog10 になります。 域は一なく atan ( x ) < となっていま 対数の底が 10 の常用対数てす。書式は以 double tanh( double x ) ; double 型の引数 x を受け取り , tanh ( x ) 下のようになります。 す。 を返します。 double 10g10 ( double x ) ; double 型の引数 x を受け取り , 10g ( x ) を ⑧ sinh 双曲線正弦関数て、す。書式は以下のよう ⑩ exp 返します。 x の定義域は 0 く x となっていま になります。 e の指数関数て、す。書式は以下のようにな す。 double sinh( double x ) ; ります。 double 型の引数 x を受け取り , sinh ( x ) ⑩ pow double exp( double x ) ; べき乗関数てす。書式は List1 ー⑨のように を返します。 double 型の引数 x を受け取り , e の x 乗を 返します。 なっています。 double 型の引数 x と Y を受け取り , x の yk ⑨ cosh 双曲線余弦関数てす。書式は以下のよう を返します。 x = 0 のときに y 坙 0 , また ⑩ log は x く 0 て Y が整数てないときに , 領域工ラ 対数の底が e の自然対数てす。書式は以下 になります。 はじめて学ぶ C プログラミング 127 List 1 2 十 > 十・ , 、 , を 1 ー 1 X く・ー 0 っ . 0 8 っ朝 LO 1 よ ↑一↑ー 0 ・ 1 ー 0 十し X X い 0 A— 0 よし Cd ・ 0 >< 0 cd 十し 0 0 0 1 よっ 0 っ 0 -4 ・ド 0 CD ー 8 0 11 ワ朝っ -4 ・ -. 0 C.D ー 8 0 1 よワ朝 C*O 一 4 ・ - -0 CD 々ー 8 0 14 ワ 0 っ -4 、 1 1 よ 1 よ 1- ・ 1 ー 4 1 よ 11 1 ー 1 14 っ朝・つ朝っ 0 ワ 3 り 0 っ 0 り 0 っ 0 ワ 0 っ 0 っ 0 っ 0 っっ 0 e 1 se 0 , LO 00 0 れじ 0 十 0. 0 for ( i = 0 : i く MAX_v; i + + ) { for ( j = 0 : j く MAX-H ; j + + ) putchar ( s [ j ] [ i ] ) :