🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

ファイル

ファイルとは、文字列に基づいた名前又はパスからアクセスすることができる、任意の情報のブロック又は情報を格納するためのリソースです。

ファイルI/O

ファイルI/Oは、コンピューターにおけるファイルの入出力です。これは生成/削除やファイルを読み込んだり、出力をファイルに書き込むようなディレクトリやファイルの運用を含みます。

Q&A

解決済

3回答

1880閲覧

こファイルの内容をコピーするプログラムで、コピー先のファイルに、余計な改行が1つ余分に記述されてしまう。

Eisaku_Yoshida

総合スコア11

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

ファイル

ファイルとは、文字列に基づいた名前又はパスからアクセスすることができる、任意の情報のブロック又は情報を格納するためのリソースです。

ファイルI/O

ファイルI/Oは、コンピューターにおけるファイルの入出力です。これは生成/削除やファイルを読み込んだり、出力をファイルに書き込むようなディレクトリやファイルの運用を含みます。

0グッド

0クリップ

投稿2021/01/14 01:59

編集2021/01/14 04:16

いつもお世話になっております。

ファイルの内容をコピーするプログラムを作成しています。
しかし、現在、コピー先のファイルに余計な改行が1つ追加されて出力されてしまっています。

疑問点①(terminal上に記載)
変数tmpLineに入っているのが、
\n000d\n→\nCRLFなのは、分かりますが、
どうして、急にCRLFが変数の中に入ってきたのかが分かりません。
というのも、これまでは、普通に、変数に入っていたのは、\n(例えば、"dddd\n")であったからです。
さらに、\nCRLFが入ってしまっているということは、2行分改行されてしまうと思います。(\nで1行分改行されて、CRLFで1行分改行される)
しかし、そんなプログラムは組んでいないと思っているので、原理が分かりません。

疑問点②(terminal上に記載)
おそらく、2回ループが回って、\nCRLFが読み込まれて、ファイルに記述されてしまっているので、
それが原因だとは思うのですが、どうして、もう一度、同じ\nCRLFが読み込まれ、記述されてしまっているのかが分かりません。

ご教授いただけませんでしょうか。
よろしくお願いいたします。

●環境
macOS Big Sur
Ver.11.1

C

1#include <stdio.h> 2#include <stdlib.h> 3#include <string.h> 4 5#define ARGUMENT_NUM 3 6#define SIZE 254 7 8int main(int argc, char **argv) 9{ 10 FILE *fromFp = NULL; 11 FILE *toFp = NULL; 12 char fromFile[SIZE]; // コピー元ファイル 13 char toFile[SIZE]; // コピー先ファイル 14 char tmpLine[SIZE]; // 読み取った文字列を格納するための変数 15 int iRet = 0; 16 char *cptr = NULL; 17 18 memset(fromFile, 0x00, sizeof(fromFile)); 19 memset(toFile, 0x00, sizeof(toFile)); 20 memset(tmpLine, 0x00, sizeof(tmpLine)); 21 22 if (argc != ARGUMENT_NUM){ 23 printf("コマンドライン引数エラー\n"); 24 return -1; 25 } 26 memcpy(fromFile, argv[1], sizeof(fromFile)); 27 memcpy(toFile, argv[2], sizeof(toFile)); 28 29 fromFp = fopen(fromFile, "r"); 30 if (fromFp == NULL){ 31 printf("fopen失敗!\n"); 32 return -1; 33 } 34 35 toFp = fopen(toFile, "w"); 36 if (toFp == NULL){ 37 printf("fopen失敗!\n"); 38 return -1; 39 } 40 41 while(!feof(fromFp)){ 42 cptr = fgets(tmpLine, sizeof(tmpLine) / sizeof(char), fromFp); 43 if (!cptr && !feof(fromFp)){ 44 printf("読み込みエラー\n"); 45 goto FILE_CLOSE; 46 } 47 iRet = fputs(tmpLine, toFp); 48 if (iRet == EOF){ 49 printf("書き込み失敗\n"); 50 goto FILE_CLOSE; 51 } 52 53 } 54 55 FILE_CLOSE: 56 iRet = fclose(fromFp); 57 if (iRet != 0){ 58 printf("fclose失敗【読み取り元ファイル】"); 59 return -1; 60 } 61 iRet = fclose(toFp); 62 if (iRet != 0){ 63 printf("fclose失敗【読み取り元ファイル】"); 64 return -1; 65 } 66 67 68}

terminal

1ユーザー名@コンピュータ名 第2回 % ls 2a.out test.txt 問題10.txt 問題8 3a.out.dSYM test2.txt 問題5.c 問題8.c 4base.h test3.txt 問題6 問題9.c 5dump.txt 問題10.c 問題7 書き込み用ファイル.txt 6ユーザー名@コンピュータ名 第2回 % cat test.txt 7aaa 8bbb 9ccc 10ddd 11 12ユーザー名@コンピュータ名 第2回 % ./a.out test.txt test3.txt 13ユーザー名@コンピュータ名 第2回 % cat test3.txt 14aaa 15bbb 16ccc 17ddd 18 19 // 改行が1つ多くコピーされてしまっている。 20ユーザ名@コンピュータ名 第2回 % 21

gdb(terminal)

1Thread 2 hit Breakpoint 2, main (argc=3, argv=0x7ffeefbff6f8) at 問題8.c:37 237 while(!feof(fromFp)){ 3(gdb) n 438 cptr = fgets(tmpLine, sizeof(tmpLine) / sizeof(char), fromFp); 5(gdb) n 639 if (!cptr && !feof(fromFp)){ 7(gdb) print tmpLine 8$1 = "aaa\n", '\000' <repeats 249 times> 9(gdb) n 1043 iRet = fputs(tmpLine, toFp); 11(gdb) n 1244 if (iRet == EOF){ 13(gdb) print tmpLine 14$2 = "aaa\n", '\000' <repeats 249 times> 15(gdb) print iRet 16$3 = 4 17(gdb) n 1837 while(!feof(fromFp)){ 19(gdb) n 2038 cptr = fgets(tmpLine, sizeof(tmpLine) / sizeof(char), fromFp); 21(gdb) n 2239 if (!cptr && !feof(fromFp)){ 23(gdb) print tmpLine 24$4 = "bbb\n", '\000' <repeats 249 times> 25(gdb) n 2643 iRet = fputs(tmpLine, toFp); 27(gdb) n 2844 if (iRet == EOF){ 29(gdb) print tmpLine 30$5 = "bbb\n", '\000' <repeats 249 times> 31(gdb) n 3237 while(!feof(fromFp)){ 33(gdb) n 3438 cptr = fgets(tmpLine, sizeof(tmpLine) / sizeof(char), fromFp); 35(gdb) n 3639 if (!cptr && !feof(fromFp)){ 37(gdb) print tmpLine 38$6 = "ccc\n", '\000' <repeats 249 times> 39(gdb) n 4043 iRet = fputs(tmpLine, toFp); 41(gdb) n 4244 if (iRet == EOF){ 43(gdb) print tmpLine 44$7 = "ccc\n", '\000' <repeats 249 times> 45(gdb) n 4637 while(!feof(fromFp)){ 47(gdb) n 4838 cptr = fgets(tmpLine, sizeof(tmpLine) / sizeof(char), fromFp); 49(gdb) n 5039 if (!cptr && !feof(fromFp)){ 51(gdb) print tmpLine 52$8 = "ddd\n", '\000' <repeats 249 times> 53(gdb) n 5443 iRet = fputs(tmpLine, toFp); 55(gdb) n 5644 if (iRet == EOF){ 57(gdb) print tmpLine 58$9 = "ddd\n", '\000' <repeats 249 times> 59(gdb) n 6037 while(!feof(fromFp)){ 61(gdb) n 6238 cptr = fgets(tmpLine, sizeof(tmpLine) / sizeof(char), fromFp); 63(gdb) n 6439 if (!cptr && !feof(fromFp)){ 65(gdb) print tmpLine 66$10 = "\n\000d\n", '\000' <repeats 249 times> // \n\000d\n \nCRLF(疑問点①) 67(gdb) n 6843 iRet = fputs(tmpLine, toFp); 69(gdb) n 7044 if (iRet == EOF){ 71-----------------------疑問点②------------------------------------------------- 72(gdb) print tmpLine 73$11 = "\n\000d\n", '\000' <repeats 249 times> // 一旦、\n\000d\n (\nCRLF)がterminal上に出力される。 74(gdb) n 7537 while(!feof(fromFp)){ 76(gdb) n 7738 cptr = fgets(tmpLine, sizeof(tmpLine) / sizeof(char), fromFp); 78(gdb) n 7939 if (!cptr && !feof(fromFp)){ 80(gdb) print tmpLine 81$12 = "\n\000d\n", '\000' <repeats 249 times> // もう一度、\n\000d\n (\nCRLF)が読み込まれて 82(gdb) n 8343 iRet = fputs(tmpLine, toFp); // 再度、\n\000d\n \nCRLFが出力されてしまっている。(→余計な改行が1行多く表示されてしまっている原因?) 84(gdb) n 8544 if (iRet == EOF){ 86(gdb) print tmpLine 87$13 = "\n\000d\n", '\000' <repeats 249 times> 88--------------------------------------------------------------------------------- 89(gdb) n 9037 while(!feof(fromFp)){ 91(gdb) n 9252 iRet = fclose(fromFp); 93(gdb) n 9453 if (iRet != 0){ 95(gdb) n 9657 iRet = fclose(toFp); 97(gdb) n 9858 if (iRet != 0){ 99(gdb) n 10064 } 101(gdb) 102

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tatsu99

2021/01/14 03:20

提示されたソースをコンパイルしてみました。 SIZEが未定義でエラーになります。 1.SIZEはどこで定義していますか? 2.環境はLinuxでしょうか。Linuxであれば改行コードはLF(0x0A)のみです。 意図的にCRLFの改行を使用されているのでしょうか。 それとも環境はWindowsでしょうか。
Eisaku_Yoshida

2021/01/14 04:12 編集

tatsu99様、 申し訳ございません! 1. 元々、自作のヘッダーファイル内で定義しておりました。 しかし、今回、コードを載せるにあたり、ヘッダーファイルを省略しました。 ヘッダーファイルを追記するのも、冗長になってしまうので、提示したコードの記述を修正して SIZEの値を記載いたします。 2. 環境を記載し忘れておりました!MacOS Big Sur ver.11.1です! 意図的にCRLFを使用しているわけではありません。勝手に?変数の中に入っておりました。(MacOSなので改行コードにCRLFが使われているのは合点がいくのかもしれませんが。)
guest

回答3

0

ベストアンサー

これ、わかりづらいですよねぇ。改行コード云々ではなく、ループ終了判定を誤っているため、最後のバッファが 2回表示されているものです。

fgets や fgetc して NULL が帰ってきたときに、feof を使ってファイル終端に到達したのか、エラーなのかを判定するわけです。ご提示のソースは、fgets で NULL が帰ってきた後も fputs しており、tmpLine は前回のデータがそのまま残っているので、最後の行が 2回表示されているものです。

よって、正しくは fgets して戻り値が NULL なら feof 判定して EOF なら (fputs せず) ループを抜ける、です。

なお、fgets が NULL を返した際に tmpLine がどうなるかは不定か未定義だったはず (調べてません) なので、前回ループの文字列が残っているのはたまたまです。

投稿2021/01/14 03:27

68user

総合スコア2022

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

68user

2021/01/14 03:45

補足しておくと、最終行を fgets した段階では fgets や feof は EOF に到達したことを知らない、ということです。もう一度 fgets して読むべきものがなかったとなったときに fgets は NULL を返すし、feof は EOF であると判断します。
Eisaku_Yoshida

2021/01/14 05:11

68user様、本当にありがとうございます! 実は、fgets関数がどうやってEOFに達したことを知るのだろうかと、ご指摘を受けてから考えていました。 しかしながら、68user様の上記のコメントのおかげで、どうやってfgets関数がEOFに達したのかを判定するのかがわかりました。(読み取るものがなかった場合に、NULLを返すのですね。)
guest

0

回答は、68userさんの通りだと思います。

そのほかに、

memcpy(fromFile, argv[1], sizeof(fromFile));

argv[1]文字列の終端のNUL文字を無視してSIZEバイトアクセスしています。
254と小さい数ということもあり、たまたまうごいていますが、範囲外アクセスなので、実行時エラーになる恐れがあります。
strncpyを使いましょう。

fgets(tmpLine, sizeof(tmpLine) / sizeof(char), fromFp)

sizeof(tmpLine) / sizeof(char)は配列の要素数ですが、引数で指定すべきなのは要素数じゃなくてバイト長なので、sizeof(tmpLine)が正しいです。fread等との混同でしょうね。

while(!feof(fromFp)){

普通のプログラムでfeofを使うことはまず無いです。
このために、指摘のあるとおり、バグが発生しています。

while(fgets(tmpLine, sizeof(tmpLine), fromFp)){が良いです。

投稿2021/01/14 07:21

otn

総合スコア85890

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Eisaku_Yoshida

2021/01/21 02:50

otn様、ソースコードの他の問題点まで、指摘して下さりまして、 本当にありがとうございます!
guest

0

iRet = fputs(tmpLine, toFp);

fputsってのは、改行を後尾につけて出力します

投稿2021/01/14 02:01

y_waiwai

総合スコア88038

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

otn

2021/01/14 02:42 編集

> fputsってのは、 putsと混同していますね。fputsでは付きません。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問