「C言語/ファイル入出力」の版間の差分

削除された内容 追加された内容
Ef3 (トーク | 投稿記録)
{{コラム|Visual Studio で、デバッグ セッションの終了時にコンソールが閉じてしまう場合| Visual Studio で、デバッグ セッションの終了時にコンソールが閉じてしまう場合は、 : [ツール] -> [オプション] -> [デバッグ] -> [デバッグの停止時に自動的にコンソールを閉じる] を無効にします。 }}
タグ: 2017年版ソースエディター
Ef3 (トーク | 投稿記録)
→‎応用例: fclose(NULL)を実行すると実行時エラーになるのは自明。
タグ: 2017年版ソースエディター
820 行
 
いっぽう、<code>strncpy(row[temp].str, strtok(NULL, ","), 150);</code> のようにstrtokの第一引数がnullなら、strtokは以前の区切り文字から次の区切り文字までを抜き取るだけなので、2番目の項目からはfor文で使いまわしができる。
 
== 応用例 ==
=== 読み取りでオープンできなかったファイルは、クローズしてはいけない ===
Windows(Visual Studio)でもLinux(gcc)でも、
 
<code>fp1 = fopen("test1.txt", "r");</code> のような関数で読み込みに失敗した場合、つまり、普通なら読み込み対象のファイルが無い場合は、
 
そもそも、関数<code>fp1 = fopen("test1.txt", "r");</code>の実行直後は、
 
まだファイル"test1.txt"は、まったくオープンされて無い状態です。
 
なので、もしも、読み込み対象の失敗時のときの操作を、If文などの記述ブロックのなかに <code> fclose(fp1);</code> を記述しても、オープンしてないファイルをクローズしようとしており、そのため、エラーになります。
 
==== Windowsの場合 ====
いくつか前に示したコード、
 
;コード例(まだ改造してない)
<syntaxhighlight lang="C">
#include "stdafx.h"
#include <stdio.h>
 
#pragma warning(disable:4996)
 
int main()
{
FILE *fp1 = fopen("test1.txt", "r"); // ここを読み取りモード"r"にするのを忘れないように
if (fp1 == NULL) {
perror("ファイルを開けませんでした。\n");
return 1;
}
else {
printf("ファイルをオープンしました。\n");
}
char str1[150];
printf("文字列を読み取っています。\n");
fscanf(fp1, "%s", str1);
 
printf("ファイルに書いてある文字列\n");
printf("%s\n", str1);
 
fclose(fp1);
printf("ファイルをクローズしました。\n");
}
</syntaxhighlight>
 
 
は、次のようにも書き換えできる。
 
 
次のコードではelse文ブロックの中に、ファイルが存在していた時の処理をまとめている。
 
;改造したコード例 (Visual Studio 2019 および Windows 7 で確認ずみ)
<syntaxhighlight lang="C">
#include "stdafx.h"
#include <stdio.h>
 
#pragma warning(disable:4996)
 
int main()
{
FILE* fp1 = fopen("test1.txt", "r"); // ここを読み取りモード"r"にするのを忘れないように
if (fp1 == NULL) {
perror("ファイルを開けませんでした。\n");
//fclose(fp1); // ここでクローズするとエラー
return 1;
}
else {
printf("ファイルをオープンしました。\n");
 
char str1[150];
printf("文字列を読み取っています。\n");
 
fscanf(fp1, "%s", str1);
 
printf("ファイルに書いてある文字列\n");
printf("%s\n", str1);
 
fclose(fp1);
printf("ファイルをクローズしました。\n");
 
}
}
</syntaxhighlight>
 
------------
 
いっぽう、タイトルのとおり、下のようなコードがあると、エラーになります。
 
このエラーの起きるコードでは、if文の条件節にファイルポインタを使ったif文の中で、むりやりクローズしようとしています。
 
<syntaxhighlight lang="C">
fp1 = fopen("test1.txt", "r");
if (fp1 == NULL) {
perror("ファイルを開けませんでした。\n");
fclose(fp1); // コメントアウトしないとエラーになる
return 1;
}
</syntaxhighlight>
 
 
ついつい、ファイルを開けなかった場合などには、「このFILE構造体へのポインタは用済みだから、すぐファイルを閉じよう(×)」って発想で、むりやりブロック中でクローズしようと思いたくなります。
 
しかし、上記のように、読み取りに失敗しあたとに、むりやりクローズしようとしても、実行時にエラーになります。
 
これはどういうことかというと、ファイルのオープンに失敗した場合、そもそも、そのファイルはオープンされてないのでクローズの必要もないです。
 
そのもそも、fp1 が NULL なので if 文が通ったので、fclose(NULL)が実行時エラーを引き起こすことは自明。
 
==== Linuxの場合 ====
Linuxでも同様です。もしもファイルのオープンに失敗した場合、Linuxでも、そのファイルはオープンされてないのでクローズの必要もないです。
 
また、もしもオープンに失敗したファイルをクローズしようとした場合、Linuxはセグメンテーションホルトになります。
 
Linuxの場合、コンパイルできてしまうかもしれ無い場合もありますが、しかしビルドされた実行ファイルの実行時にセグメンテーションホルトになり、「コアダンプ」などのメッセージが表示されます。
 
たとえば、次のコードは、読み取り対象のファイル"SettingFile.txt"の無い場合には、実行時にエラーになります
<syntaxhighlight lang="C">
// Linuxでエラーになる例
#include <stdio.h>
 
// #pragma warning(disable:4996) // Linux なので不要。未知の pragma は無視されるのでコメント化は必要ない
 
int main()
{
FILE* fp1 = fopen("SettingFile.txt", "r"); // まず、読み込みモードで開く。
 
if (fp1 == NULL) { // 読み込みが失敗(NULL)の場合のブロック
perror("ファイルを開けませんでした。\n");
fclose(fp1);
}
else {
printf("ファイルを開けました。 \n");
fclose(fp1);
}
//fclose(fp1);
 
printf("ファイルを閉じました。 \n");
printf("終了しています。 \n");
}
</syntaxhighlight>
 
 
いっぽう、次のように改善して、読み込み失敗後のクローズ関数を除去すれば、セグメンテーションホルトにならず、実行できます。
 
(Fedora 31 で確認ずみ)
<syntaxhighlight lang="C">
// Linuxでエラーにならない例
#include <stdio.h>
 
// #pragma warning(disable:4996) // Linux なので不要だし、あるとエラーの原因になる
 
int main()
{
FILE* = fopen("SettingFile.txt", "r"); // まず、読み込みモードで開く。
 
if (fp1 == NULL) { // 読み込みが失敗(NULL)の場合のブロック
perror("ファイルを開けませんでした。\n");
}
else {
printf("ファイルを開けました。 \n");
fclose(fp1); // elseが実行される場合、このブロックの始めではファイルがオープン状態なので、すぐ閉じたいなら、ここで。
printf("ファイルを閉じました。 \n");
}
//fclose(fp1); // ここではない。
 
printf("終了しています。 \n");
}
</syntaxhighlight>
 
もし、読み取り対象のファイルの無い場合に、
 
上のコードの
// ここではないfcloseからです。そのような動作は、エラーになり、認められません。
 
 
同様に、elseブロック内でクローズした場合でも、さらに「ここではない」の場所でもう一度クローズしてしまうと、これもセグメンテーションホルトになります。
 
 
 
<!-- 「読み込みモードの対象ファイルが無い場合に、作成したい」と前提が意味不明。また最初の fopenで開いたストリームを始末していない。レースコンディションがある。など問題が多い。
 
=== 読み込もうとしたファイルが無い場合に、同名のファイルを作成したい場合 ===
 
C言語では、<code> fopen </code>で書き込みモードでオープンしようとしたとき、もしファイルがなければ、自動的にファイルを作成します。
 
 
しかし、読み込みモードでは、対象のファイルが無い場合には、実行形式は開こうとしたファイルを作成しません。
 
 
 
では、どのような場合に、読み込みモードの対象ファイルが無い場合に、作成したいと思うのでしょうか。
 
一例では、上書き可能なユーザーごとのカスタム設定ファイルなどが、もし無くなった場合に、標準設定の内容になっている設定ファイルを新規に自動作成すると、便利でしょう。
 
 
ともかく、もし読み込みモードで対象ファイルが無い場合に、同名のファイルを新規作成したい場合には、
 
Windowsなら、たとえば、次のようなコードで実装できます。
 
<syntaxhighlight lang="C">
FILE* fp1;
{
fp1 = fopen("SettingFile.txt", "r"); // まず、読み込みモードで開く。
 
if ( (fp1 = fopen("SettingFile.txt", "r") ) == NULL) { // 読み込みが失敗(NULL)の場合のブロック
fp1 = fopen("SettingFile.txt", "w"); // 同名のファイルをそのまま fopen で開いてもいい
{
// ここに、標準設定の書き込みの処理を書く。
}
}
fclose(fp1);
}
</syntaxhighlight>
 
ここで重要なのは、 <code> fopen </code> で開いた回数は、上記のコードでは2箇所あるのに(if文の条件の記述での <code> fopen </code> は除く)、実際に開かれるファイル名は1個(例の場合ならテキストファイル "SettingFile.txt" の1個だけ)であるという事です。
 
また、 <code> fopen </code> は2箇所あるのに、いっぽうで <code> fclose </code> は1箇所だけであるという事です。
 
 
この理由は、おそらく、読み込みに失敗した場合は、そもそも何もオープンしてないと判断なされるので、なので、失敗したぶんの <code> fopen </code> の1回ぶんは、カウントされないからです。
 
ただし、書き込みは通常、対象ファイルの有無に関係なく成功するので(対象ファイルの無い場合には自動作成するので)、なので、書き込みの関数のさいにファイルがオープンされるので、その1回ぶんのクローズは必要になります。
 
 
 
なので、上記のコードの直後に、もし <code> fgets </code> などの読み込み関数をしても、エラーになって、読み込みできないです。
 
上記のコードのあとに読み込みをしたい場合、再度 <code> fopen </code> の読み込みモードで開いてからでないと、 <code> fgets </code> などの読み込み関数は実行できないのです。
 
 
 
 
 
Linux の場合で、ほぼ同様です。実際、次のソースコードを実行すると、わかります。
 
;コード (Fedora 31 で確認ずみ)
<syntaxhighlight lang="C">
// Linux用
#include <stdio.h>
 
// #pragma warning(disable:4996) // Linux なので不要だし、あるとエラーの原因になる
 
int main()
{
FILE* fp1 = fopen("SettingFile.txt", "r"); // まず、読み込みモードで開く。
 
if (fp1 == NULL) { // 読み込みが失敗(NULL)の場合のブロック
fp1 = fopen("SettingFile.txt", "w"); // 同名のファイルをそのまま fopen で開いてもいい
 
<!--ここにも fp1 が NULLを でないかのテストが必要 -->
 
printf("ファイルが存在しないので作成しています。ファイル名: SettingFile.txt \n");
fprintf(fp1, "書き込みテスト \n");
}
else {
printf("ファイルはすでに存在しています。 \n");
}
fclose(fp1);
 
printf("ファイルを閉じました。 \n");
printf("終了しています。 \n");
}
</syntaxhighlight>
 
 
;実行例 (ファイルのない場合)
<pre>
ファイルが存在しないので作成しています。ファイル名: SettingFile.txt
ファイルを閉じました。
終了しています。
</pre>
 
 
;実行例 (ファイルのある場合)
<pre>
ファイルはすでに存在しています。
ファイルを閉じました。
終了しています。
</pre>
 
;SettingFile.txtの中身
<pre>
書き込みテスト
</pre>
 
のようになります。
 
 
* エラー例
いっぽう、もし <code>fclose(fp1);</code> を2回使っても、コンパイルは失敗してエラーになり、下記のように途中でエラーメッセージが表示されてしまい中断します。
<pre>
ファイルはすでに存在しています。
free(): double free detected in tcache 2
Aborted (コアダンプ)
</pre>
-->
 
=== メモのあるファイルの数値の読取 ===
 
たとえば
<pre>
途中までの計算結果: 41
</pre>
(ファイル名は totyuu.txt としよう。)
 
のような解説文「途中までの計算結果」のような文字列のあるテキストファイルから、数値の「41」だけを読取たい場合があるだろう。
 
 
このようなファイルの読取り方は、さきほど紹介した strtok や fgets などを応用するのが良い。
 
また、文字列型を整数型に変換するには <code> atoi </code> という関数を使います。
 
なお、浮動型の数に変換するには <code> atof </code> という関数になります。
 
 
とりあえず、コード例として、読み取ったファイルに数 1 を足すだけの簡単なプログラムを作ってみましょう。
 
 
;コード例 (Fedora30 で確認ずみ)
 
<syntaxhighlight lang="C">
#include <stdio.h>
#include <string.h>
 
int main()
{
FILE *fp1 = fopen("totyuu.txt", "r"); // ここを読み取りモード"r"にするのを忘れないように
 
if (fp1 == NULL) {
perror("ファイルを開けませんでした。\n");
return 1;
}
else {
printf("ファイルをオープンしました。\n");
}
char buffer1[150];
printf("文字列を読み取っています。\n");
fgets(buffer1,150,fp1);
 
printf("1行目の文字列\n");
printf("%s", buffer1);
 
 
char str1[150];
char str2[150];
 
strncpy(str1, strtok(buffer1,":") ,150); // "," ではなく":" に変わってるのに注意。
strncpy(str2, strtok(NULL,":") ,150);
 
printf("str1として読み取った文字列\n");
printf("%s", str1);
printf("\n");
 
printf("str2として読み取った文字列\n");
printf("%s", str2);
 
 
printf("中断してた計算の続きを開始します。\n");
 
int henkan = atoi(str2);
int keisan = henkan + 1;
 
 
printf("%d に 1 を足すと %d です。\n", henkan, keisan );
 
 
fclose(fp1);
printf("ファイルをクローズしました。\n");
return 0;
}
</syntaxhighlight>
 
実行結果
<pre>
ファイルをオープンしました。
文字列を読み取っています。
1行目の文字列
途中までの計算結果: 41
str1として読み取った文字列
途中までの計算結果
str2として読み取った文字列
41
中断してた計算の続きを開始します。
41 に 1 を足すと 42 です。
ファイルをクローズしました。
</pre>
 
 
== if文との組み合わせ ==