幼稚園時代「何組」でしたか?

2つのプログラムで一つのテキストファイルに書き込むと,エラーが起きてプログラムが停止するんじゃないかなーと思っていましたが,起きませんでした.
検証結果がまた謎であり,知識も足りず,解釈ができないので,力を貸してください.
動作環境は先に言いますと,win7のVC++2010コンパイラです.
---プログラムA
FILE *fp = fopen("test.txt", "w");
while(true){
fputc('@', fp);
}
fclose(fp);
---end
---プログラムB
FILE *fp = fopen("test.txt", "w");
while(true){
fputc('_', fp);
}
fclose(fp);
---end
(1)Aを走らせる・・・ファイルには@がたくさん出力されていると思われる.
(2)Bを走らせる・・・Aがファイルを操作しているんだから,fopen関数でエラーが出ると思ったが,出なかった.稼働してしまったので,fopenの書き込みモードによってファイルは真っ新になり,「_」が先頭から羅列され始めただろう.
(3)Bを停止する・・・「_」の出力は止まっただろうが,プログラムAの方は今何をしているんだろう.元気かな
(4)Aを停止する
出力結果すなわちtest.txtの内容は次のようだった.
___________・・・______[NULL][NULL][NULL]・・・[NULL][NULL]@@@@@@@・・・@@@@@@@
これについて質問があります.
Q.NULLが出力されているのはどうしてか.
Q.AはBが書き込んでいる間,待っていたようだ(Bを停止した後で待ってましたと言わんばかりに@が書き込まれた形跡があるから)が,これは仕様なのか.エラーが出るということを気にする必要はないのか.
余談,予備知識大歓迎です.よろしくお願い致します.

A 回答 (2件)

同様の事を経験した事があります。


(以下、既に御存知の事が含まれていたらすみません)

> Q.NULLが出力されているのはどうしてか.

OS に依存する話かもしれませんが、少なくとも windows ではファイルハンドル毎に「現在の書き込み位置」が管理されています。そして、ファイルが現在どの様な状態になっていようとも「現在の書き込み位置」にデータを書き込もうとします。質問の例では、以下の様な動作になっているのではないでしょうか。
(1) プログラム A が起動し、test.txt を作成する (または、既存のファイルの長さを0にする)。
  (初期の「書き込み位置」は、勿論ファイルの先頭。)
(2) プログラム A が "位置 X" まで '@' を書き込む (X 文字の @ を出力する)
(3) プログラム B が起動し、test.txt の長さを 0 にする。
(4) プログラム A は "位置 X" に新しく '@' を書き込もうとするが、
  test.txt の長さが短くなっているので、「長さ X まで拡張」してから '@' を書き込む。
(5) 後は、プログラム B は先頭から順番に文字 '_' を書き込み、
  プログラム A は "位置 X" 以降に文字 '@' を書き込む。
  プログラム B は "位置 Y" まで書き込んだ所で停止し、
  プログラム A は "位置 Z" まで書き込んだ所で停止する。

この過程の (4) の「長さ X まで拡張」の所で 0 (NUL) を fill しているのでしょう。
結果として、"_ _ … _ _ (位置 Y) NUL NUL … NUL NUL (位置 X) @ @ … @ @ (位置 Z)" という内容になるのだと思います。

(3) で長さ 0 にされるのが嫌ならば、fopen の時に "w" ではなくて "r+" で開けば良いです。
"r+" は既存ファイルを読み書き両用で開き、ファイルをクリアしない物です。
ファイルが既存でないかもしれないならば、"a" で開いてから rewind 関数を呼ぶなどすれば良いです。

> Q.AはBが書き込んでいる間,待っていたようだ

うーん。質問に記述された結果だけ見れば、特にその様な事があったとは言えない気がします。また、(ファイル排他制御などしていないので) 「書き込みの間待っている」という仕様もないと思います。それから、エラーが出るかどうかについては…エラーが出るという事は無いと思いますが、ファイルの中身が滅茶苦茶になるのでこの様なプログラムに意味があるかは分かりません。。。

但し、それぞれのプログラムのバッファリングによって、文字が実際にファイルに書き込まれるタイミングが遅れる事には注意して下さい。
つまり、プログラム上で fputc を実行しても即座にディスクにデータが書き込まれる訳ではなくて、プログラム内部でデータが或る程度溜まるのを待ってから、まとめて書き出しを実行します。(1文字ごとにディスクと通信していたら時間がかかりますものね!) また、fclose を実行すると、溜めているデータを全部書き出してからファイルを閉じますが、プログラムを強制終了すると、溜めている未書き出しデータが出力されずに終了します。


余談: 参考までに:

「現在の書き込み位置」については ftell 関数 fseek 関数 rewind 関数
「バッファリング」に関しては: fflush 関数, setvbuf 関数

この回答への補足

xitoaki様、akinomyoga様、ご回答ありがとうございました。
返信が遅れてすみません。
OSが勝手に排他制御をやってくれているわけではないと分かったので、排他制御をいれていこうと思います。
お二方とも、ありがとうございました。

補足日時:2013/10/24 17:07
    • good
    • 0
この回答へのお礼

なるほど、(4)の時点で¥0で埋められている可能性があるのですね。
Y>XだとNULLが発生しないことになりますね。実験パターン不足ですみません。
納得しました。ありがとうございます。
又、rewindなどといった関数を初めて知りました。
あえて排他制御せずにこの失敗実験のようなことを意図的にやりたいときは、アナザープログラムで追加書き込みオープン+ファイルポインタ先頭化でいけるわけですね。必要になったら活かしていこうと思います。
バッファーのサイズも指定できるんですか。
いつもデバッグでプリント文挟んで、そのプリント文を通過しているにもかかわらず出力されず、結局exit関数でエラーの位置を特定するはめになる理由もこれかもしれないですね。出力バッファのサイズを1文字にすればこれを回避できそうです。効率が悪くなるのはありますけれども。
貴重なお話をありがとうございました。

お礼日時:2013/10/24 17:03

排他制御をしていなければこのようになるのが不思議ではありません。


OS依存の部分が大きいので、断定は出来ませんが、
「AはBが書き込んでいる間,待って」はいないと思います。

どちらもファイルの先頭にあたる同じアドレスから書き始めていますが、
Aが先に「@」を書き込んだ部分に、Bが後から「_」を上書いていっています。
先にBを止めたので、上書きが途中までしか行われず、
後半にAが書いた部分が残っているだけでしょう。

途中のNULLは、強制終了しているために変なデータが入っているのかもしれません。
テキストで開いているため、NULLに見えるだけで、
NULLではない何らかの制御文字が入っている可能性もあります。
毎回同じようになるのであれば、OSのファイル書き込み処理の手順的なものが
書かれているのでしょう。そうでなければ、たまたま何かゴミが入ったものと思います。

EOFを出力せずファイルをクローズもしていないので、
EOFの扱いがどうなっているか判りませんが
(このあたりもOSに依存するものでしょう)
Bが書いた領域の直後にEOFが書かれなかった為、結果的にAが書いたものが続いて見えています。
    • good
    • 0
この回答へのお礼

バイナリエディタで開いた上でNULLでした。
一連の現象はOS依存の未定義の挙動であって、本来は排他制御を行う必要があるということですね。
xitoakiさんの推理になるほどと思いました。
ご回答ありがとうございました。

お礼日時:2013/10/24 16:22

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!


おすすめ情報