C言語でプログラムを終わらせる方法として、exit関数とreturn文がありますが、「どちらを使えばよいのか」「どう違うのか」が分かりにくいと感じる方は多いです。
本記事では、C言語のexit関数の基本から、returnとの違い、実践的な使いどころまで、図解とサンプルコードを交えながら詳しく解説します。
【C言語】exit関数の基本
exit関数とは何か

exit関数は、プログラムを即座に終了させるための標準ライブラリ関数です。
C言語の標準ヘッダstdlib.hで宣言されており、次のようなプロトタイプを持ちます。
/* stdlib.h より(イメージ) */
void exit(int status);
引数のstatusは、終了ステータス(終了コード)と呼ばれ、OSや呼び出し元に「成功か失敗か」を伝えるために使われます。
exit関数を使うための準備
exit関数を使うには#include <stdlib.h>が必要です。
これを忘れると、コンパイル時に警告やエラーが出る可能性があります。
#include <stdio.h>
#include <stdlib.h> /* exitを使うために必要 */
int main(void) {
printf("プログラムを終了します。\n");
/* 終了ステータス0は「正常終了」を表すことが多い */
exit(0);
/* ここには到達しません */
printf("この行は実行されません。\n");
return 0;
}
プログラムを終了します。
このようにexitを呼び出したあとのコードは実行されません。
コンパイラによっては「到達不能コード」として警告されることもあります。
exitとreturnの違いを明確に理解する
基本的な違い(対象とする「終了範囲」)

returnは「いま実行している関数を終了して呼び出し元に戻る」ためのキーワードです。
一方でexitは「プログラム(プロセス)全体を終了する」ための関数です。
returnの動き
#include <stdio.h>
void func(void) {
printf("func: 開始\n");
return; /* 呼び出し元(main)に戻る */
printf("func: この行は実行されません\n");
}
int main(void) {
printf("main: funcを呼び出します\n");
func();
printf("main: funcから戻ってきました\n");
return 0; /* OSに0を返して正常終了 */
}
main: funcを呼び出します
func: 開始
main: funcから戻ってきました
returnはあくまで自分の関数を終えるだけであり、プログラム全体は続行します。
exitの動き
#include <stdio.h>
#include <stdlib.h>
void func(void) {
printf("func: 開始\n");
printf("func: ここで異常を検知したのでプログラムを終了します\n");
exit(1); /* プロセス全体を終了する */
printf("func: この行は実行されません\n");
}
int main(void) {
printf("main: funcを呼び出します\n");
func();
printf("main: この行には到達しません\n");
return 0;
}
main: funcを呼び出します
func: 開始
func: ここで異常を検知したのでプログラムを終了します
exitは現在どの関数にいるかに関わらず、プログラムを即終了させることが分かります。
main関数でのreturnとexitの違い
main関数の末尾でreturn 0;とexit(0);は、多くの環境ではほぼ同じ意味になります。
どちらもOSに0を返し、正常終了を示します。
ただし、次のような違いがあります。
- 記述場所の自由度
- return 0; はmain関数の中でのみ使用
- exit(0); はどの関数からでも呼び出し可能
- 構文か関数か
- returnは言語仕様の一部
- exitは標準ライブラリ関数(ヘッダが必要)
通常の終了ではmain関数の末尾でreturnを使う方が自然ですが、ライブラリ設計や深い階層からの強制終了が必要な場合にはexitが選択されます。
exit関数の終了ステータス(終了コード)
終了コードの意味
exitの引数には整数の終了ステータスを指定します。
一般的な慣習は以下の通りです。
- 0: 正常終了
- 0以外: 異常終了(エラーあり)
プラットフォームによっては、終了コードを上限などの制約付きで扱うことがありますが、0が成功、非0が失敗という方針は多くの環境で共通です。
マクロEXIT_SUCCESSとEXIT_FAILURE
stdlib.hには、終了ステータスとして使えるEXIT_SUCCESSとEXIT_FAILUREというマクロが定義されています。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
FILE *fp = fopen("data.txt", "r");
if (fp == NULL) {
fprintf(stderr, "ファイルを開けませんでした。\n");
/* 異常終了を表すマクロ */
exit(EXIT_FAILURE);
}
printf("ファイルを開くことに成功しました。\n");
fclose(fp);
/* 正常終了を表すマクロ */
exit(EXIT_SUCCESS);
}
(1) data.txtがある場合
ファイルを開くことに成功しました。
(2) data.txtが無い場合
ファイルを開けませんでした。
EXIT_SUCCESS / EXIT_FAILUREを使うことで、環境依存の細かい差異を気にせず終了コードを表現できるため、移植性を重視するコードでは推奨されます。
exitが行う「後始末」とプログラム終了の流れ
exitが内部で行う処理

exitは単に「即終了」ではなく、次のような後始末処理を行います。
- atexitで登録された関数を呼び出す
- 標準入出力ストリームのバッファをフラッシュする
- 開いているストリームをクローズする
- OSに終了コードを返してプロセス終了
この後始末のおかげで、printfの出力が正しく画面やファイルに書き込まれた状態で終了できるようになっています。
atexitと組み合わせた使い方
atexitは、プログラム終了時に自動的に実行される関数を登録するための仕組みです。
#include <stdio.h>
#include <stdlib.h>
void cleanup1(void) {
printf("cleanup1: 終了前の後始末を実行します。\n");
}
void cleanup2(void) {
printf("cleanup2: 追加の後始末を実行します。\n");
}
int main(void) {
/* 終了時に呼ばれる関数を登録(後から登録したものが先に呼ばれる) */
atexit(cleanup1);
atexit(cleanup2);
printf("メイン処理を開始します。\n");
/* 何らかの条件で終了したくなったと仮定 */
printf("異常を検知したため、exitで終了します。\n");
exit(EXIT_FAILURE);
/* ここには到達しません */
return 0;
}
メイン処理を開始します。
異常を検知したため、exitで終了します。
cleanup2: 追加の後始末を実行します。
cleanup1: 終了前の後始末を実行します。
exitを呼び出しても、atexitで登録したクリーンアップ処理は必ず実行されることが確認できます。
実践: エラー処理でのexitの使い方
エラー発生時にメッセージを出して終了する
典型的な使い方として、エラー発生時にメッセージを表示し、終了コードを返してプログラムを終了する方法があります。
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(stderr, "使い方: %s 入力ファイル名\n", argv[0]);
/* コマンドライン引数エラーで終了 */
exit(EXIT_FAILURE);
}
FILE *fp = fopen(argv[1], "r");
if (fp == NULL) {
fprintf(stderr, "ファイル %s を開けませんでした。\n", argv[1]);
/* ファイルオープンエラーで終了 */
exit(EXIT_FAILURE);
}
printf("ファイル %s を開くことに成功しました。\n", argv[1]);
/* ファイル処理... */
fclose(fp);
exit(EXIT_SUCCESS);
}
(1) 引数なしで実行
使い方: ./a.out 入力ファイル名
(2) 存在しないファイル名を指定
ファイル hoge.txt を開けませんでした。
(3) 存在するファイルを指定
ファイル data.txt を開くことに成功しました。
このようにエラーの種類に応じて適切なメッセージと終了ステータスを返すことで、呼び出し側(シェルスクリプトなど)が結果を判断しやすくなります。
深い関数の中から一気に終了したい場合
ネストの深い関数の中で致命的なエラーが起きたとき、すべての呼び出し元にエラーコードをreturnしながら戻るのは煩雑です。
そのようなときにexitは便利です。
#include <stdio.h>
#include <stdlib.h>
void deep_func3(void) {
printf("deep_func3: 致命的なエラーが発生しました。\n");
/* ここでプログラム全体を終了 */
exit(EXIT_FAILURE);
}
void deep_func2(void) {
printf("deep_func2: deep_func3を呼び出します。\n");
deep_func3();
printf("deep_func2: ここには到達しません。\n");
}
void deep_func1(void) {
printf("deep_func1: deep_func2を呼び出します。\n");
deep_func2();
printf("deep_func1: ここには到達しません。\n");
}
int main(void) {
printf("main: deep_func1を呼び出します。\n");
deep_func1();
printf("main: ここには到達しません。\n");
return 0;
}
main: deep_func1を呼び出します。
deep_func1: deep_func2を呼び出します。
deep_func2: deep_func3を呼び出します。
deep_func3: 致命的なエラーが発生しました。
どの階層からでも一気に終了できるのがexitの強みですが、多用しすぎるとコードの見通しが悪くなるため、設計方針に合わせて慎重に使うことが大切です。
exitを使う時の注意点とアンチパターン
ローカル変数のデストラクションに注意(特にC++)
C言語自体にはコンストラクタ/デストラクタはありませんが、C++やCとC++が混在する環境ではexitでは自動変数(ローカルオブジェクト)のデストラクタが呼ばれない点に注意が必要です。
C言語の文脈でも、「関数を抜けたときに片付ける」つもりだったリソースが片付かない可能性があります。
ライブラリコードからのexitは慎重に
ライブラリ関数の中からexitを呼び出すと、それを利用しているアプリケーション側も巻き込んで強制終了してしまいます。
ライブラリではreturnでエラーコードを返し、アプリケーション側でexitするかどうかを判断させる設計が一般的です。
exitとabortの違い
abortは異常終了(アボート)のための関数で、exitとは次の点が異なります。
- atexitで登録された関数は呼び出されない
- バッファフラッシュなど、通常の後始末を行わない実装もある
- デバッグ用にコアダンプを生成する環境もある
「後始末をせずに即座にクラッシュさせたい」場合にabortが使われ、通常のエラー終了にはexitが使われます。
まとめ
exit関数は、プログラム全体を即座に終了させるための強力な手段であり、エラー処理やコマンドラインツールの終了ステータスの返却などでよく使われます。
一方、returnはあくまで「今の関数を終える」ための構文であり、特にmain関数の末尾ではreturn 0;が自然な書き方です。
通常の終了や関数の終了にはreturn、致命的エラーなどでプログラム全体を止めたいときにexitという使い分けを基本にしつつ、EXIT_SUCCESS / EXIT_FAILURE、atexitによる後始末なども組み合わせて、安全で分かりやすい終了処理を設計していきましょう。
