閉じる

【C言語】 tmpfileの使い方|一時ファイル作成から削除まで

C言語で大量データを一時的に保存したいとき、ファイル名を自分で決めてfopenを使う方法は少し面倒です。

そこで便利なのが自動で一時ファイルを作成し、不要になれば自動削除までしてくれるtmpfileです。

本記事では、tmpfileの基本から実践的な活用例、OSごとの挙動の違いまで、図解とサンプルコードを交えながら詳しく解説します。

tmpfileとは

tmpfileとは何をする関数か

tmpfileは、C言語の標準ライブラリ<stdio.h>で提供されている一時ファイルを作成してFILE *を返すための関数です。

この関数の特徴は次のように整理できます。

ここではまず概要を文章で押さえます。

第一にtmpfile一意な一時ファイルを自動で作成します。

ファイル名や保存場所はOSや実装に任されており、通常は開発者が意識する必要はありません。

第二に、返されるFILE *によって、fwritefreadなど、通常のファイルと同様の入出力が可能です。

この意味で、tmpfile「名前の分からない普通のファイル」と考えるとイメージしやすくなります。

第三に、一時ファイルはプログラム終了時やfclose時に自動的に削除されるよう設計されています。

これにより、後片付けの手間や削除忘れによるファイルのゴミを減らすことができます。

一時ファイルの用途とメリット

一時ファイルは、「今だけ必要なデータを、プログラム終了後には残さず捨ててよいデータ」を扱うときに使われます。

典型的な用途として、次のようなケースが挙げられます。

1つ目は、大量データの一時退避です。

例えば、巨大なログデータを処理するときに、すべてをメモリ上に持つのは現実的ではありません。

そこで、一時ファイルに一旦書き出してから順番に読み出すことで、メモリ使用量を抑えつつ処理できます。

2つ目は、ソートや集計の中間結果の保存です。

外部ソートアルゴリズムなどでは、一度分割してソートした結果を複数のファイルに書き出し、それをマージして最終結果を得ます。

このような一時的な中間結果には、tmpfileによる一時ファイルが適しています。

3つ目は、メモリ不足時の「一時ディスク利用」です。

例えば、大きな配列の代わりに、一時ファイルにデータをページングしながら読み書きすることで、システムのメモリ制約を回避できます。

これらの場面では、処理が終わればファイルを残しておく必要はありません。

そのため、自動的に削除される一時ファイルは非常に相性が良いのです。

一般的なファイル操作との違い

一般的なファイル操作では、次のような流れを取ることが多いです。

  1. 開発者がファイル名とパスを決める
  2. fopen("data.tmp", "wb+")のように開く
  3. 読み書きする
  4. fcloseで閉じる
  5. ファイルを残したくなければremove("data.tmp")で削除する

これに対してtmpfileでは、ファイル名やパスを指定する必要がありません

また、fcloseした時点で、同時にファイルが削除されるのが大きな違いです。

つまり、通常のfopenと比較すると、ファイル名の競合や削除し忘れのリスクが減り、セキュリティ上も「どこにどんな名前で作られたかを知られにくい」というメリットがあります。

tmpfileの基本的な使い方

tmpfileの関数プロトタイプと戻り値

tmpfileのプロトタイプは次のように定義されています。

C言語
#include <stdio.h>

FILE *tmpfile(void);

戻り値の意味は非常に重要です。

  • 成功時: 有効なFILE *が返り、そのポインタを通して読み書きができます。
  • 失敗時: NULLが返り、グローバル変数errnoにエラー内容が設定されます。

必ずNULLチェックを行い、失敗時の処理を書いておくことが、安全なプログラムを書く上で非常に重要です。

詳細は後の「エラー処理とNULLチェックのポイント」で解説します。

tmpfileによる一時ファイルの作成手順

tmpfileを使う際の基本的な手順は、次の4段階に整理できます。

  1. 一時ファイルを作成するためにtmpfile()を呼び出し、FILE *を受け取る。
  2. 戻り値がNULLでないかエラーを確認する。
  3. 通常のファイルと同じ要領で、fprintffreadfwriteなどで読み書きする。
  4. 処理が終わったらfcloseクローズし、自動削除させる。

次の節以降では、実際のサンプルコードを通して具体的な手順を確認していきます。

tmpfileで開いた一時ファイルへの書き込みと読み込み

ここでは、実際にtmpfileで一時ファイルを作成し、文字列を書き込んでから読み出すシンプルな例を示します。

C言語
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    // 一時ファイルを作成し、FILEポインタを取得
    FILE *fp = tmpfile();
    if (fp == NULL) {
        // tmpfileに失敗した場合はエラーメッセージを出して終了
        perror("tmpfile failed");
        return EXIT_FAILURE;
    }

    // 一時ファイルに文字列を書き込む
    const char *msg = "Hello, tmpfile!\n";
    // fputsは文字列を書き込む関数です
    if (fputs(msg, fp) == EOF) {
        perror("fputs failed");
        fclose(fp);  // エラー時でも忘れずにクローズ
        return EXIT_FAILURE;
    }

    // 読み出しの前に、ファイル位置を先頭に戻す
    // rewindはファイル位置を先頭に移動し、エラー状態もクリアします
    rewind(fp);

    // 読み出し用バッファ
    char buf[256];

    // fgetsで1行読み込む
    if (fgets(buf, sizeof(buf), fp) == NULL) {
        perror("fgets failed");
        fclose(fp);
        return EXIT_FAILURE;
    }

    // 読み出した内容を標準出力に表示
    printf("tmpfile content: %s", buf);

    // 一時ファイルをクローズ(同時に削除されます)
    fclose(fp);

    return EXIT_SUCCESS;
}
実行結果
tmpfile content: Hello, tmpfile!

このコードでは、書き込み後にrewind(fp)を呼んでいる点が重要です。

ファイルは内部に「現在の位置」のインジケータを持っており、書き込んだ直後はファイル末尾に位置しています。

そのままfgets等で読み込もうとしても、「もう読むデータがない」と判断されてしまうためです。

書き込み → 読み込みという流れを取るときは、rewindfseek(fp, 0, SEEK_SET)ファイル位置を先頭に戻すことを忘れないようにしてください。

バイナリデータを扱う場合の例

次に、構造体をバイナリ形式で一時ファイルに保存し、再度読み出す例を示します。

C言語
#include <stdio.h>
#include <stdlib.h>

typedef struct {
    int id;
    double value;
} Record;

int main(void) {
    FILE *fp = tmpfile();
    if (fp == NULL) {
        perror("tmpfile failed");
        return EXIT_FAILURE;
    }

    // 書き込むデータを用意
    Record out = { 123, 45.67 };

    // 構造体をバイナリとして一時ファイルに書き込む
    if (fwrite(&out, sizeof(Record), 1, fp) != 1) {
        perror("fwrite failed");
        fclose(fp);
        return EXIT_FAILURE;
    }

    // 読み出しのためにファイル位置を先頭に戻す
    rewind(fp);

    // 読み出し先の構造体
    Record in;

    // 構造体を一時ファイルから読み出す
    if (fread(&in, sizeof(Record), 1, fp) != 1) {
        perror("fread failed");
        fclose(fp);
        return EXIT_FAILURE;
    }

    // 読み出した内容を確認
    printf("id = %d, value = %.2f\n", in.id, in.value);

    fclose(fp);
    return EXIT_SUCCESS;
}
実行結果
id = 123, value = 45.67

このように、テキストでもバイナリでも、通常のFILE *と全く同じ感覚で使えることがtmpfileの大きな利点です。

エラー処理とNULLチェックのポイント

tmpfileが失敗する原因としては、例えば以下のようなものがあります。

  • OSが一時ファイルを作成するためのディスク容量を確保できない
  • 一時ファイルを作成するディレクトリへの権限がない
  • 同時に開けるファイル数の上限に達している

このような場合、tmpfileNULLを返し、errnoにエラーコードを設定します。

perror関数を使うとerrnoの内容をわかりやすいメッセージとして出力できるため、デバッグ時に非常に有用です。

特に重要なのは次の2点です。

1つ目は、戻り値がNULLでないことを必ず確認することです。

FILE *NULLなのにfwrite等を呼び出すと、未定義動作となり危険です。

2つ目は、エラー発生時の後始末です。

すでに何かを書き込んだ後で別のエラーが起きた場合、その時点でもfcloseを呼んで後片付けをしておくとよいでしょう。

tmpfileであれば、クローズと同時にファイルも削除されます。

tmpfileで作成した一時ファイルの削除タイミング

tmpfileの自動削除の仕組み

tmpfileで作成された一時ファイルは、プログラムから参照されているFILE *が閉じられたときに自動的に削除されます。

この仕組みはOSごとに実装が異なりますが、一般的には次のいずれかの方式で動作します。

1つ目の方式は、作成直後にファイル名を削除(アンリンク)し、FILE *を通じてのみアクセスできるようにする方法です。

Unix系システムでよく用いられます。

この場合、ファイルシステム上には「名前のないファイル」として存在し、FILE *が最後の参照である限り削除されません。

2つ目の方式は、一時的なファイル名を付けて作成し、fclose時に削除する方法です。

Windowsなど一部のプラットフォームで用いられます。

この方式でも、基本的な動作としては「プログラムからはファイル名を意識しなくてよい」ことが保証されています。

いずれの方式にせよ、プログラマとしてはfcloseを呼び出せば一時ファイルも消えると理解しておけば十分です。

fcloseによる一時ファイルの明示的なクローズ

tmpfileで作成した一時ファイルも、他のファイルと同様、fcloseを呼ぶことでバッファのフラッシュとリソース解放が行われます。

その際に、同時に一時ファイルそのものも削除されます。

つまり、次のようなコードでは、fcloseを呼んだ時点で一時ファイルは存在しなくなります。

C言語
FILE *fp = tmpfile();
/* 読み書き */
fclose(fp);  // ここで一時ファイルも削除される

意識すべきポイントは、fcloseを忘れると、一時ファイルがプログラム終了まで残り続けるという点です。

通常はプロセス終了時にOSが後片付けしてくれますが、大量に一時ファイルを開いたままにすると、同時オープン数の制限に達するおそれがあります。

したがって、使い終わったらすぐfcloseするという基本ルールは、tmpfileでも変わりません。

プログラム異常終了時の一時ファイルの扱い

プログラムが異常終了した場合の一時ファイルの扱いは、OSや実装に依存しますが、多くの環境では次のような挙動が期待できます。

Unix系システムで、作成直後にファイル名を削除している実装では、プロセスが終了するとファイルディスクリプタが閉じられ、それに伴ってファイルも自動的に削除されます。

したがって、異常終了であっても一時ファイルが残り続けることはほぼありません

一方、Windowsなど名前付きで一時ファイルを作成し、クローズ時に削除する実装では、まれに異常終了時に一時ファイルが残ってしまう可能性があります。

とはいえ、このようなケースは通常はファイル名も不明であり、検出や削除は困難です。

このため、クリティカルな環境では異常終了しないような設計や、専用の一時ディレクトリを監視・クリーンアップする仕組みが重要になります。

tmpfileとremove(標準ファイル削除関数)の違い

一時ファイル削除に関して、tmpfileremove関数の違いを整理しておきます。

次の表を見てください。

項目tmpfilefopen + remove
ファイル名指定不要必要(文字列で指定)
削除タイミングfclose時(自動)、またはプロセス終了時removeを呼んだ時(手動)
削除手段暗黙的(実装依存だが自動)明示的にremove("path")
セキュリティ名称を扱わないため露出しにくいパス/名前がコードやログに残りやすい
コード量少ないやや多い

本質的な違いは、「ファイル名をプログラム側で扱うかどうか」にあります。

tmpfileファイル名の生成と削除をライブラリ側に任せる設計であり、remove自分で決めたファイル名を、自分で削除するための関数です。

tmpfile利用時の注意点と実践的な活用例

tmpfileのセキュリティと一時ファイル名の扱い

一時ファイルは、ときに機密性のある一時データを扱うことがあります。

たとえば、一時的に復号したデータや、認証処理の中間結果などがそれにあたります。

tmpfileのセキュリティ上の利点として、ファイル名を自分で決めたり公開したりしないことが挙げられます。

Unix系の実装ではファイル名そのものが存在しないため、別プロセスが「同じパス名で開き直す」ことが原理的に不可能です。

これは、tmpnammktempのように一時ファイル名だけを生成してからfopenする方法と比較すると、明らかにセキュリティ上の利点があります。

後者ではTOCTOU(Time Of Check To Time Of Use)攻撃と呼ばれる、「名前を生成した後で、開くまでの間に他者が同じ名前でファイルを作成してしまう」リスクがあるためです。

ただし、tmpfileを使えば絶対に安全というわけではありません

OSやファイルシステムの実装、不適切な権限設定などによっては、情報漏洩の可能性も完全には排除できません。

それでも、自前でファイル名を生成するよりは、一般に安全性が高い選択肢と言えます。

WindowsとLinuxでのtmpfileの挙動の違い

tmpfileの仕様は標準Cで定義されていますが、内部的な挙動はOSによって異なります

代表的なLinux(Unix系)とWindowsでの違いを、概念的に整理しておきます。

LinuxなどのUnix系システムでは、多くの場合次のような流れになります。

  1. /tmpなどの一時ディレクトリに、一意な一時ファイルを作成する。
  2. すぐにファイル名を削除(unlink)し、匿名ファイルとする。
  3. FILE *を通じてのみアクセスできる状態になる。
  4. fclose、またはプロセス終了時に、ファイルディスクリプタが閉じられ、物理的にも削除される。

この方式では、プロセス外からファイルのパスを辿ることが困難であり、セキュリティ上のメリットがあります。

一方、Windowsでは、標準ライブラリの実装にもよりますが、概ね次のようなイメージになります。

  1. GetTempPathなどで取得した一時ディレクトリに、一時ファイルを作成する。
  2. ファイル名は内部的には存在しており、実行中にも確認可能な場合がある。
  3. fcloseが呼ばれると、ファイルが削除される。

この場合でも、通常はプログラムからファイル名を扱う必要はありません

しかし、異常終了時にファイルが残る可能性など、Unix系とは異なる挙動を示すことがあります。

プラットフォーム依存の挙動が気になる場合は、ターゲット環境で実際に挙動を検証しておくと安心です。

大量データ処理でのtmpfile活用例

tmpfileは特に大量データ処理で真価を発揮します。

ここでは、簡易的な外部ソート風処理を例として、どのように活用できるかをイメージだけ掴んでおきましょう。

例えば、巨大な数値列をソートするとします。

  1. メモリに収まるサイズだけデータを読み込む。
  2. メモリ内でソートする。
  3. ソート済みデータを一時ファイルに書き出す。
  4. これを繰り返して、複数のソート済み一時ファイルを作る。
  5. 最後にそれら一時ファイルを読みながらマージし、最終的なソート結果を得る。

このとき、各一時ファイルをtmpfileで生成すれば、ファイル名の競合や削除忘れを気にせずに済みます

以下は、非常に簡略化したイメージコードです(実際の外部ソートほど汎用的ではありませんが、流れを掴むための例です)。

C言語
#include <stdio.h>
#include <stdlib.h>

#define CHUNK_SIZE 5

// 簡単な整数比較関数(qsort用)
int int_cmp(const void *a, const void *b) {
    int ia = *(const int *)a;
    int ib = *(const int *)b;
    return (ia > ib) - (ia < ib);
}

int main(void) {
    int data[] = { 9, 1, 7, 3, 2, 8, 5, 4, 6, 0 };
    size_t n = sizeof(data) / sizeof(data[0]);

    // ここでは説明のため、一つのチャンクだけを処理する
    size_t chunk = n < CHUNK_SIZE ? n : CHUNK_SIZE;

    // チャンクをメモリ内でソート
    qsort(data, chunk, sizeof(int), int_cmp);

    // ソート済みチャンクを一時ファイルに保存
    FILE *fp = tmpfile();
    if (fp == NULL) {
        perror("tmpfile failed");
        return EXIT_FAILURE;
    }

    if (fwrite(data, sizeof(int), chunk, fp) != chunk) {
        perror("fwrite failed");
        fclose(fp);
        return EXIT_FAILURE;
    }

    // 読み戻して確認
    rewind(fp);

    int buf[CHUNK_SIZE];
    if (fread(buf, sizeof(int), chunk, fp) != chunk) {
        perror("fread failed");
        fclose(fp);
        return EXIT_FAILURE;
    }

    printf("sorted chunk: ");
    for (size_t i = 0; i < chunk; i++) {
        printf("%d ", buf[i]);
    }
    printf("\n");

    fclose(fp);  // 一時ファイルも削除される

    return EXIT_SUCCESS;
}
実行結果
sorted chunk: 1 2 3 7 9

実際の大規模処理では、複数のFILE *tmpfileで用意し、順次マージしていくことで、使用メモリを一定以下に保ちながら巨大なデータセットを扱うことが可能になります。

tmpfile以外の一時ファイル作成方法との比較

一時ファイルを作成する方法はtmpfile以外にもいくつか存在します。

それぞれの特徴を簡単に整理します。

方法特徴安全性ファイル名を取得できるか
tmpfile名前不要で一時ファイル作成。クローズで自動削除。比較的高い通常は不可
tmpnam一時ファイル名の文字列だけを生成。非推奨。低い(競合・レースの危険)取得可能
tmpnam_s(一部環境)tmpnamの安全版を目指したもの。改善されているが注意必要取得可能
mkstemp(POSIX)一意なテンプレートからファイルを作成&オープン。高い取得可能
自前でランダム名生成 + fopen実装次第。バグやレースの危険。実装に依存取得可能

最も大きな分岐は、「ファイル名が必要かどうか」です。

  • 一時ファイルに別プロセスや別プログラムからアクセスする必要がない場合は、tmpfileが最も簡便で安全な選択です。
  • 一時ファイルのパスを別の部分に渡したい、あるいは他プロセスとの連携に使いたい場合には、POSIX環境ならmkstempなどの方法も検討することになります。

tmpnamは競合やセキュリティ上の問題から非推奨とされており、新しいコードでは基本的に避けるべきです。

まとめ

tmpfileは「ファイル名を意識せずに、安全に一時ファイルを扱える」便利な関数です。

tmpfileで一時ファイルを作成し、通常のFILE *同様に読み書きし、使い終わったらfcloseするだけで自動的に削除されます。

大量データ処理や一時的な中間結果の保存など、メモリだけでは扱いきれない場面で特に有用です。

OSごとの挙動の違いを意識しつつ、「エラー時のNULLチェック」「クローズの徹底」を守れば、シンプルかつ安全に一時ファイルを活用できます。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!