• ベストアンサー

C言語、ファイル操作、fgets()について

次のプログラムは入力された行を読み込み、コマンドラインで指定されたファイルに書き込みます。 空白行が入力されたら、入力の終了とみなしてファイルを閉じます。続いてファイルを入力用に開き、 fgets()を使ってファイルの内容を表示するものです。 (ソースコードが長くてすみません) #include <stdio.h> #include <stdlib.h> #include <string.h> int main(int argc, char *argv[]) { FILE *fp; char str[80]; /* コマンドライン引数を検査する */ if(argc!=2) { printf("ファイル名を指定してください\n"); exit(1); } /* 出力用にファイルを開く */ if((fp = fopen(argv[1], "w"))==NULL) { printf("ファイルを開くことができません\n"); exit(1); } printf("終了するには空白行を入力してください\n"); do { printf(": "); gets(str); strcat(str, "\n"); /* 改行を追加する */ if(*str != '\n') fputs(str, fp); } while(*str != '\n'); fclose(fp); /* 入力用にファイルを開く */ if((fp = fopen(argv[1], "r"))==NULL) { printf("ファイルを開くことができません\n"); exit(1); } /* ファイルを読み込み直す */ do { fgets(str, 79, fp); if(!feof(fp)) printf(str); } while(!feof(fp)); fclose(fp); return 0; } 【質問】fgets()内のint型の数値「79」がどうして79なのかが分かりません。     80でも良いような気がするのですが・・・

質問者が選んだベストアンサー

  • ベストアンサー
  • php504
  • ベストアンサー率42% (926/2160)
回答No.1

char str[80]; で文字列用の配列を80バイト分確保していますが文字列の終端には'\0'が必要です。 そのため実際に使える文字列の長さは79になります。 それを意識して fgets(str, 79, fp); としたのだと思いますがfgetsの引数は'\0'を含んだ数なので fgets(str, 80, fp); で問題ないでしょう。

Guchiken
質問者

お礼

さっそくの回答ありがとうございます! fgets(str, 80, fp); にしても問題なく実行できたのでおかしいな?と思って質問しました。 私が学習している本に、 『char *fgets(char *文字列, int 数値, FILE *ストリーム);  fgets()関数は、「ストリーム」に結び付けられているファイルから文字を読み込み、  「文字列」が指す文字列に格納していきます。この操作は「数値」マイナス1個分  の文字を読み込むか、改行文字に出会うか、あるいはファイルの終わりに達するまで  続けられます。いずれの場合も、格納された文字列の後にヌル文字が追加されます。  類似のgets()と違って、改行文字も格納されます。』 とあります。 また質問で恐縮なんですが、「数値」は何のためにあるのでしょうか? あと、改行文字が格納されることと数値が79なのは無関係ですか? 未熟者の私ですが、どうか教えてください。お願いします。

その他の回答 (4)

  • chie65536
  • ベストアンサー率41% (2512/6032)
回答No.5

>ご指摘のとおりに実行してみましたが、エラーが出ません。 strの中に「%s」って文字列が入っていた時に printf(str); を実行するのは printf("%s"); を実行するのと同じです。 printf("%s"); って書いた時に「何かが足りないような?」って思いませんか? では、strの中に「%3.3d」って文字列が入ってる時は? printf(str); を実行するのは printf("%3.3d"); を実行するのと同じです。 こっちも「何かが足りない」って感じませんか? そうです。「書式文字列に、2個目以降の引数の型を指定してるのに、2個目以降の引数が無い」ですね。 printfは「ある筈の2個目以降の引数が無いと、不定な値が渡され、何が起きるか判らない」のです。 >正:aaa(null)\naaaa\n エラーは起きてます。 起きてますが「引数が足りずに不定な値が渡されたが、偶然に0が渡され、ヌルポインタとして判断されて『(null)』が代わりに表示され、致命的なエラーを免れた」だけです。 実はVisual StudioのCコンパイラのライブラリは「printfの書式文字列の変換ルーチンで、ポインタ(文字列のアドレス)を受け取った時、ポインタがヌルだったら『(null)』と表示し、致命的例外を出さないよう、対策が施してある」のです。 今回は「偶然に」ヌル対策で助かりましたが、いつもそうとは限りません。次の実行時、メモリの中にゼロじゃない何かの残骸が残ってたら「ヌルにならず、例外処理されて、赤バッテンのダイアログが出て強制終了」になるかも知れません。 >それでもやっぱり、printf("%s", str); としたほうが良いですか? もちろん。strの中身に何が来るか判らないので。 それに「printfの第1引数は、出力したい文字列ではなく、書式文字列を指定する」ので「文字列を出力したいなら、書式文字列に%sを指定し、引き続く引数に文字列を指定する」のが「本当の使い方」です。 それとANo.3の方が「わざわざ効率の悪いprintf」と言っていますが、書式文字列を元に最終的な出力文字列に変換する際の処理や、前述のような「ヌルポインタで例外を出さない対策」などが「効率が悪い」原因になっています。 最適な解はANo.3、4のように「fputs(str,stdout)」を使う事です。

Guchiken
質問者

お礼

質問に答えてくださってありがとうございます!! 大変勉強になりました! これからはprintf(str); のような書き方に気をつけていこうと思います。 丁寧に回答してくださってありがとうございました。

  • jacta
  • ベストアンサー率26% (845/3158)
回答No.4

> fputs()関数はファイルに書き込むための関数だから、表示できないのでは? ファイルというよりはストリームに書き込むための関数ですね。 printfもストリームに書き込むための関数です。そして、printfを使ったとしても、「表示」できるかどうかは環境に依存します。 ちなみに、printfの出力先はstdoutです。ですから、fputsでstdoutを指定すれば、printfと出力先が同じになります。

Guchiken
質問者

お礼

回答ありがとうございます! fputs(str, fp); に勝手に変えてました。ごめんなさい! >ちなみに、printfの出力先はstdoutです。ですから… この文章を読んでからfputs(str, stdout)にしたら表示できました! 大変勉強になりました。ありがとうございました。

  • jacta
  • ベストアンサー率26% (845/3158)
回答No.3

> printf("%s", str); としたほうが良いですか? わざわざ効率の悪いprintfを使わなくても、fputs(str, stdout); とすればよいのでは? 書式文字列に絡むバグも fputs なら起こりようがないですし。

Guchiken
質問者

お礼

回答ありがとうございます! fputs(str, stdout); ですか・・・ 推測でしかないんですけど、 fputs()関数はファイルに書き込むための関数だから、表示できないのでは?

  • chie65536
  • ベストアンサー率41% (2512/6032)
回答No.2

fgetsは「自動的に付けられる文字列の終端記号も含んだ、バッファの大きさ」を指定するので80で良い筈ですが、たま~に「終端記号を含まない、読み込む最大長」と勘違いしている人がいます。 それよりも問題なのは  if(!feof(fp)) printf(str); の行のprintfです。これは  if(!feof(fp)) printf("%s",str); と書かないと、思わぬバグを引き起こします。 試しに、1行入力の入力時に「aaa%s\naaaa\n」と入力してみましょう。unix環境ではcore dumpしますし、Windows環境だと例外処理で強制終了したり、Abnormal program terminationの表示が出て停止します。 例えば、キーボードで文字チャットが出来るMMOタイプの某オンラインゲームは、チャット文字中に「%s」ってのを入れると、ゲーム本体が例外を出して停止しますが「とても恥かしいバグを出した」って事で有名になりました。

Guchiken
質問者

お礼

回答ありがとうございます! ご指摘のとおりに実行してみましたが、エラーが出ません。 aaa \naaaa\n と表示されるだけです。 ちなみに、「Visual Studio .NET 2003 コマンドプロンプト」で実行しました。 それでもやっぱり、printf("%s", str); としたほうが良いですか?

Guchiken
質問者

補足

お礼の訂正: 誤:aaa \naaaa\n 正:aaa(null)\naaaa\n

関連するQ&A