ファイルを削除する場面は、ログの整理や一時ファイルの掃除など、C言語での開発では意外と頻繁に発生します。
その中心となる関数がremove関数です。
本記事では、C言語のremove関数の基本仕様から使い方、ありがちな失敗例と対処法、安全な削除パターンまでを、サンプルコードと図解を交えながら詳しく解説します。
remove関数とは
C言語のremove関数の基本仕様

remove関数は、指定したファイルを削除するための標準Cライブラリ関数です。
ファイル名(パス)を文字列で指定し、成功すればそのファイルはファイルシステムから削除されます。
仕様のポイントを整理すると、次のようになります。
- 対象は「ファイル」であり、ディレクトリ削除には通常は使えません(環境依存)。
- 引数には
char *またはconst char *形式のパス文字列を渡します。 - 削除の成功・失敗は戻り値で判定します。
- 失敗した場合、
errnoがセットされ、原因を調べることができます。
なお、C言語標準の観点では、remove関数は「リンク(ディレクトリエントリ)を削除する」ものと定義されますが、一般的なOSではユーザーから見て「ファイルが消える」動きと考えて差し支えありません。
remove関数の宣言とヘッダファイル
remove関数は標準ライブラリ関数なので、特別なヘッダではなくstdio.hに宣言されています。

標準的な宣言は次のようになっています。
int remove(const char *filename);
C言語でremove関数を使うときは、必ずソースコードの先頭付近で#include <stdio.h>を記述します。
これを忘れると、コンパイラによっては暗黙の宣言扱いになったり、警告・エラーが発生したりするので注意が必要です。
remove関数の使い方
remove関数の書式と戻り値

remove関数の基本的な書式は次のとおりです。
int remove(const char *filename);
戻り値の意味は非常に重要なので、しっかり押さえてください。
- 0 … ファイル削除に成功
- 0以外 … ファイル削除に失敗(エラー発生)
失敗した場合にはerrnoが適切なエラーコードに設定されます。
これを利用して、なぜ失敗したのかを調査できます。
たとえばENOENTなら「そんなファイルやディレクトリは存在しない」、EACCESなら「アクセス権限がない」などです(詳細は環境依存です)。
ファイル削除の簡単なコード例
まずは、単純なファイル削除の例を示します。
#include <stdio.h> // remove関数の宣言があるヘッダ
#include <stdlib.h> // exit, EXIT_FAILURE などを使う場合に必要
int main(void)
{
// 削除したいファイル名(同じディレクトリにある "sample.txt" を削除する例)
const char *filename = "sample.txt";
// ファイル削除を実行
// removeは成功で0、失敗で0以外を返します
if (remove(filename) == 0) {
printf("ファイル \"%s\" を削除しました。\n", filename);
} else {
// 今は簡易的に「削除失敗」とだけ出力しています
printf("ファイル \"%s\" の削除に失敗しました。\n", filename);
}
return 0;
}
上記プログラムを実行する前に、実行ファイルと同じディレクトリにsample.txtを用意しておくと、削除の様子を確認できます。
(例) sample.txt が存在し、削除に成功した場合の出力例:
ファイル "sample.txt" を削除しました。
sample.txt が存在しない場合は、次のような出力になります。
ファイル "sample.txt" の削除に失敗しました。
この時点では失敗理由までは表示していないため、のちほどerrnoやperrorを使って詳しく確認する方法を解説します。
戻り値で削除成功・失敗を判定する方法

remove関数の結果は必ず戻り値でチェックするべきです。
戻り値を無視してしまうと、削除に失敗しているのに気が付かず、後続処理が誤った前提で進んでしまう危険があります。
以下は、戻り値を厳密にチェックし、失敗したときにはperrorでエラーの詳細を表示する例です。
#include <stdio.h>
#include <errno.h> // errnoを使う場合に必要
int main(void)
{
const char *filename = "data.txt";
// 削除を試みる
if (remove(filename) != 0) {
// removeに失敗した場合
// perrorは、直近のライブラリ関数エラーに対応するメッセージを標準エラー出力へ表示します
perror("remove failed");
// errnoの値を直接表示することもできます
printf("errno = %d\n", errno);
} else {
// removeに成功した場合
printf("ファイル \"%s\" を正常に削除しました。\n", filename);
}
return 0;
}
実行例(ファイルが存在しない場合の典型的な出力例。実際のメッセージはOSやロケールにより異なります):
remove failed: No such file or directory
errno = 2
このように、戻り値を確認し、失敗時にはerrnoやperrorで原因を調べるのが、実践的な使い方です。
remove関数が失敗する主な原因
ファイルが存在しない場合のエラーと対処法

最もよくある失敗原因が、指定したファイルが存在しないというケースです。
パスの入力ミスや、既に別の処理で削除されている場合などが該当します。
この場合、典型的にはerrnoにENOENTがセットされます。
対処法としては、次のような方針が考えられます。
- 削除前にファイルの存在を確認する。
- 指定パスのスペルミスや相対パス/絶対パスの誤りを見直す。
- 「存在していなくても問題ない」仕様であれば、エラーを無視するロジックを明示的に書く。
存在チェックについては、後半でfopenやaccessなどを使った具体例を紹介します。
ファイルがオープン中(使用中)の場合のエラーと対処法

ファイルがすでにプログラム内でオープンされている状態でremoveを呼び出すと、挙動はOSによって異なります。
- Windows系: 通常は「使用中のファイルは削除できない」ためエラーになります。
- Unix系(Linux, macOSなど): ディレクトリエントリだけを削除し、オープン中のプロセス内では引き続きアクセス可能という動作が一般的です。ファイルの中身は、全てのプロセスがクローズした時点で物理的に解放されます。
ポータブルなプログラムを書くためには、「自分がオープンしたファイルは必ずクローズしてからremoveする」というルールを徹底することが重要です。
#include <stdio.h>
int main(void)
{
const char *filename = "temp.log";
// 1. ファイルを開く
FILE *fp = fopen(filename, "w");
if (fp == NULL) {
perror("fopen failed");
return 1;
}
fprintf(fp, "ログのテスト出力です。\n");
// 2. クローズを忘れると、環境によってはremoveに失敗する
if (fclose(fp) != 0) {
perror("fclose failed");
return 1;
}
// 3. クローズした後で削除する
if (remove(filename) != 0) {
perror("remove failed");
return 1;
}
printf("ファイル \"%s\" を正常に削除しました。\n", filename);
return 0;
}
上記のようにfcloseを確実に呼び、エラーもチェックしておくと、原因不明の削除失敗を避けやすくなります。
アクセス権限不足でremoveに失敗するケース

ファイルが書き込み権限のない場所にある、あるいはファイル自体のパーミッションが制限されている場合、removeはエラーになります。
このときの典型的なerrnoはEACCESです。
例として、Unix系環境の/var/logなど管理者権限が必要なディレクトリにあるファイルは、一般ユーザーでは削除できません。
対処としては次のような選択肢があります。
- 実行ユーザーに適切な権限を付与する(所有者変更、グループ設定、パーミッション変更など)。
- 削除したいファイルを、一般ユーザーが書き込み可能な領域(ホームディレクトリなど)に配置するよう設計を見直す。
- 必要に応じて管理者権限で実行する(セキュリティを十分に検討した上で)。
プログラム側では、単に「失敗した」で終わらせず、perrorでPermission deniedといったメッセージが出ていないかを確認すると原因を特定しやすくなります。
パス指定の誤り(相対パス・絶対パス)による失敗例

パス指定の誤りも、remove失敗の大きな原因です。
特に相対パスと絶対パスの違いや、カレントディレクトリの位置を勘違いしているケースが多く見られます。
- 相対パス: 現在の作業ディレクトリ(カレントディレクトリ)からの相対的な位置を表すパス。
- 絶対パス: ファイルシステムのルートから完全に指定するパス。
プログラムを実行している位置が想定と違うと、"data/file.txt"が指す場所も変わり、その結果「ファイルが存在しない」と判断されてしまいます。
デバッグの際は、次のような方法で問題を切り分けられます。
- 一時的に絶対パスを使って削除を試す。
- 実行時に
getcwd関数(Unix系ならpwdコマンド)などで、カレントディレクトリを確認する。 - ログ出力などで実際にremoveに渡しているパス文字列を表示する。
たとえば、デバッグ用に次のようなログ出力を仕込むと役に立ちます。
#include <stdio.h>
int safe_remove(const char *filename)
{
printf("[DEBUG] remove target: \"%s\"\n", filename);
if (remove(filename) != 0) {
perror("remove failed");
return -1;
}
return 0;
}
ディレクトリをremoveしようとして失敗するケース

標準Cのremove関数はディレクトリにも使えることになっていますが、実際のOSやランタイムでは「ファイル専用的な扱い」になっていることが多く、ディレクトリを指定するとエラーになるケースが多数派です。
代表的な例として、POSIX系ではディレクトリを削除するためにrmdirという別の関数が用意されています。
Windows APIでもRemoveDirectoryなど、別の関数を使います。
特に注意すべき点は、中身のあるディレクトリは一度に削除できないことが多いという点です。
一般的には、次のような手順が必要です。
- ディレクトリの中身を列挙する。
- 中にあるファイルを
removeなどで削除する。 - 空になったディレクトリを
rmdirや対応するAPIで削除する。
remove関数は「単一ファイル削除専用」と考えておくとトラブルを避けやすくなります。
Windows特有のロックやパス制限による失敗例

Windowsでは、removeが失敗する原因として、OS特有の事情がいくつか存在します。
1つ目はファイルロックです。
Windowsでは、あるプロセスがファイルを開く際に共有モードを指定するため、他プロセスが共有を許可しない形でオープンしていると削除できない場合があります。
また、自分のプロセスでオープンしたファイルをクローズし忘れている場合も同様です。
2つ目はパス長制限(旧来のMAX_PATH問題)です。
従来のWindows APIでは260文字程度の制限があり、これを超えるパスを扱うには特殊な書き方(例: \?\C:\very\long\path...)が必要でした。
最近のWindowsでは設定やAPIによって緩和されているものの、古い環境では依然として問題になります。
3つ目として、ウイルス対策ソフトやバックアップソフトによる一時的なロックも挙げられます。
この場合、少し時間をおいて再度削除を試みると成功することがあります。
Windows向けのプログラムでは、これらを考慮して次のような工夫が有効です。
- 削除前に自分のプロセスがファイルを開いていないかを確認する。
- 削除に失敗した場合にリトライ処理を入れる(数百ミリ秒〜数秒の間隔を空けて数回試行するなど)。
- 長いパスを扱う場合はパスの短縮や特殊なプレフィックスの利用を検討する。
remove関数のエラー処理とデバッグ
errnoとperrorでエラー原因を確認する方法

removeが失敗したとき、原因を知るための鍵となるのがerrnoとperrorです。
- errno:
int型のグローバル変数で、直近のライブラリ関数エラーの種類を表すエラー番号が格納されます。 - perror:
errnoの内容に応じて、人間が読める文字列メッセージを標準エラー出力に表示する関数です。
removeの失敗原因を調べる典型的なコードは次のようになります。
#include <stdio.h>
#include <errno.h> // errno定義
#include <string.h> // strerrorを使う場合に必要
int main(void)
{
const char *filename = "not_exists.txt";
if (remove(filename) != 0) {
// perrorを使う方法
perror("remove failed");
// errnoを使って自前でメッセージを構築する方法
printf("errno = %d (%s)\n", errno, strerror(errno));
return 1;
}
printf("ファイル \"%s\" の削除に成功しました。\n", filename);
return 0;
}
remove failed: No such file or directory
errno = 2 (No such file or directory)
perrorは簡単に使えて便利ですが、ログフォーマットを整えたい場合などは、strerror(errno)で文字列を取得した上でprintfなどに渡すと柔軟に扱えます。
削除前にファイルの存在をチェックする方法

removeを呼ぶ前に、そもそもファイルが存在するかどうかを確認しておくと、わかりやすいエラーハンドリングができます。
代表的な存在チェック方法としては次のものがあります。
- fopenで開いてみる: 読み込みモード
"r"で開き、成功したらすぐに閉じる。 - access関数を使う(POSIX): 指定パスに対するアクセス権限を調べる。
- stat関数を使う(POSIX): ファイル情報の取得を試みる。
ポータブルで簡単な方法として、fopenを使った例を示します。
#include <stdio.h>
int file_exists(const char *filename)
{
FILE *fp = fopen(filename, "r");
if (fp == NULL) {
return 0; // 存在しない、または読み取り不可
}
fclose(fp);
return 1; // 存在し、少なくとも読み取り可能
}
int main(void)
{
const char *filename = "check.txt";
if (!file_exists(filename)) {
printf("ファイル \"%s\" は存在しません。削除処理はスキップします。\n", filename);
return 0;
}
if (remove(filename) != 0) {
perror("remove failed");
return 1;
}
printf("ファイル \"%s\" を削除しました。\n", filename);
return 0;
}
このように、存在していなければ「削除は不要」とみなす、といったポリシーをコードに明示しておくと、後から読んでも意図がわかりやすくなります。
削除前にファイルクローズを徹底するポイント

前述の通り、特にWindows環境では、オープン中のファイルは削除できないことが多いです。
そのため、ファイル削除を行う関数を設計する際には、次のようなポイントを押さえておくと安全です。
- 自分が開いたFILEポインタは必ずfcloseする。
- エラーが発生して異常系で関数を抜ける場合も、クローズ処理を忘れないようにする。
- 複数のreturnパスがある関数では、「共通クリーンアップラベル」を使うなどしてクローズ漏れを防ぐ。
共通クリーンアップを使った具体例を示します。
#include <stdio.h>
int process_and_delete(const char *filename)
{
FILE *fp = NULL;
// 1. ファイルを開く
fp = fopen(filename, "r");
if (fp == NULL) {
perror("fopen failed");
return -1;
}
// 2. ファイル処理(ここでは簡略化)
// 実際には読み取りや解析などを行う部分
printf("ファイル \"%s\" の処理を行いました。(ダミー)\n", filename);
// 3. 正常終了時の共通クリーンアップへ
cleanup:
if (fp != NULL) {
// fpが開いていれば必ずクローズ
if (fclose(fp) != 0) {
perror("fclose failed");
return -1;
}
fp = NULL;
}
// 4. クローズ後に削除を行う
if (remove(filename) != 0) {
perror("remove failed");
return -1;
}
return 0;
}
上記では簡略化のためにgotoを使わず直接cleanupラベルに飛ばしていますが、実際にはエラーが発生した各箇所からgoto cleanup;で集約すると、より複雑な処理でもクローズ漏れを防ぎやすくなります。
安全なファイル削除パターン

最後に、実務でおすすめできる「安全なファイル削除パターン」の一例を、関数としてまとめてみます。
#include <stdio.h>
#include <errno.h>
#include <string.h>
// ログ出力用の簡単なラッパ(実際のプロジェクトではロガーを使う想定)
static void log_error(const char *msg)
{
fprintf(stderr, "[ERROR] %s (errno=%d: %s)\n",
msg, errno, strerror(errno));
}
/**
* 安全志向のファイル削除関数。
* - 存在しない場合は「削除不要」とみなして0を返す。
* - 削除失敗時はエラー内容をログに出力し、-1を返す。
*/
int safe_remove_file(const char *filename)
{
// 1. NULLポインタ防止
if (filename == NULL) {
fprintf(stderr, "[ERROR] safe_remove_file: filename is NULL\n");
errno = EINVAL; // 不正な引数
return -1;
}
// 2. 存在チェック(ここでは簡略にfopenを利用)
FILE *fp = fopen(filename, "r");
if (fp == NULL) {
// fopenが失敗した場合、「存在しない」以外の理由もあり得るが、
// ここでは「存在しないなら削除不要」とみなして早期リターン
if (errno == ENOENT) {
fprintf(stderr,
"[INFO] \"%s\" は存在しません。削除処理は不要と判断しました。\n",
filename);
return 0;
} else {
log_error("safe_remove_file: fopen failed");
return -1;
}
}
// 3. 開けたらすぐにクローズしてロックを解放
if (fclose(fp) != 0) {
log_error("safe_remove_file: fclose failed");
return -1;
}
// 4. 実際の削除処理
if (remove(filename) != 0) {
log_error("safe_remove_file: remove failed");
return -1;
}
fprintf(stderr, "[INFO] ファイル \"%s\" を安全に削除しました。\n", filename);
return 0;
}
int main(void)
{
const char *target = "delete_me.txt";
if (safe_remove_file(target) != 0) {
fprintf(stderr, "safe_remove_fileに失敗しました。\n");
return 1;
}
printf("safe_remove_fileは正常終了しました。\n");
return 0;
}
想定される出力例(ファイルが存在しない場合):
[INFO] "delete_me.txt" は存在しません。削除処理は不要と判断しました。
safe_remove_fileは正常終了しました。
想定される出力例(ファイルが存在し、削除できた場合):
[INFO] ファイル "delete_me.txt" を安全に削除しました。
safe_remove_fileは正常終了しました。
このように、存在チェック・クローズ・エラーログを1つの関数にまとめておくと、プロジェクト全体で一貫した安全な削除処理を共有でき、バグの温床を減らすことができます。
まとめ
本記事では、C言語のremove関数について、基本仕様と使い方から、よくある失敗要因(存在しないファイル、オープン中、権限不足、パスミス、ディレクトリ指定、Windows固有の問題)までを整理し、errno・perrorを用いたデバッグ方法や安全な削除パターンを紹介しました。
実務では、戻り値とエラー情報を必ず確認し、削除前のクローズやパス確認を徹底することが重要です。
ここで紹介した考え方とサンプルコードをベースに、自分のプロジェクトに合った堅牢なファイル削除処理を設計してみてください。
