ファイル処理

ファイルのオープン

FILE *fopen(
   const char *filename, // ファイル名
   const char *mode      // 許可するアクセスの種類
);
fopen、_wfopen | MSDN

fopenの代わりに、セキュリティが強化されたfopen_sを使うことが推奨されています。

errno_t fopen_s(
   FILE **pFile,         // 開かれたファイルポインタを受け取るポインタ
   const char *filename, // ファイル名
   const char *mode      // 許可するアクセスの種類
);
fopen_s、_wfopen_s | MSDN

ただしfopen_sで開いたファイルは共有できないため、その必要があるときには_fsopenを用います。Remarks - fopen_s, _wfopen_s | Microsoft Learn

modeの文字
文字列    
読み取り 書き込み ファイルが存在しない ファイルが存在する
r × 呼び出しに失敗 そのファイルから読み込み
r+
w × ファイルを作成 その内容を破棄して、書き込み (上書き)
w+
a × ファイルを作成 EOFを削除せずに、末尾に書き込み
a+ EOFを削除して、末尾に書き込み
modeに追加できる文字
文字列 機能
t テキスト モード。キャリッジリターンとラインフィードを、変換する
b バイナリ モード。キャリッジリターンとラインフィードを、変換しない
c コミット フラグを有効
n コミット フラグをリセット
N 子プロセスに継承されないように指定
S キャッシュを、シーケンシャルアクセスに最適化
R キャッシュを、ランダム アクセスに最適化
T 一時ファイルとして指定。可能ならば、ディスクに書き込まれない
D 一時ファイルとして指定。ディスクに書き込まれない

fopenとfopen_sの違い

これらの関数の違いはエラーコードの取得方法にあります。fopenでは戻り値がNULLであることからエラーの発生を検知し、errnoマクロからエラーコードを取得します。一方でfopen_sでは戻り値でエラーコードが返され、それが0ではないことでエラーを検知できます。

FILE* stream;
int errorCode;

if (NULL == (stream = fopen("sample.txt", "r")))
{
    // エラー発生
    errorCode = errno;
}
FILE* stream;
errno_t errorCode;

if (0 != (errorCode = fopen_s(&stream, "sample.txt", "r")))
{
    // エラー発生
}

_fsopen

オープン時にファイル共有の方法を指定できます。

FILE *_fsopen(
    const char *filename,
    const char *mode,
    int shflag
);
_fsopen、_wfsopen | MSDN
shflagの値
定数 読み取り 書き込み
_SH_DENYNO
_SH_DENYRD (ReaD) ×
_SH_DENYRW (ReadWrite) × ×
_SH_DENYWR (WRite) ×

ファイルのクローズ

開いたファイルは必ず閉じます。特に書き込んだときに閉じないと、バッファ内のデータが書き込まれません。

  • fclose() … 指定されたストリームを閉じる。
  • _fcloseall() … 標準入出力 (stdin、stdout、stderr) 以外のすべてのストリームを閉じる。
int fclose(
   FILE *stream
);
int _fcloseall( void );
fclose、_fcloseall | MSDN

fflush

int fflush(
    FILE *stream
);
fflush | MSDN

ストリームをフラッシュできます。バッファが空か読み取り専用モードのときは、何も作用しません。

次のsetvbuf関数でバッファを無効にすることで、フラッシュを不要にできます。

バッファサイズの変更

ストリームのバッファサイズは既定で4kですが、これは次の関数で変更できます。

int setvbuf(
   FILE *stream, // FILE 構造体へのポインタ
   char *buffer, // ユーザーが割り当てたバッファー
   int mode,     // バッファリングのモード
   size_t size   // バッファー サイズ [バイト単位]
);
setvbuf | MSDN
modeの値
 
_IOFBF フル バッファリング (Full buffering)
_IOLBF 行バッファリング (Line buffering)
_IONBF バッファリングなし (No buffering)
char buf[1024];
if (setvbuf(stream, buf, _IOFBF, sizeof(buf)) != 0)
{
    // 失敗
}
// バッファを無効にする
setvbuf(stream, NULL, _IONBF, 0);

ファイルポインタ

  • rewind() … ファイルの先頭へ、ファイルポインタを移動
  • fseek() … 指定位置へ、ファイルポインタを移動
  • ftell() … ファイルポインタの現在の位置を取得
  • feof() … ファイルポインタが終端を越えているか確認
void rewind(
   FILE *stream //
);
rewind | MSDN
int fseek(
   FILE *stream, //
   long offset,  //
   int origin    //
);
fseek、_fseeki64 | MSDN

rewind()は、fseek()で

fseek(stream, 0L, SEEK_SET);

と呼び出すことと同義です。ただしrewind()では成功したかどうかは確認できず、呼び出し時にはエラー インジケーターがクリアされます。

long ftell(
   FILE *stream // FILE 構造体へのポインタ
);
ftell、_ftelli64 | MSDN

データの書き込み

int fputc(
   int c,       // 書き込む文字
   FILE *stream // FILE 構造体へのポインタ
);
fputc、fputwc | MSDN
int fputs(
   const char *str, // 書き込む文字列
   FILE *stream     // FILE 構造体へのポインタ
);
fputs、fputws | MSDN
size_t fwrite(
   const void *buffer, // 書き込むデータへのポインタ
   size_t size,        // 項目サイズ (バイト単位)
   size_t count,       // 書き込む項目の最大数
   FILE *stream        // FILE 構造体へのポインタ
);
fwrite | MSDN
int fprintf(
   FILE *stream,         // FILE 構造体へのポインタ
   const char *format [, // 書式指定文字列
   argument ]...         // 省略可能な引数
);
fprintf、_fprintf_l、fwprintf、_fwprintf_l | MSDN

これらの関数は、それぞれ次の値を返します。

  • fputc … 成功ならば書き込んだ文字、さもなくばEOF
  • fputs … 成功ならば0以上の値、さもなくばEOF
  • fwrite … 実際に書き込んだ項目数
  • fprintf … 成功ならば書き込んだバイト数、さもなくば負の値
FILE* stream;
errno_t errorCode = fopen_s(&stream, "sample.txt", "w");

if (errorCode != 0)
{
    // error
}
else
{
    int character = fputc('a', stream); // 97が返される
    int code = fputs("123", stream); // 0が返される


    char str[] = "ABC";
    size_t size1 = fwrite(str, sizeof(*str), sizeof(str) / sizeof(*str), stream); // 4が返される

    short num[] = { 1,2,3 };
    size_t size2 = fwrite(num, sizeof(*num), sizeof(num) / sizeof(*num), stream); // 3が返される


    char word[] = "Hello";
    int byte = fprintf(stream, "%s", word); // 5が返される

    if (fclose(stream) != 0)
    {
        // error
    }
}

データの読み込み

データを読み込む関数も複数ありますが、これらは読み込むデータサイズが異なります。

  • fgetc … 1文字
  • fgets … 1行
  • fread … 指定バイト数
  • fscanf … 指定書式の文字数

また、戻り値も異なります。

  • fgetc … 成功ならば読み取られた文字、さもなくばEOF
  • fgets … 成功ならば引数で渡したデータの格納場所 (str)、さもなくばNULL
  • fread … 読み込まれた項目数
  • fscanf … 変換され代入されたフィールド数

fgetc()

1文字読み込めます。

int fgetc(
   FILE *stream // FILE 構造体へのポインタ
);
fgetc、fgetwc | MSDN
int c = fgetc(stream);

ファイルの内容をすべて読み込むには、EOFが返されるまでfgetc()をくり返し呼びます。

FILE* stream;
errno_t errorCode;

if (0 == (errorCode = fopen_s(&stream, "sample.txt", "r")))
{
    int c;
    while (EOF != (c = fgetc(stream)))
    {
        fputc(c, stdout);
    }
    fclose(stream);
}

fgets()

1行分読み込めます。厳密には、次のいずれかの条件を満たすまで読みます。

  • 改行文字を読み込んだとき
  • 指定文字数-1文字まで読み込んだとき
  • ファイルの終端に達したとき
char *fgets(
   char *str,   // データの格納場所
   int n,       // 読み込む最大文字数
   FILE *stream // FILE 構造体へのポインタ
);
fgets、fgetws | MSDN

fgets()では末尾に必ずNULLが付加されるため、読み込まれる最大文字数はn-1となります。

char buffer[10];
if (NULL == fgets(buffer, 10, stream))
{
    // error
}

fread()

size_t fread(
   void *buffer, // データの格納場所
   size_t size,  // 項目のバイト単位のサイズ
   size_t count, // 読み込む項目の最大数
   FILE *stream  // FILE 構造体へのポインタ
);
fread | MSDN

fread()のcountは読み込む最大数であり、ファイルの末尾に達するかエラーが発生した場合には、実際に読み込まれるのはそれより少なくなります。実際に読み込まれた項目数は、戻り値で確認できます。

char buffer[10];
size_t size1 = fread(buffer, sizeof(*buffer), 10, stream);

// バッファの容量分だけ読み込む
size_t size2 = fread(buffer, sizeof(*buffer), sizeof(buffer) / sizeof(*buffer), stream);

fscanf()

int fscanf(
   FILE *stream,         // FILE 構造体へのポインタ
   const char *format [, // 書式指定文字列
   argument ]...         // 省略可能な引数
);
fscanf、_fscanf_l、fwscanf、_fwscanf_l | MSDN

fscanf_s

fscanf()には、バッファオーバーフローを防ぐ機能が追加されたfscanf_s()があります。

int fscanf_s(
   FILE *stream,         // FILE 構造体へのポインタ
   const char *format [, // 書式指定文字列
   argument ]...         // 省略可能な引数
);
fscanf_s、_fscanf_s_l、fwscanf_s、_fwscanf_s_l | MSDN

一見すると引数がfscanf()と同じですが、書式指定に文字列を含む場合に違いがあります。たとえばVisual C++で次のように記述すると、

char buffer[10];
fscanf_s(stream, "%s", buffer);

コンパイラは次のように警告してきます。

warning C4473: 'fscanf_s': 書式文字列として渡された引数が不足しています
note: プレースホルダーとそのパラメーターには 2 の可変個引数が必要ですが、1 が指定されています。
note: 不足している可変個引数 2 が書式文字列 '%s' に必要です
note: この引数はバッファー サイズとして使用されます

これに対処するには文字列を受け取る書式指定の後に、そのバッファのサイズを与えます。c++ - fscanf / fscanf_s difference in behaviour - Stack Overflow

fscanf_s(stream, "%s", buffer, _countof(buffer));

複数の変数を受け取る場合には、次のように記述します。

char buffer[10];
int x[10];
fscanf_s(stream, "%s%d", buffer, _countof(buffer), x);

エラーの確認

読み込んだ結果EOFが返されたとしても、それだけでは

  • ファイルの終端に達した
  • エラーが発生した

のいずれの状況なのか判断できません。

バイナリデータをテキストモードで読み込んだ場合には、終端でもエラーでもない状況でEOF (-1) が返されることがあります。これに対処するにはファイルのオープン時にモードに"b"を追記し、バイナリモードとします。

feof

ファイルの終端か否かを判断できます。

int feof(
   FILE *stream // FILE 構造体へのポインタ
);
feof | MSDN

ファイルポインタがファイルの終端を越えているならば0以外、さもなくば0が返されます。

feof()だけでループさせてはなりません。feof()は終端を越えた後に0以外を返すため、終端に達した時点を判定できません。またファイルポインタの取得に失敗した場合には、終端ではないときと同様に0が返されるため、無限ループとなってしまいます。c - Why is “while ( !feof (file) )” always wrong? - Stack Overflow

while (!feof(stream)) // streamが無効ならば、ループは終わらない
{
    fgetc(stream); // 必ず1バイト余計に読み取ってしまう
    ...
}

ferror

int ferror(
   FILE *stream // FILE 構造体へのポインタ
);
ferror | MSDN

エラーが発生しているならば0以外、さもなくば0が返されます。

ファイル情報の取得

int _stat(
   const char *path, // ファイルパス
   struct _stat *buffer
);
_stat、_stat32、_stat64、_stati64、_stat32i64、_stat64i32、_wstat、_wstat32、_wstat64、_wstati64、_wstat32i64、_wstat64i32 | MSDN

すでに開いているファイルを対象とするならば、_fstat()を用います。

int _fstat(
   int fd, // 開いているファイルのファイル記述子
   struct _stat *buffer
);
_fstat、_fstat32、_fstat64、_fstati64、_fstat32i64、_fstat64i32 | MSDN
_stat構造体
メンバ 内容
st_gid Numeric identifier of group that owns the file (UNIX-specific) This field will always be zero on Windows systems. A redirected file is classified as a Windows file.
st_atime Time of last access of file. Valid on NTFS but not on FAT formatted disk drives.
st_ctime Time of creation of file. Valid on NTFS but not on FAT formatted disk drives.
st_dev Drive number of the disk containing the file (same as st_rdev).
st_ino Number of the information node (the inode) for the file (UNIX-specific). On UNIX file systems, the inode describes the file date and time stamps, permissions, and content. When files are hard-linked to one another, they share the same inode. The inode, and therefore st_ino, has no meaning in the FAT, HPFS, or NTFS file systems.
st_mode ファイルモード情報のビットマスク
定数 意味
_S_IFMT File type mask
_S_IFDIR Directory
_S_IFCHR Character special (indicates a device if set)
_S_IFREG Regular
_S_IREAD Read permission, owner
_S_IWRITE Write permission, owner
_S_IEXEC Execute/search permission, owner
Remarks - _stat Structure st_mode Field Constants | MSDN
st_mtime Time of last modification of file.
st_nlink Always 1 on non-NTFS file systems.
st_rdev Drive number of the disk containing the file (same as st_dev).
st_size Size of the file in bytes; a 64-bit integer for variations with the i64 suffix.
st_uid Numeric identifier of user who owns file (UNIX-specific). This field will always be zero on Windows systems. A redirected file is classified as a Windows file.
Remarks - _stat, _stat32, _stat64, _stati64, _stat32i64, _stat64i32, _wstat, _wstat32, _wstat64, _wstati64, _wstat32i64, _wstat64i32 | MSDN
struct stat buf;
if (stat("sample.txt", &buf) == 0)
{
    unsigned short mode = buf.st_mode;
    long size           = buf.st_size;
}

ファイルの存在確認

stat()によりファイルの情報を取得できたときに、それが存在していると判定できます。Fastest way to check if a file exist using standard C++/C++11/C? - Stack Overflow

この関数はロックが原因で開けないときでも、0を返します。

struct stat buf;
if (stat("sample.txt", &buf) == 0)
{
    // 存在している
}
else
{
    // 存在していない
}

パス

パス名の分解

void _splitpath(
   const char *path, // 完全パス
   char *drive,      // ドライブ文字
   char *dir,        // ディレクトリ パス
   char *fname,      // ファイル名 (拡張子なし)
   char *ext         // ファイル名の拡張子
);
_splitpath、_wsplitpath | MSDN

分解するパスをpathで渡すと、分解されたパスがそれ以外の引数で返されます。結果として不要な要素にはNULLを渡します。

char path[] = "C:\\a\\b\\c.txt";

char drive[_MAX_DRIVE];
char dir[_MAX_DIR];
char fname[_MAX_FNAME];
char ext[_MAX_EXT];

_splitpath(path, drive, dir, fname, ext);

// drive : "C:"
// dir   : "\\a\\b\\"
// fname : "c"
// ext   : ".txt"

リダイレクト

FILE *freopen(
   const char *path, // 新しいファイル パス
   const char *mode, // アクセス許可の種類
   FILE *stream      // 割り当て元のファイルへのポインタ
);
freopen、_wfreopen | MSDN

freopen()はpathのファイルを開き、そのポインタをstreamへ割り当てます。そして開いたファイルへのポインタを返します。

たとえばstderrへの出力をファイルへリダイレクトするには、次のようにします。

FILE* stream = freopen("err.txt", "w", stderr);

パイプ

int _pipe(
   int *pfds,          // 読み書きファイル記述子を格納する配列
   unsigned int psize, // パイプのために予約するメモリ量 [バイト単位]
   int textmode        // ファイルモード
);
_pipe | MSDN

2つの要素を持った配列pfdsを渡すと、_pipe()はそれに

  • pfds[0] … 読み取り記述子
  • pfds[1] … 書き込み記述子

を割り当てます。

psizeで十分なメモリを確保しないと、パイプを使用した読み書き時に待機し続けることになります。

textmodeでは変換モードを指定します。0と指定した場合は、既定で_O_TEXTとなります。

  • _O_TEXT (テキスト モード / 変換モード) … 復帰と改行が変換される
  • _O_BINARY (バイナリ モード / 無変換モード) … 復帰と改行は変換されない
#define O_TEXT    _O_TEXT
#define O_BINARY  _O_BINARY

#define _O_TEXT   0x4000  // file mode is text (translated)
#define _O_BINARY 0x8000  // file mode is binary (untranslated)
ucrt\fcntl.h

ストリームの接続

int _dup(
   int fd // 開いているファイルを参照するファイル記述子
);

fdのコピーが作成され、それが返されます。

int _dup2(
   int fd1, // 開いているファイルを参照するファイル記述子
   int fd2  // 任意のファイル記述子
);
_dup、_dup2 | MSDN

fd1のコピーが作成され、それがfd2に割り当てられます。

エラーが発生した場合には、各関数とも-1を返します。

#define BUFFER_SIZE 256

int pfds[2];
_pipe(pfds, BUFFER_SIZE, O_BINARY);

_dup2(pfds[0], _fileno(stdin));  // 読み取り記述子のコピーを作成し、それを標準入力へ割り当てる
_dup2(pfds[1], _fileno(stdout)); // 書き込み記述子のコピーを作成し、それを標準出力へ割り当てる


setvbuf(stdout, NULL, _IONBF, 0); // バッファを無効にする
fprintf(stdout, "123456789");     // 標準出力への出力を、標準入力へパイプする

char buffer[BUFFER_SIZE] = {};
fread(buffer, sizeof(*buffer), 5, stdin);


_close(pfds[0]);
_close(pfds[1]);
FILE* stream = fopen("sample.txt", "r");

// ファイルストリームのコピーを作成し、それを標準入力へ割り当てる
_dup2(_fileno(stream), _fileno(stdin));

ファイルを示すオブジェクトの変換

ファイルを示すオブジェクトには、

  • FILE構造体 (FILE structure)
  • ファイル記述子 (file descriptor)
  • Win32 ファイル ハンドル (Win32 file handle)

がありますが、これらはそれぞれ変換できます。

変換元 → 変換先 関数
ファイル記述子 → FILE構造体
FILE *_fdopen(
   int fd,          // 開いているファイルの、ファイル記述子
   const char *mode // ファイルアクセスの種類
);
_fdopen、_wfdopen | MSDN
FILE構造体 → ファイル記述子
int _fileno(
   FILE *stream // FILE 構造体へのポインタ
);
_fileno | MSDN
ファイル記述子 → ファイル ハンドル
intptr_t _get_osfhandle(
   int fd // 既存のファイル記述子
);
_get_osfhandle | MSDN
ファイル ハンドル → ファイル記述子
int _open_osfhandle (
   intptr_t osfhandle, // OSのファイル ハンドル
   int flags           // 許可される演算の種類
);
_open_osfhandle | MSDN

参考

参考書

Microsoft Learnから検索