大域脱出ーーⅵ加 / 衂加 本ロ■ 五ロ 乗松保智 今回は標準ライプラリ関数 setjmp / longjmp を取り上げま す。これらは例外処理などで使われる関数で , 通常の関数 のコール / リターンとは動作か異なりますから注意が必要 です。 setjmp/longjmp の動作を説明した後 , 応用例とし て式を評価して値を返すサプルーチンを作成します。 CALL 命令は CALL の次の命令のアドレス RET 命令はスタックからポップしたアドレ 関数のコールとリター ( リターンアドレス ) をスタックにブッシュ スをプログラムカウンタに代入します。し して , プログラムカウンタに CALL 命令のオ たがって , p 「 intf の処理が終了して RET 命令 関数はサプルーチンて、あり , そのコール ペランドを代入します。つまり , 戻ってく を実行すると , スタックの先頭にブッシュ とリターンは機械語の命令 CALL / 日 ET のペア るべきアドレスを憶えておいてからジャン されていたアドレス , つまり CALL printf の て、実現されます。 プします。プログラムの実行に従って , ス 次の命令に制御が移ります。同様にして , たとえば List 1 の単純なプログラムは大雑 タートアップルーチン内の CALL main の次 main の処理が終わるとスタートアップルー 把にいえば Fig. 1 のようにコンパイルされま の命令のアドレス , main 関数内の CALL チンの main の呼び出しの次の命令から実行 す。この図て、は関数名の先頭に ( アン p ⅱ ntf の次の命令のアドレスがスタックにプ が再開されます。 ダースコア ) がついていることに注意してく ッシュされていきます。 このように , CALL 命令て、サプルーチンを ださい。これは関数名が機械語のレジスタ 名などと重なってアセンプルエラーを生じ Fig. 1 List 1 のコンパイル例 ないための処置て、す。 MS ー C や Turbo C を含 む多くの C 言語の処理系は関数名の先頭にア ンダースコアを付加しますが , LSI C ー 86 は 関数名の最後にアンダースコアを付加しま す。 Fig. 1 て、は , main も printf も機械語命令 RET て、終了しています。 main 関数の中から printf を呼び出している命令は CALL て、す。 main も p 「 intf と同じく関数て、すから , スター トアップルーチンから CALL 命令て、呼び出さ れます。 Fig. 2 は Fig. 1 のコードが実行されていく ときのスタックのようすを示しています。 ただしこの図て、はローカル変数などの領域 は無視してあります。 126 CMAGAZINE 19 10 pri ntf: 関数の前処理 , 関数の後処理 RET , 関数本体の処理 , p ⅱ n 廿を呼び出した CALL 命令の ; 次の命令に移る mal n: ・関数の前処理 CALL ; 関数の後処理 R ET , p 「 in 廿関数を呼び出す pri ntf ; main を呼び出した CALL 命令の ・次の命令に移る
: 龜新 MS - DOS プロクラミング入門 そこて、本連載ては MS ー DOS の機能のなか 場合には筆者が使用経験し , ある程度の資 などを考慮したものといたします ( ただし , からおもしろそうなテーマをひとつずっ取 料も入手している 動作確認は PC ー 9801 シリーズのみ ) 。 り上げその原理を説明し , アセンプラや C PC ー 9801 シリーズ て、きるだけ多くのコンパイラに対応させ J ー 3100 シリーズ 言語による簡単なプログラミング例を示し たいのて、すが , ヘッダファイルやライプラ ていくことにしました。解説を希望する機 AX マシン リ関数に違いがあるためなかなか難しい面 能がありましたら , 本稿への手厳しい批判 TabIe 1 MS-DOS の構成 を添えて編集部宛てにお便りをください 機能 ファイル名 とりあえず連載を開始するにあたって ドライプ A : , B ・ テパイスドライバ ( CON , AUX, CLOCK, 9. SYS 1 リダイレクト MSDOS. SYS システムファンクション INT 20h ~ 3Fh 2 ファイルの日付 / 属性の取得と変更 COMMAND.COM 内部コマンド ( COPY , TYPE, DEL, ・・・ ) , バッチファイルの起動 3 ファイルの検索 標準出力に出力するプログラム ( testout. c) 4 デバイスドライバとの会話 というような項目を拾い上げてみましたが 1 : #include く stdio. h> ( もうひとつパッとしないてすね ) , 今後の 2 : 3 : int main(void); 反響により内容は変わっていくと思います 4 : 5 : int main() し , 任期を満了しなくてもネタ切れと同時 printf( " 標準出力 \ n " ) : 7 : に本連載もヾ無事〃終了してしまうてしょ return( 0 ) : 8 : う ( 連載止めるにや刃物は要らぬ / 批判の 3 つも書けばよい ! ? ) 。 前提となる MS-DOS の環境 本稿を書くうえて想定している M S ー D O S の環境としては , MS-DOS Version 3. x Microsoft C Version 5.10 Microsoft Macro Assembler Version 5 . 10 を標準の環境とし , とくに断わらない場八 はこの環境を前提にしています。動作確認 は PC ー 9801 シリーズの MS-DOS 3.3B ( 1989 ー 11 ー 27 ) て行いますが , てきるだけマシン依 存を避けたプログラミングをこころがけま す。したがって MS-DOS 3.1 以降が動作す るマシンて、あれば問題なく動作するはずて す。ただし , PC ー 9801 シリーズの MS-DOS 3.1 て、もバージョンにより一部動作の異なる 部分がありますから , ほかのマシンの MS- DOS< も動作が異なる場合があるかもしれ ません。手元にマシンがあれば確認するの てすが , 一介のエンジニアに各種のマシン がそろえられるはずがありません。その点 もし , マシンに依存す はご容赦ください List 出力のリダイレクトテストプログラム 1 (testl . c) 1 : # i ncl ude く std i 0. h > 2 : #include く process. h> 3 : 4 : int main(void); 5 : 6 : char *cmd[] "testout ” 7 : 8 : NU しし } : 9 : int main() return( spawnvp( P-WAIT, cmd[ 0 ] , cmd ) List 出力のリダイレクトテストプログラム 2 (test2. c) List 1 : #include く stdio. h> 2 : #include く process. h> 3 : 4 : int main(void); 5 : 6 : char *cmd ロ 7 : command ” 8 : 9 : ” testout ” NUL し } : 13 : int main() return( spawnvp( P-WAIT, cmd[ 0 ] , cmd ) 新 MS-DOS プログラミング入門 93
のに , 、、 Outofmemory 〃のエラーを発生し ような文を単純に展開すると List 2 のように コンパイルて、きなかった。 main 関数から ふたつの文になってしまう。 List 1 の場合て、は , このようにふたつの文 、、 ( pow ( fl , 3 ) - ... 〃の 1 行を削除するとエラ ーを外すことがて、きたが , 原因はよくわか に展開されてしまうような文が , ひとつの Turbo C 十十には , 汎用のクラスライプ らない。 Turbo C 十十の構文解析に問題が 文しか書けない位置 ( 最初の for 文に続く一 ラリがソースっきて、含まれている。このク あるのかもしれない。反対に , List 5 に示し 文 ) にあるために生じたものと思われる。 List たプログラムは , TurboC 十十以外の処理 ラスライプラリは , 、、 Object" という名前の 3 のようにふたつめの f0 「文をプロックて、囲ん 系て、はうまく処理て、きなかった。 C 十十て、 抽象クラスを項点とする単純継承による階 て、やるとエラーを外すことがて、きた。 は , 0 ポインタて、 delete することが許されて 層構造をもっており , Sma Ⅱ talk の構造とよ Turbo C 十十以外の処理系てはこのプロ く似ている ( 継承を用いず , 独立したクラス いる。しかし , List 5 のように仮想化された ックづけを暗黙のうちに処理してくれるが , TurboC 十十はやらないようだ ( 編集部注 : ディストラクタをもっクラスの 0 ポインタを の集合体て、ある ZortechC 十十のクラスライ これは AT&T C 十十 Ver. 2.0 の仕様に準 プラリとは対象的て、ある ) 。 delete すると , MIWA C 十十や Zortech また , このクラスライプラリを使用した 拠しているそうて、す ) 。 C 十十て、は暴走してしまった。また IDEA サンプルプログラムも含まれているのて、 , C 十 + て、は実行時工ラーを発生し , 実行を中 ふたつめのエラーはサンプルプログラム クラスの使い方やオプジェクト指向の考え (smplexpr) の main 関数て、生じた (List 4 ) 。 断してしまった。 Turbo C 十十だけは正常 のプログラムは複雑て、もなく大きくもない 方を学ぶよい教材になるだろう。 終了した。 クラスライプラリ List List 1 よっなっ 0 -4 LO 1 : main() for ( i nt i = 0 : i く 10 : i + + ) 3 : for( 4 : int j=o; j く 10 : j 十十 for( int j=o; j<IO: j + 十 ) : ↓ for( j=0; j く 10 : j + + ) : List LiSt 1 : VOid main() / / 省略 3 : 4 : expr fl = x + y + z, f2 ーー x + y + 2 , f3 = x-y + 2, f4 ー X 十 y—z; (pow(fl. 3 ) ー pow(f2, 3 ) ー pow(f3, 3 ) ー pow(f4, 3)). print(); 5 : 6 : / / 省略 1 : main() for( int i=0, i く 10 : i + + ) { 3 : for ( 4 : int j=o; j く 10 : j 十十 5 : List List List 1 : struct A { 2 : virtual 4 : 5 : ma i n ( ) 7 : A *p = 0 : 8 : del ete P , 1 : #include く stdio. h> 2 : void main() 4 : i nt i : for( j=o; j く 1000 : j + 十 ) 5 : printf( "XdYn" 6 : 0 0 0 1 よ 0 0 ー 0 74 CMAGAZINE 19 10
′学 五ロ 三一口 呼び出した関数の実行は一時停止し , 呼び 出された関数の実行が開始され , その処理 が終了した後に再開されます。この CALL/ RET の動作が関数呼び出しにおけるプログラ ムの実行順序の制御の基本て、す。 大域脱出 ところて , プログラミングをしていると きに , 次のような状況に出会うことがあり ます。 処理の内容をいくっかの関数に分割し , ある関数が別の関数を呼び出していて , そ の関数がさらに別の関数を呼び出している そしてこれが何重にも繰り返されて いる場合を考えてください。つまり関数呼 び出しが深くネストしている場合てす。各 関数はその処理中にエラーを検出すると値 ERROR を返すとしましよう。 この場合 , 工ラーをチェックしながら処 理を進めていくコードは List 2 のようになる て、しよう。関数のネストが深くなっている ときにエラーを検出した場合 , いちいち if 文 て、の判定を実行しながら処理を開始したト とが保証されます。ということは , 関数呼 ップレベルの関数に戻ることになります。 出したところから一気にトップレベルに戻 CALL / 日 ET の組み合わせて、関数呼び出しを実 び出しに関しては if 文によるエラーチェック ることがてきればとても便利て、す。ェラー 現している以上 , これはしかたがありませ を検出した時点て、トップレベルに戻ってし は不必要になりますね。 このように , 通常の関数のコール / リター まうのて、すから , 呼び出した関数から戻っ ん。 ンの動作をバイバスして上位の関数へ直接 、しこのような状況ては , 工ラーを検 てきたときにはエラーは発生していない しカ リターンすることを「大域脱出」といいます。 ネストした関数呼び出しでのエラー処理 大域脱出のために C 言語に用意された標準ラ イプラリ関数が setjmp と longjmp て、す。 etjmp と longjm setjmp のプロトタイプ宣言は次の形をし ています。 int setjmp(jmp buf env) : jmp ー buf 型は現在の環境を格納するための 型て , setjmp. h の中て定義されています。 こていう「環境」とは環境変数のことて、はな く , リターンアドレスやスタックポインタ やそのほかのレジスタなどの CPU の実行環 境のことてす。 Fig. 2 Fig. 1 が実行されたときのスタックのようす Low Address CALL—printf の 次のアドレス CALL—main の 次のアドレス CALL—main の 次のアドレス CALL—main の 次のアドレス High Address (a) main の先頭 (b) printf の先頭 (c) p 「 in 廿から R ETC 戻ってきたとき 単純な関数呼び出しの例 int printf(char *p, / * pr i ntf のコード ( 省略 ) * / 3 : 5 : 6 : 7 : int main(int argc, char *argv[]) printf("hello worldYn"); 9 : 10 : } List 1 2 List f3() int i f ( 工ラーを検出した ) 3 : (ERROR) : 4 : return 7 : f2() 8 : i nt if (f3() ーニ ERROR) (ERROR) : return 14 : fl() 15 : i nt if (f2() = ERROR) 17 : (ERROR) : return 20 : } c 言語雑学講座 127
五ロ はじめて学ぶプロクラー ニンク List 2 List 1 ⅱ stl. c ファイルのオープン・クローズ 2 : 4 : # i nc 1 ud e く std i 0. h > 6 : void main(void) F 比 E * f Ⅱ e 1 ; 8 : 9 : f 叩 en("myfile. xyz' f ⅱ e 1 fclose(filel); 13 : } ⅱ st2. c ファイルのオープン・クローズ 2 : 4 : # i nc lud e く s td i 0. h > 6 : void main(void) 8 : F I し E * f i I e l; 9 : fopen("abcde. xyz" f i 1 e 1 fc I ose (f i 1 e 1 ) : 12 : 13 : } て Fig. 1 のような状態が確立されます。ファ イル ( デバイス ) をクローズすることによっ て , その結合は解消されます。 したがってファイルを取り扱う ( 読み・書 き ) ときには , そのファイルをオープンした りクローズすることが必要になってきます ( 注 : 本稿て、は , データをファイルから読む ( ファイルへ書く ) こととストリームから読 む ( ストリームへ書く ) ことを同じ意味て、用 List 3 1 i st3. c ファイルのオープン・クローズ 2 : 4 : #include く stdio. h> 5 : 6 : void main(void) char FiIeName[100]; 8 : F I し E *f i I e 1 : 9 : printf("lnput file name : PiI eName) : scanf("%s" 12 : if ((filel = fopen(FiIeName, printf("YnYnYafi le open error 14 : exit(-l); 15 : } e 1 se { printf("Yn%nSuccess fi le open%nyn") : 17 : fclose(filel); 20 : } = NULL) : XsYnYn ” FileName); いています ) 。 ファイルのオープン・クローズ ファイルに関して何か処理をしようとす るときには , 必ず次の手順を取ります。 1. ファイルのオープン 2. なんらかの処理 3. ファイルのクローズ こて、は重要なファイルのオープン・ク ローズに関するお話をしましよう。 前述のようにファイルをアクセスするに は前もってファイルをオープンする必要が あります。ファイルをオープンする場合 , 基本的に次の情報が記述されていなくては なりません。 ①どのファイルを ( ファイル名 ) ②どのようなモードで (read, write, append など ) そして , オープンしたファイルが , ③どのストリームと結合しているか を知る必要があります。 これを行ってくれ るのが fopen 関数てす。 はしめて学ぶ C プログラミング 133 TabIe 1 ファイル関係の関数・マクロ 1 ファイルのオープン・クローズ 関数名 fopen ・形式 FILE * fopen(const char *filename, const char * mode) ; ・機能 指定されたファイル名のファイルをオープンし , ファイルとプログラムを結ぶストリームと結 合する f 「 eopen 関数名 ・形式 FILE * freopen(const char * filename, const char * mode, FILE * stream) , 指定されたファイルをクローズし , その後同じファイルをオープンする。 mode を変更するとき ・機能 に用いる fclose 関数名 ・形式 int fcIose(FlLE * stream) , バッフアの内容をファイルに書き出したのちファイルをクローズする ・機能 関数名 fflush ・形式 int ffIush(FlLE *stream) , 出力バッフアに残っている内容を指定ファイルに書き出す。ファイルのクローズは行わない ・機能 2 ファイルからの入力 マクロ名 getc ・形式 int getc(FILE *stream) ,
工ル江ス・アイジャパン 0 「面 on from (ompiler ma 「 5 LSI C ー 80 今回は LSI C ー 80 の ROM に関する 質間を集めました。 0 ライプラリは ROM 化に使用で きますか ? ライプラリの関数の中には , オペレーティングシステム ( CP / M ) の機能に依存しているものがあり ます。 ROM プログラムて、は , これ らの関数は使用てきません。そこ て LSIC ー 80 パッケージには , 標準 ライプラリ clib(z). sof のほかに ROM 化用ライプラリ romlib(z). sof が用意されています。 ROM 化の場 合はこれをライプラリとしてリン クします。 また , ヘッダファイルは stdrom. h を使用します。 stdrom. h には ROM プログラムて、使用て、きる関数 のプロトタイプ宣言があります。 どのような関数が ROM 化に使用て きるかはこの stdrom. h を参照して 0 ROM 化用初期設定プログラム ( スタートアップルーチン ) はパッ ケージ中にふくまれていますか ? CP/M 下て動くプログラムの 初期設定はパッケージ中の ck. sof の中て行ってますが , ck. sof は ROM プログラムては使用不可能て、 す OLSI Cー80ては LSI C ー 86 のよう な ROM 化用初期設定プログラムは パッケージ中にはふくまれていま せんが , LSI C ー 86 のように複雑て、 はありませんのて、初期設定プログ ラムを用意していただきます。初 期設定は付録ディスク LISTI のよ うなアセンプラブログラムて行い 160 CMAGAZINE 19 10 ます。すなわち , スタックポイン タを設定し , 必要なハードウェア の初期設定を行ったのちに C の関数 main へジャンプするわけてす ( この とき , C の入口の名前がとくに main てなければいけないという理由は 何もありません ) 。 スタックポインタの設定は C 言語 て書くことは不可能てす ( たとえア センプリインライン関数を使って も ) から , 必ずこの初期設定用のア センプラブログラムは必要になり ます。この場合には , main に渡す パラメータはありませんのて , 通 常の CP/M- ドの main プログラムと は異なり , 付録ディスク LIST2 の ように書かなくてはなりません。 さて , 上の初期設定プログラム の使い方て、すが , まずこのプログ ラムを作り , アセンプルして . sof ファイルにしておき , リンクする ときに先頭につけます。 0 于ート入出力はどのように行 うのですか ? ポート入出力を行う場合は , machine. h の中て定義されている inp 関数と outp 関数 ( 実はアセンプ リインライン関数 ) を使用すること がてきます。 例として , 8251 ( intel 社のシリア ル I/O 用 LSI) の初期設定 , 1 バイト 入力 , 1 バイト出力を行うプログラ ムを用いて書いてみましよう ( 付録 ディスク LIST3 参照 ) 。 ただし , 標準て用意されている inp, outp は 8080 の in / out 命令をア センプリインライン関数て作って いるためポート番号を変数て指定 することがてきません。このよう な場合は , Z80 の IN / OUT 命令てア センプリインライン関数を作り使 用します。これはパッケージ中に はふくまれていませのて , 必要な 方はお問い合わせください。 このほかにも , 64180 用の IN/ OUT 用関数等も簡単にアセンプリ インライン関数て実現てきます。 0 絶対番地の参照は c 言語でど のように行うのですか ? A 入出力ポートが I / 0 空間てな く , メモリ空間に割り当てられて いる例て示します。 たとえば , ポートを 0FF00h 番 地 , 0FF01h 番地を使って操作を行 うとしますと , 付録ディスク LIST4 のように書けます。 0 割り込み処理は c 言語で記述 できますか ? A 割り込み処理も C 言語て行う ことがてきます。ただし , 割り込 みの入口ての全レジスタの待避 , 出口てのレジスタの回復 , そして 割り込みマスクの解除といったシ ーケンスなどの処理はアセンプラ て書かなければいけません。 例として , 1 秒ごとにタイマ割り 込みが入って , 時刻データを更新 するというプログラムを考えてみ ましよう。時刻は , それぞれ hour, minute, cond て現すこと にし , 割り込みは RST7 に入ってく るものとします。付録ディスク LIST5 を参照してください。 C のプ ログラム中て , 割り込みを禁止し たり , 解除したりという処理が必 要な場合は , ヘッダファイル machine. h のなかて定義されている di マクロと ei マクロを使用します。 さらに , 割り込み処理と通常の 処理の両方から呼ばれる関数があ る場合 , その関数は recursive にし ておく必要があります。再帰呼び 出しはしていなくとも再入の可能 性が要求される場合は , recursive 0 c 言語で定数テープルを ROM に配置することは可能ですか ? イニシャライズされている変 数のうち , 記憶クラスが static と extern のものはコードセグメント (ROM) に配置されます。これらの 変数はコードセグメントに置かれ るのて ROM プログラムては値を書 き換えることがてきなくなり , あ たかも「定数」てあるかのような振 舞をします。十分注意してくださ い。したがって逆に値を変更する 必要のある変数は明示的に代入文 て変数の初期化を行わなければい けません。 C 言語て、記述した変数が ROM , RAM のどちらに配置されるかを例 て示します ( 付録ディスク LIST6 参 照 ) 。また , 文字列は「記憶クラス static< ある初期化された文字の配 列」てあり , コードセグメントに配 置されるために , ROM プログラム てはその値を変えることはてきま せん。 誌面の関係てサンプルプログラ ムを掲載てきませんてした。 10 月 号分付録ディスクに収録されてい るリストを参照ください
力を行います。したがってこれらの関数を 用いる場合には , 「どのファイルと入出力を 行うか ? 」をシステムに知らせなければい けません。この目的のために fopen したとき に得られた , 「ストリーム制御変数へのポイ ンタ」を使うわけて、す。各関数 , マクロの 用法は Table 1 や本誌 4 月号を参照していた こて、はなるべく例題を 挙げて説明しましよう。 例 1 「 ASC Ⅱファイルの内容をディスプレ イに表示するプログラム ( List 4 ) 」 このプログラムて、は , main 憫数の引数の 機能を用いて , プログラム名ファイル名固 と入力するルールにしてあります。そして , 引数の数が合わない ( a 「 gc が 2 て、ない ) ときに usage: プログラム名 FileName <ret> と標準出力に表示して処理を中断していま す。また , ファイルがないなど fopen 時に工 ラーが生じたら , open file error : ファイル名 と表示して , やはり処理を打ち切っていま す。そしてファイル ( ストリーム ) から 1 文字 取ってきてはその文字を標準出力に書き出 す , ということを入力ファイルが終了する まて、続けます。プログラム中の getc は fgetc に変更してもまったく同じ働きをします。 putchar(c) ; ・形式 int feof(FILE * stream) : ・機能ファイルの終わりに達したかどうかをチェックするマクロ。 EOF に達していれは真 , そうでな ければ偽を返す マクロ名 fe 「「 0 「 ・形式 int fe 「「 0 「 ( 日 LE * stream) : ファイルの読み書き中にエラーが起こったことがあるかどうかをチェックするマクロ。工ラー ・機能 が生じていたら真 , 工ラーがなければ偽を返す clearerr 関数名 ・形式 void clearerr(FILE * stream) : ・機能 ファイルについてのエラーおよび EOF の状態を解除する TabIe 2-1 ファイルのオープンモード mode 意味 read モード。ファイルはすでに存在していること write モード。ファイルがなければ新設 , あれば空にされる append モード。ファイルがなければ新設され , あれば , ファイルの現在位置をそ のファイルの終わりに設定する ファイルの更新のために「 ead / w 「 ite モードでオープンする。 ただしファイルはすでに存在すること ファイルの更新のために「 ead / w 「 ite モードでオープンする。ファイルがなければ 新設され , あれば空にされる ファイルの更新のために「 ead / w 「 ite モードでオープンする。ファイルがなければ 新設され , あればアベンドされる は , TabIe 2-2 文字 意味 テキストモード バイナリモード TabIe 3 標準テキストストリーム ストリーム stdin stdout stderr これらの他に OS ( 処理系 ) によってはプリンタ出力 , 補助出力など用意されているものもある 通常割当テパイス 呼び名 標準入力 標準出力 標準工ラー出力 キーボード 画面 画面など は , PUtc(), stdout) ; あるいは , fputc(), stdout) ・ - としても同じ働きをします。 List 5 List 4 list5. c A S C I I ファイルの表示その 2 2 : 4 : # i ncl ud e く std i 0. h 〉 5 : 6 : VOid main(int argc, char *argv[]) 8 : i nt C : 9 : F I し E 料 nF i 1 e : if (argc ! = 2 ) { fprintf(stderr, ex i t ( ー I) : 1 ist4. c A S C I I ファイルの表示 2 : 4 : #include く stdio. h> 5 : char *argv[]) 6 : VOid main(int argc, 8 : int c: 9 : F I し E * I nF i I e : if (argc ! = 2 ) { printf("usage ・ . %s FileName \ 心 く ret> exit(-l); argv[0]) : %s Fi leName く ret> Yn ” usage: , argv [ 0 ] ) : CMAGAZINE 19 10 136
やはりエラーメッセージは標準工ラー出 力に出したい , と考えるならば , 工ラーメ ッセージを出す printf 関数を fP 「 intf 関数に変 更し , 出力ストリームを stde 「「にすればよい て、しよう (List 5 ) 。 例 2 「ファイルをコピーするプログラム ( List 先のプログラム同様 main 関数の引数を利 用します。用法は , プログラム名ファイル名 1 ファイル名 2 固 という形を取ります。ファイル名 1 のファイ ルをバイナリモードて読み込み用にオープ ンし , ファイル名 2 のファイルをやはりバイ ナリモードて、書き込み用にオープンします。 オープンした後は , 先ほどと同様ファイル の終わりまて、 1 文字ずつ読んて、は書いてを繰 り返します。 List 6 をちょっと改造してみましよう。 List 7 はほとんど List 6 と同じプログラムて すが , ファイルの終わりを検出する feof を利 用しています。 feof は , ファイルが終わりに達していたら非 0 , 達していなかったら 0 を返します。プログラム中て、は , while( ! foef(lnFiIe)) と使用しています。ファイルが終わりに達 していない間処理を繰り返す , という記述 て、す。これによって , ファイルの終わりま て、 , 1 文字ずつ読み書きを繰り返すことがて、 きるわけて、す。 if((c=fgetc(lnFiIe)) = =EOF) という形のファイルエンドの検出は , fgetc 関数の機能を利用しています。ストリーム がファイルの終わりて、ある場合 ( これは feof て、識別可能 ) は EOF を返す , という働きが fgetc にあるだめて、す。しかし , ストリーム がバイナリモードて、オープンされている場 合には , ファイルの終わりは feof を , 工ラー の検出には fer 「 0 「を用いるべきて、しよう ( コ ラム 1 参照 ) 。したがって List 6 より List 7 の ほうがお勧めて、きるスタイルて、す。 例 3 「ファイルを 1 行すっコピーするプロ グラムを fgets, fput 関数を用いて作 ってみましよう。コピーするファイ CMAGAZINE 19 10 138 List 7 8 9 0 1 つなっ 0 っ 0 っ 0 fcIose(InFiIe); fc I ose(0utPile) : List 8 2 : ⅱ st8. c ファイルの行ごとコピー 4 : #include く stdio. h> 5 : 6 : void main(int argc, char *argv[]) char str[256] : 8 : 9 : int count; FI し E *InFile, *0utFile; if (argc ! = 3 ) { 12 : fprintf (stderr, exit(-l); i f ((InFiIe = fopen(argv[l], = NULL) { : XsYnYn", argv[l]) : fprintf (stderr, "open f i le error exit(-l); i f ( (0utF ile = fopen (argv [ 2 ] , 20 : fprintf(stderr, "open fi le : XsYnYn", argv [ 幻 ) : error exit(-l); 22 : 23 : 24 : 25 : 26 : 28 : 29 : 30 : 32 : 33 : 34 : } InputFi 厄 OutputFi le く ret> Yn", argv[0]) : usage: Xs count ニ 0 : while (fgets(str, 256 , InPi le) ! = N 乢い { count 十十 : fputs (str, OutFiI e) : printf("%d 行転送しました Yn" fcIose(InPile); fcl ose(OutFiIe) : count) : List 9 1 ist9. c ファイルの行ごとコピーその 2 2 : 4 : #include く stdio. h> 6 : void main(int argc. ・ char *argv[]) char str [ 256 ] : 8 : 9 : int count; P I し E nF i 1 e, *0utF ⅱ e : if (argc ! = 3 ) { fprintf(stderr, exit(-l); if ((InPile = fopen(argv[l], = NULL) { fprintf (stderr, ” open file error exit(-l); i f ( (0utP ile = fopen (argv [ 2 ]. 20 : fprintf(stderr, open file error 22 : exit(-l); 23 : 24 : 25 : 26 : 28 : InputFile 0utputFi le く ret> Yn", argv[0] ) : usage: %s : Xs%nYn", argv[l]); : XsYnYn", argv[2]) : count = 0 : while (fgets(str, 256. InFile) = N 乢し ) { count 十十 : fputs (str, 0utFil e) : fputs (str, stdout) : printf("Xd 行転送しました Yn" count) :
PT C (signed int)C ・ 算術演算子 十一 & 十一 & ところが , BSD ( および UNIX との互換性 を考慮した多くの処理系 ) て、は , 上記て、定義 されている値保持規則て、はなく , unsigned 型はつねに unsigned int 型に拡張されるとい う規則が採用されています。このため以下 のようなプログラム例て、は異なる結果が引 き起こされる場合があります。 #include <STDIO. H> く = くく main( ) unsigned Char C ; 句読点 c=getchar( ) : if(c ー @ ' く 0) puts( "LO" ) ; else puts( " 印 " ) ; このプログラムを MS ー C て、コンパイルして 実行すると , キーポードからネ@″よりア スキーコードの小さい文字が入力された場 合は、、 LO 〃が , 大きい文字が入力された場 合は、、 HI 〃が表示れます。ところが , 同じ プログラムを QuickC て、コンパイルして実行 すると , つねに、、 HI 〃が表示され , "LO" が表示されることはありません。 また , 2 項演算子を使用して異なるデータ タイプの演算を行う場合には , 以下のよう な拡張規則により両方の値が同じデータタ イプに変換されます。 if(A = = LongDouble ー = LongDouble) 空白 水平タブ 垂直タブ 空白 注釈 文字 注釈 プロジェクト PragmaC 53
五ロ はじめて学ぶプログラー ニンク List 4 List 5 if ((InFile = fopen(argv[l], printf("open file error exit(-l); if ((InPile = fopen(argv[l], = NU しし) { fprintf(stderr, open fi le error : XsYnYn" exit(-l); argvC1]) : -4 ド 0 々ー 8 0 、 1 り 0 っ 4 1 よ、 1 1 よ 41 1 よ、 1 っ 0 つなつなつなっ 0 4 0 れ 0 行ー 8 0 11 っ 0 っ 0 4- 、 1 1 1 ー 14 1 一 1 よっ 3 り 0 り 0 00 っ 0 = NUL し ) { : XsYnYn", argv[l]) : while ()c = getc(InFiIe)) ! = EOF) putchar (c) : fclose(InFile); while ()c = getc(InPiIe)) ! = EOF) putchar(c) : fcIose(InFile); List 6 コラム 1 テキストモードと ノヾイナリモード fopen のモード指定には , テキストモード とバイナリモードとがあります。テキスト モードではファイルをテキストファイルと してオープンしバイナリモードではファ イルをバイナリファイルとしてオープンし ます。それぞれのファイルの定義は以下の ようになっています。 テキストファイル 行を構成する , 整列した文字の列 ( 行端 には改行文字が付加されている ) からなる テキストで構成されているファイル バイナリファイル すべての制御文字をふくみ得る , 整列 された文字の列であるパイナリからなる ファイル。データの内部表現をそのまま 記憶し得る ふたつの形が混在して少々めんどうです が , かといってバイナリだけにしてしまう と , 不都合が生じてきます。 たとえば , テキストファイルでは行端の 改行文字は非常に重要な句切りとなってい ます。 OS に応じて改行文字のコードは異な るので自動的にコード変換されている場合 がほとんどです。ところが , バイナリモー ドにするとこのコードの自動変換がされす , 一切を加工しないでデータをやりとりしま す。テキストファイルでファイルの終了と 決まっているコードですら , 一般のデータ として処理してしまうため混乱を生じてし まうこともあるのです。 逆にファイルのコピーをしたいようなと きには , データの加工は一切必要ありませ んから , List 6 , 7 のようにバイナリモード でファイルをオープンして使います。 1 ist6. c ファイルのコピー 2 : 4 : #include く stdio. h> 5 : 6 : VOid main(int argc, char *argvC]) 8 : int c; FI し E *InFile, *0utFiIe; 9 : if (argc ! = 3 ) { fprintf(stderr, exit(-l); ((InFile = fopen(argv[l], i f fprintf(stderr, open fi le error exit(-l); if ((0utFile = fopen(argv[2], fprintf(stderr, "open fi le error 20 : exit(-l); 22 : 23 : 24 : 25 : 26 : 28 : 29 : } usage: %s lnputFiIe OutputFile く ret> argv[O]) : : XsYnYn", argv[l]) : : XsYnYn", argv[2]) : while ()c = fgetc(InPiIe)) ! = EOF) fputc(), 0utFi (e) : fclose(InFiIe); fclose(0utPi (e) : List 7 2 : list7. c ファイルのコピーその 2 4 : #include く stdio. h> 5 : 6 : VOid main(int argc, char *argv[]) 8 : int c; 9 : FILE *InPile, *0utFile; if (argc ! = 3 ) { fprintf(stderr, exit(-l); 14 : i f ((InPile = fopen(argv[l], fprintf(stderr, open f i le error . XsYnYn", argv[l]) : exit(-l); if ((0utFi le = fopen(argv[2], 20 : fprintf(stderr, "open file error . XsYnYn", argv[2]) : exit(-l); 22 : 23 : 24 : 26 : usage: Xs lnputFile OutputFile く ret> Yn", argv[0]) : while (!feof(InFile)) { = fgetc(InPile) : C fputc (), 0utFi (e) : はじめて学ぶ C プログラミング 137