プログラム実行中に取り返しのつかないエラーが起きた場合、処理を即座に打ち切って異常終了させたいことがあります。
C言語のabortはそのための関数です。
本記事では、abortの基本、exitとの違い、使い方、注意点、使うべき場面と避けるべき場面を、C言語初心者の方にもわかりやすく解説します。
異常終了(abort)とは
異常終了(abort)の意味
abortは、プログラムを即座に異常終了させるための関数です。
C言語標準ではabortを呼ぶとSIGABRTシグナルを発生させ、プログラムは決して通常の手続きでは戻りません。
多くのOSではコアダンプを生成し、デバッガで原因調査ができるようになります。
abortは「復旧不能の重大エラーを検知したときに、安全のために即座に止める最終手段」です。
補足
- C標準では
abortはvoid abort(void)で宣言され、戻りません。 - SIGABRTをハンドルしても、ハンドラから復帰すると最終的に異常終了します(実装依存の細部はありますが、継続実行は想定外です)。
- POSIX環境ではデフォルトでコアダンプを出すことが多いです。
exitとの違い
exitは「正常な終了手続きを踏んで」終了する関数です。
一方abortは「異常終了」で、後片付けをスキップします。
違いを整理します。
| 項目 | exit | abort |
|---|---|---|
| 目的 | 正常終了 | 異常終了 |
| atexitハンドラ | 呼ばれる | 呼ばれない |
| ストリームのflush | 原則行われる | 保証なし |
| シグナル | 送出しない | SIGABRTを送出 |
| 終了ステータス | 指定値(EXIT_SUCCESS/EXIT_FAILUREなど) | 実装依存(一定でない) |
| コアダンプ | 通常なし | 環境次第で出る |
「後片付けをして落ちる」ならexit、「とにかく止める」ならabortと覚えると理解しやすいです。
abortの使い方
ヘッダファイル(stdlib.h)をインクルード
abortは<stdlib.h>で宣言されています。
使う前に必ずインクルードします。
#include <stdlib.h> // abortを使うのに必要
関数の宣言と呼び出し
宣言は次の通りです。
引数はなく、戻り値もありません。
// C標準での宣言
void abort(void);
呼び出すと制御は戻らないため、この後に書いたコードは到達不能になります。
// 例: 到達不能であることを明示するコメントを付ける
abort(); // ここから先には進みません
/* 以降は実行されません */
最小のサンプル
もっとも小さな使用例です。
メッセージを1つ表示した後、異常終了します。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
puts("プログラム開始");
// 致命的なエラーを検出したと想定して異常終了する
abort(); // 以降には到達しません
puts("この行は実行されません"); // 到達不能
return 0; // 到達不能
}
実行結果の一例(Linuxの場合。環境により異なります)
プログラム開始
Aborted (core dumped)
「Aborted…」の行はシェルなどの実行環境が表示するもので、プログラム自身が出力したものではありません。
よくあるミス
初心者の方が陥りやすい点を、理由と対策付きでまとめます。
- 正常系のエラー処理にabortを使う
利用者にとっては「ただ落ちる」ため不親切です。exit(EXIT_FAILURE)やエラーコードの返却で丁寧に終了させます。 - エラーメッセージを出さずにabortする
原因調査が困難になります。fprintf(stderr, "...")で要因を出し、fflush(stderr)の後でabortしましょう。 - atexitやファイルcloseが実行されると思っている
abortでは呼ばれません。必要な最低限のflushを自前で行ってからabortします。 - 終了コードを固定値だと思っている
実装依存です。終了コードに依存した外部監視を書かないようにします。 - catchして続行できると思っている
Cでは例外はありません。シグナルハンドラで受けても通常継続はできない前提です。
abortは「最後の切り札」。
むやみに使わず、使うときは情報を残して落とすことが肝心です。
abortの注意点
atexitは呼ばれない
atexitで登録した終了時処理はabortでは呼ばれません。
比較用に、同じコードをabortとexitで実行してみます。
#include <stdio.h>
#include <stdlib.h>
static void on_exit_handler(void) {
puts("atexitハンドラが呼ばれました");
}
int main(void) {
if (atexit(on_exit_handler) != 0) {
fputs("atexitの登録に失敗\n", stderr);
return 1;
}
puts("abortの直前です");
abort(); // ここで異常終了するため、on_exit_handlerは呼ばれない
// 到達しません
return 0;
}
abortの直前です
Aborted (core dumped)
同等の処理をexitで行うと以下のようになります。
#include <stdio.h>
#include <stdlib.h>
static void on_exit_handler(void) {
puts("atexitハンドラが呼ばれました");
}
int main(void) {
atexit(on_exit_handler);
puts("exitの直前です");
exit(EXIT_FAILURE); // 正常な終了手続き(異常終了コードだが手順は正常)
}
exitの直前です
atexitハンドラが呼ばれました
後片付けを必ず実行したいならabortではなくexitを使います。
出力が消えることがある
stdout(標準出力)はバッファリングされます。
abortはバッファのflushを保証しないため、改行なしの出力などが消えることがあります。
悪い例(出力が消えることがある)
#include <stdio.h>
#include <stdlib.h>
int main(void) {
printf("重要なメッセージ(改行なし)"); // バッファに溜まる可能性がある
abort(); // flushされずに終了 -> 表示されないことがある
}
改善例(stderrへの出力と明示的flush)
#include <stdio.h>
#include <stdlib.h>
int main(void) {
fprintf(stderr, "致命的エラー: 何らかの要因が発生しました\n");
fflush(stderr); // できるだけ確実に出力してから落とす
abort();
}
stderrは多くの環境で無バッファまたは行バッファですが、仕様上は「必ず無バッファ」とは限りません。
確実性を上げるにはfflush(stderr)を併用します。
ファイルやリソースの後片付けはされない
開いているファイル、ソケット、動的メモリなどの後片付けはabortでは行われません。
OSはプロセス終了時に多くの資源を回収しますが、標準I/Oのユーザ空間バッファに残ったデータは失われます。
失われる例
#include <stdio.h>
#include <stdlib.h>
int main(void) {
FILE *fp = fopen("example.txt", "w");
if (!fp) {
perror("fopen");
return 1;
}
fputs("この行はflushせずに書いています", fp); // まだユーザ空間バッファ
// fcloseやfflushをしないまま異常終了
abort();
// 到達しません
// fclose(fp);
}
この場合、example.txtが空のままだったり、途中までしか書かれていない場合があります。
最低限、ログなど残したいものはfflush(fp)してからabortしてください。
POSIX環境ならfsyncでディスクへの反映まで保証できますが、標準Cの範囲外です。
致命的エラー時に「完全な後片付け」を期待してはいけません。
必要最小限の情報を吐いて、安全に止めるのが基本方針です。
終了コードは一定でないことがある
abortの終了コードは実装依存です。
例として以下のような挙動が観測されます。
| 環境の例 | 挙動の例 |
|---|---|
| Linux(glibc) | SIGABRT(番号6)で終了。シェルの終了ステータスは概ね128+6=134、メッセージ”Aborted (core dumped)”表示のことあり |
| macOS | 類似の挙動。コアダンプの扱いは設定次第 |
| Windows(MSVC) | 終了コード3など。ダイアログや診断が出る場合あり |
スクリプトや監視側で「終了コードがXならabort」と決め打ちしないでください。
abortを使う場面と避ける場面
復旧不能の致命的エラーで使う
次のような場面ではabortが有効です。
- 内部不変条件の破れ(到達不能な分岐に到達、データ構造の重大破損)
- セキュリティ的に危険な状態(検証不能な入力で一貫性が失われた等)
- デバッグ時にコアダンプを取得したい場合
assertの失敗がabortに繋がるのは「プログラムの前提が壊れたら直ちに止める」ためです。
ユーザー向けアプリでは極力避ける
GUIアプリや一般ユーザーが使うCLIでは、可能な限り丁寧なエラー処理を心がけます。
設定ファイルの欠如やネットワーク障害など、外部要因で回復可能なエラーではabortではなく、メッセージ表示やexit(EXIT_FAILURE)、再試行などを選びます。
「アプリが突然落ちる」体験は避け、ユーザーに状況と対処を知らせるのが原則です。
エラーメッセージを出してからabortする
やむを得ずabortする場合でも、必ず情報を残してから落とします。
最小限の実装例です。
#include <stdio.h>
#include <stdlib.h>
// 致命的エラーを報告して直ちに異常終了する簡易マクロ
#define FATAL(msg) do { \
fprintf(stderr, "FATAL %s:%d %s: %s\n", __FILE__, __LINE__, __func__, (msg)); \
fflush(stderr); \
abort(); \
} while (0)
int main(void) {
// 例: 想定外の状態を検知
int condition = 0;
if (!condition) {
FATAL("想定外の状態を検出しました");
}
// 到達しません
return 0;
}
FATAL sample.c:8 main: 想定外の状態を検出しました
Aborted (core dumped)
エラーログはstderrへ、flushしてからabort、が調査効率を大きく上げます。
まとめ
abortは「復旧不能な致命的エラーが起きたら、情報を残して即座に停止する」ための関数です。
exitとの違いは、atexitが呼ばれない、ストリームのflushが保証されない、終了コードが一定でないなどにあります。
初心者の方は次の要点を押さえてください。
- 通常のエラー処理では
abortを使わず、exitやエラー戻り値で丁寧に処理すること。 abort前にfprintf(stderr,...)で要因を出力し、fflushしてから落とすこと。- ファイルの内容やログが消える可能性に注意し、必要なら
fflush(環境に応じてfsync)を行うこと。 - 終了コードには依存しないこと。
「むやみに使わない」「使うなら情報を確実に残す」。
これがabortとの正しい付き合い方です。
