ファイル内容を確実に出力するための方法についてのお勉強
お勉強シリーズ第一弾ッ! (1回しかやらなさそう臭がハンパないですね)
日々勉強した結果を、複数回に分けて記載・修正して本エントリを完成させます。(2回更新しました)
※ご注意※ 本エントリは未完成です。内容に誤りがある可能性がありますのでご注意ください。
ファイル書き込み関数で出力したはずなのに、実際のファイルサイズが0バイト。
少し時間が経過するとちゃんと書き込まれているのだけど…という経験はありませんか?
これはfwrite()などの関数が内部的にバッファを保持しており、
一定量や一定時間経過後(このへん適当に言ってます)にまとめて出力しているためです。
毎回ディスクアクセスを行わず、効率よくディスクに出力しているのですね。
このディスクへ書き込む処理のことをフラッシュと呼びます。
時にこのようなバッファリング処理は問題を起こすことがあります。
例えば、ファイルをポーリングで監視しており、ファイルを見つけたらオープンして内容を
読み込んで処理するような場合です。ファイル自体は存在するものの、
まだ書き込まれていないファイルをオープンして読もうとしますがサイズは0。困りますね。
では、どうすればいいのでしょうか。
書き込んだら、すぐにバッファをフラッシュするような処理をプログラム側で行えば良いのです。
それぞれの特徴と気を付けるべき点をまとめようと思います。
まずはWindowsを中心に。余裕があればLinuxも。
ファイル書き込みAPI(Windows)
APIによって行うべき処理が変わります。
まずは表で一覧にしてみます。
API名 | ヘッダ | フラッシュする方法 |
CreateFile,WriteFile | windows.h | FlushFileBuffers(HANDLE) |
_open/_wopen,_write | io.h,windows.h | _commit(int)もしくは_get_osfhandle(int)→FlushFileBuffers(HANDLE) |
fopen,fwrite | stdio.h | fflush(FILE*)もしくはあまり使わないがflushall() |
ofstreamのopen,<<演算子 | fstream | ofstream::flush |
CreateFile,WriteFile(ヘッダ:windows.h)
FlushFileBuffers(HANDLE)を使います。GENERIC_WRITE アクセス権が必要です。
_open/_wopen,_write(ヘッダ:io.h、windows.h)
方法は2つあるようです。
一つ目は_commit(int)を単純に呼び出す方法。引数はファイルディスクリプタです。
二つ目はファイルディスクリプタをHANDLE型に変換して、
FlushFileBuffers(HANDLE)を使う方法です。
#include <io.h> #include <windows.h> #include <errno.h> HANDLE h = (HANDLE)_get_osfhandle(fd); if ( h == INVALID_HANDLE_VALUE ) { // error } if ( !FlushFileBuffers(h) ) { // error // GetLastError()でエラーコードを取得できる。 }
fopen,fwrite (ヘッダ:stdio.h)
fflush()でFILE*内部のバッファを出力するようです。_commit()は効かないので注意!
またあまり使わないですがflushall()を使う方法もあります。
この関数は、現在プロセスがオープンしているすべての
ファイルディスクリプタをフラッシュするようです。
ofstreamのopen,<<演算子 (ヘッダ:fstream)
C++のストリームを使用する場合は、ofstreamのメンバ関数flush関数を使います。
#include <fstream> using namespace std; std::ofstream outfile; outfile.open("test.txt"); outfile << "abc"; outfile.flush(); outfile.close(); return 0;
Linuxの場合
fsync()を使います。