10.8 工ラー発生時の対処 一例外処理で有効な goto 文 ファイル処理の例外処理に goto 文が有効な場合があります。たとえば、ある関数 sample で、 3 つ のファイル (samplel. txt, sample2. txt, sampIe3. txt) をオープンして、もし 3 つのファイルを正常に オープンすることができたら 0 を返し、 1 つでも失敗したら残りのファイルはオープンせずに一 1 を 返す関数を考えてみましよう。 ファイル処理の操作で大切なことは、オープンしたファイルは必すクローズしなければならない ことです。たとえば、 1 つ目のファイルのオープンに成功して、 2 つ目のファイルのオープンに失 敗したとしましよう。その場合は、ファイルのオープンに成功した 1 つ目のファイルをクローズし てから関数を抜ける必要があります。同様に 3 つ目のファイルのオープンに失敗した場合も、その ような場合を考える必要があります。こういった処理をいちいち検出した if 文の中で行うのは、 スしやすく、プログラムも複雑になってしまいます。 このようにファイルをオープンしてその後クローズしなければならないようなケースは、 c では、 goto 文を使用して、すっきりした処理を行うことができます。 g 。 to 文を使用した例をリスト 10.8 に fpl ! = NULL ) fp2 ! = NULL ) fp3 ! = NULL ) fopen ("samp1e3 リスト 10.8 例外処理て g60 文が有効な例 示します。 int sample ( ) F 工 LE F 工 LE FILE int if ( if ( if ( re セ ERROR : if ( if ( if ( *fpl *fp2 *fp3 て e セ (fpl (fp2 (fp3 0 ′・ てセ : NULL : NULL : NULL : fclose ( fclose ( fclose ( fopen ("samplel . txt" fopen ( "samp1e2. セ米セ " . セ x セ " fp3 fp2 fp 1 NULL ) go セ 0 ERROR ー NULL ) go セ 0 ERROR ー NULL ) 90 セ 0 ERROR; マ 19 マ 20 マ 21 詳しくは、 coffee break 10.2 を参照してください。 W ⅲ dows 系のファイルシステムの場合です。 制御コードは、直接テキストファイルで表現することができません。制御コードを c で表現するには、エスケープシー ケンスを使用します。 347
8 章ポインタ なので変数です。しかし、今まで学習してきた変数とは異なる点があります。ポインタ変数 , 8 は、 それが指し示す ( つまり、ポイントする ) 対象となる変数が存在しなければ、何の意味もないという ことです。つまり、ポインタ変数単独では、存在価値がないのです。 ポインタ変数のイメージを図 8.2 に示します。 0X1000 番地 ポインタ変数 0X1000 番地 0X1004 番地 ・図 8.2 ポインタは変数の番地を格納する変数 図 8.2 は、ポインタ変数が、 0X1000 番地に存在する int 型の変数 ( 10 が格納されている ) を指してい る様子を示しています。 また、ポインタも変数なのでアドレスをもっています。次の図を見てください。 ポインタ n 番地 m 番地 ・図 8.3 ポインタも変数の 1 つであり、メモリの中におかれる このように、ポインタ変数も他の変数と同じメモリ上にあります。 ポインタの特殊なものとして、 NULL ポインタ (NULL pointer) と呼ばれるものがあります。 NULLV9 ポインタは、すべてのビットが「 0 」であり、どこのアドレスも指さないポインタのこと をいいます。どこのアドレスも指さないので、 NULL ポインタを用いた参照を行うと思わぬ障害に 出会いますⅵ 0 以降、特別な理由がなければポインタ変数とポインタは同じ意味で用います。 NULL はく stdio. h > でマクロ定義してあり、「 0 」の値をもちます。 NULL ポインタは、通常ポインタを返す関数のエラー の値として用いられます。 プログラムが暴走したり、実行時工ラーとなる可能性があります。 マ 10 272
7 章文字列 期化します。 char s セて 1 [ 6 ] これをパソコンで実行したところ、次のように表示されてしまいました。 しかし、これでは面倒なので、文字列に限って次のような形式で初期化することも許されていま す。 char s セて 2 [ 6 ] " HE 0 " また、配列の要素の数を指定しないで、初期化で指定された要素にの場合は、 char 型 ) の数だけ メモリを確保することもできます。 char s セて 3 [ ] char s セて 4 [ ] " HE 0 " ー YO リ : 上記 4 つの str 配列の定義 ( strl ~ str4 ) は結果的にまったく同じデータに初期化されます。では、 次の 2 つの場合はどうなるでしよう。 char str5 [ 6 ] char str6[ 6 ] " HE " この 2 つの場合 ( str5 、 str6) は、先頭の 2 文字は「 HE 」となり、残りの 4 文字は「 0 」 ( 判 NULL 文字 ) に初期化されます。ただし、配列の大きさを省略して、 char str7 [ ] ます。試しに、次のプログラムを実行してみましよう。 ないことに注意してください。 NULL 文字がないと、文字配列の終わりがわからなくなってしまい とすると、この配列 (str7) は 2 バイトの大きさをもち、響ど E で初期化されますが、 NULL 文字は リスト 7.6 NULL 文字のない文字配列 int main ( void ) #include く stdio . h> char str[] printf ( "%sYn" return 0 ー str ) : 254
for 文・ fprintf ・ fputc fputs FreeBSD fscanf ・ 143 , 147 , 151 , 154 , 213 , 252 , 263 ・・ 339 ・・ 343 ・・ 348 ・ 329 , 335 , 344 long LONG MAX LONG MIN ・ 64 , 84 , 105 ・・ 66 ・・ 66 ・・ 26 ・・ 174 ・・ 225 ・・ 301 ・・ 249 main 関数 malloc memcmp MFC getchar ・ gets gets 関数 GNU ・ goto 文 0t0 文論争 GUI ・ 139 , 141 ・・ 262 ・・ 261 ・・ 18 ・ 152 , 153 , 154 , 155 , 156 , 159 , 347 ・・ 159 NS チャート NTFS ファイルシステム・ NULL ・ NULL ポインタ NULL 文字 ・・ 60 ・・ 337 ・・ 328 ・・ 272 ・ 249 , 250 , 252 , 254 , 262 , 265 HCP チャート・ ・・ 60 0 OR 論理演算子 ・ 107 , 109 if-else 文・ if 文 if 文のネスト int int 型 ・・ 116 ・ 51 , 56 , 103 , 112 , 124 ・・ 126 ・ 64 , 67 , 73 , 84 ・・ 106 Pascal ・ PDP-II POW printf printf("c=%dYn",c) ; putchar ・ ・・ 193 ・ 127 , 138 , 162 ・・ 27 ・・ 43 ・ 139 , 286 JIS C ・ JIS 規格・ ・・ 67 ・・ 360 ・・ 360 ・ 189 , 200 ・・ 197 ・・ 27 ・ 128 , 164 , 174 , 187 ・・ 308 ワ〕ワ」 1 ・ 1 人 LDBL DIG LDBL MAX LDBL MIN Lint1X 10g 10g10 regi Ster ・ register 変数 return return 文 RO M 379
1 0 章 ファイ丿レ fclose ( fp ) : return EX 工 T_SUCCESS ー もしそのファイルが存在しな 工ラー発生時の対処 力する場合も同じです。 なかった場合には、そのファイルが自動的に作成されます。これは、ファイルにデータを新規に出 かったらどうなるでしようか。ファイルを追加出力する場合に、オープンしたいファイルが存在し ところで、ファイルを追加モードでオープンしようとしたときに 多いので、エラー対策は重要です。 それを未然に防ぐための手だてを用意するべきです。特に、ファイル入出力の場合にはトラブルが 工ラー処理が必要になります。また、エラーが発生する条件がわかっている場合には、可能な限り、 実際にファイル人出力を行う際には、その処理の過程で不都合が発生したときの対処、すなわち if ( cnt > 0 ) 10.8.1 たとえば、リスト 10.1 の場合、平均値を求める際に、 操作を行う場合にも必要となります。リスト 10.2 では、ファイルをオープンするときに ます ( 0 による除算を未然に防ぐ役割を果たしています ) 。このような事前のチェックは、ファイル として、入力データ総数を示す cnt の値が 0 のときには、 sum/cnt の計算を行わないようにしてい if ( ( fp = fopen ( "indata . dat" NULL ) という条件文を記述しています。これは、目的のファイルが存在しなかったときに、後の処理を実 行しないようにするためです。この条件文がないと fp の値は NULL となり、以後のファイル操作関 数にストリームボインタとして NULL が渡されることになります。これは、大変危険なことで、プ 342
7.2 文字列の操作 リスト 7.11 電報の料金計算 #include く stdio . h> int str—length ( char [ ] ) ー 土 n セ main ( void ) char telegram[ 100 ] : unsigned int len; unsigned int charge ー printf ( ”電文を入力してください > > > " gets ( telegram ) ー 1en s セて length ( telegram ) : charge = 5 ☆ len; / * s セて一 leng セ h 関数のプロトタイプ宣言☆ / / ☆電文格納工リア / ☆電文の長さ / ☆電文の料金 / ☆電文の入力 printf ( " あなたの電文の長さは宅 d 文字です。 Yn" printf ( " 料金は宅 d 円です。 Yn" charge ) : return 0 : 1en ) : gets 関数は、標準人力から 1 行分マ 26 を入力し、文字配列 ( この場合は、 telegram) にセットする関 数ですマ 27 。 1 行といっても、その行の最後の心ま 0 に置き換わります。 このプログラムの実行結果は、次のようになります。 実行結果 ( リスト 7.11 ) クリスマスメッセージの料金 電文を入力してください > > > A Merry Christmas to you! あなたの電文の長さは 25 文字です。 料金は 125 円です。 文字判出現するまで、その文字列の長さを延々と数え続けます一 まちがえて文字列データから NULL 文字をなくしてしまうと、 str_length 関数はメモリ上に NULL は必ず NULL 文字響 0 て終わるという規則を思い出してください。もし、文字列操作をした結果、 て - しえられた文字列 ( 配列 ) に代人操作をしないので、メモリ破壊は起きません。ところで、文字列 ここでも、文字列についての注意点をあげておきましよう。 str_length 関数は、パラメータとし マ 26 マ 27 マ 28 すなわち、改行が押されるまでの入力文字列のことです。 gets 関数のプロトタイプ宣言は次の形式をしています。 char *gets ( char *s ) : この関数は、読み込んだ文字列へのポインタ ( 8 章参照 ) s を返しますが、エラーが発生したりファイルが終了したときに は NULL'E インタ ( 8.1.2 項参照 ) を返します。 その結果、場合によってはアクセスしていはいけないメモリ上の領域へアクセスしてしまうこともあります。 261
EOF とは異なりますので戻り値に注意してください。 リスト 10.4 のプログラムを fgets 関数に置き換えたプログラムを紹介しましよう。 #include く stdlib . h> #include く stdio . h> リスト 10.7 fge 熔関数を使用した例 10.8 のいずれかの条件を満たすまで文字列を読み込みます。結果は string に格納され、 NULL 文字 ”合計 = %d ( 判りが追加されます。ファイルの終端に達すると、 fgets 関数は NULL を返します。 fscanf 関数の 工ラー発生時の対処 #define STRING_SIZE 200 int main ( VOid ) int 土 n セ int char char F 工 LE va1 ー sum = 0 ー cnt Fi1eName [ 14 ] : *fp; string [ STRING_S 工 ZE ] ′・ printf( ”入力ファイル名 > > > " ) : scanf( ” s " ′ Fi1eName ) ′ if ( ( fp = fopen ( Fi1eName, printf ( ”ファイルが見つかりません。 exit ( EXIT_FA 工 LURE ) ー cnt = 0 ー while ( fgets (string, sscanf ( string, sum 十 = val; printf ( if ( cnt > 0 cnt 十十一 fclose ( fp ) : return EX 工 T_SUCCESS : STRING S 工 ZE ′ ー %gYn" &val ) : 平均値ー / ☆入力ファイルのオープン☆ / NULL ) (p) ) / ☆データの入力 %sYn" ′ Fi1eName sum, (double) sum/cnt ) ー / ☆合計・平均値の表示 / ☆ファイルのクローズ ☆ / 345
7.1 実行結果 ( リスト 7.6 ) HEk 薛☆ 文字列の表現ー つまり、先頭 2 文字は確かに " HE " ですが 3 文字目以降は不定です。ですから、 NULL 文字が発見 されるまで、配列の先頭からメモリの内容を表示し続けてしまっているのですⅵ 2 。 NULL 文字のな い文字配列をあえて作ることもないでしようから、必ず NULL 文字は必要なものであると考えてく ださい。初期化の「 = { … } 」は、定義部のみで使用できるのであり、代人文としての使用は許され ていないことを覚えておきましよう。たとえば、次のようなプログラムは文法工ラーになってしま いますⅵ 3 char s tr [ 10 ] : str また、配列はポインタとも密接な関係がありますが、初心者のうちは配列とポインタは異なるも のだと考えてください。配列とポインタの関係は、応用編で詳しく説明します。 . 落し穴 どうして、 char str [ ] とは書けるのに 7.3 " He110 文字列の代入 WO て ld ー " ・ char s セて [ 14 ] ー のです。 と書くとコンバイルエラーになってしまうのでしようか。これは変数の初期化と代入の違いによるも str " He110 ′ wo て 1d " charstr[] ="HeIlo, world ! " ; の場合は、 str 変数に値を代入しているのではなく st 「変数を初期化してい ですから、 str = "HeIIo, world"; は、定数に値を代入しようとしてエラーとなってしまうのです。 配列名 str は式の中で配列の格納されているメモリの先頭アドレスを示す定数として扱われます。 るのだけなので、問題はありません。 マ 13 たとえば、 UNIX では実行時工ラーとなる場合もあります。 以前にも脚注で示しましたが、 char *p : "abc" P という表現は可能です。この仕組みはポインタを学習すると理解することができます。 255
str—cat ( printf ( str—cat ( printf ( return 0 : stmnt ′ stmnt, " とランニング " ) : stmnt ) : セ a 土 1 ) : stmnt ) : / ☆最後の文字を追加 リスト 7.13 を実行すると、次のようになります。 実行結果 ( リスト 7.13 ) 私の好きなスポーツは 私の好きなスポーツは水泳 ・落し穴 . 私の好きなスポーツは水泳とランニングです。 私の好きなスホーツは水泳とランニング 文字配列で、 char と、 char ス 5 str [ 6 ] str [ 6 ] 配列の初期化の危ない話 " HE 0 " ・ は同じ初期化を行うといいました。 では、次の初期化はどうなるでしよう。 char char 7.2 文字列の操作ー ー YO リ : ー YO : strl[ 5 ] str2[ 5 ] "HELLO" strl には、 5 つしか配列の要素を割り当てていないにもかかわらず、 6 つの初期化を与えているの でコンパイルエラーになります。しかし、 str2 の初期化には、コンパイラは文句をいいません。これ は、文字定数で初期化を行う場合と、文字列定数で初期化を行う場合とでは異なった文法規則がある ことを示しています。文字列定数による初期化は、 1 . 文字列定数で NULL 文字が出現するまで 2. 文字配列を埋め尽くしたとき に終了するという規則があるのです。この場合、 str2 配列には ( 2 ) の規則が適用されるので、文字列 定数 " HELLO " の先頭 5 文字で埋められてしまいます。その結果、文字列の終了を示す NULL 文字のな い配列ができてしまうのです。ですから、文字列定数の最後には、目には見えないけれども NULL 文 字があることを忘れることのないように、初期化で格納すべき配列の大きさに注意しましよう。 265
1 0 章ファイル 複数のファイルから入力するには 複数のファイルからデータを人力するには、ストリームボインタを必要な個数分だけ宣言します。 *fpl, *fp2, *fp3; を工石 E それぞれに対して、 fopen を行います , 13 そして、 fopen ( "testl . dat" fp2 fopen ( "test2 . dat" fp 3 fopen ( "test3. dat" さらに、それぞれのファイルに対して、データの読み込みを行います。ファイルをオープンしよ うとしたときにエラーが生じると、 f 叩 en 関数はそのエラーに対応したエラーコードを返します。 ファイルを人出力するプログラムを作成しようとするときには、エラーが起こった場合の処理も考 慮する必要があります。これは、 fopen 関数に限らずファイルを計むときや書くときにも常に考慮 しなければ 0 、けな 0 、ことです。なお、 , イ ~ を読み込もうとしときの , ラーに対処する方法に ついては、 10.8 節で説明します。 ところで、ファイルはいくつまで同時に開くことができるのでしようか。ファイル名の最大の長 さと同様に、 C ではく stdio. h > に同時に開くことのできる個数をく stdio. h > のマクロ FOPEN MAX で 定義しています。 今までは、ファイルからデータを人力する方法を学んできましたが、今度は逆に、ファイルに データを書く方法について勉強しましよう。ファイルヘデータを出力するには、次の作業が必要で ファイルへの出力 ( 1 ) ファイルを読む場合と同じように、ファイルをオープンする *fp; F 工 LE fp = fopen ( "indata . dat" もちろん、 f 叩 en の後 fpl 、 fp2 、 fp3 の各値が NULL でないことをチェックする必要がありますが、 マ 13 こでは省略します。 実際には、 fp の値が NULL でないことをチェックする必要があります。 ( a ) 書き込み権限がない場合、 ( b ) ファイル名が マ 14 ファイル命名規則に違反している場合 ( co ebreak 10.3 を参照 ) 、 ( c ) ディスク容量が不足している場合は、ファイルの オープンに失敗して、 fp の値が NULL になります。 338