科学

ランタイムエラーとは?直し方や原因も解説【C++など】

当サイトでは記事内に広告を含みます

ランタイムエラーとは何か、その直し方や原因がよくわからないという方は多いのではないでしょうか。

「プログラムを実行したら突然エラーが出て止まってしまった」「コンパイルは通るのになぜかエラーになる」という経験は、C++をはじめとしたプログラミングを学ぶ多くの方が直面する壁のひとつです。

この記事では、ランタイムエラーとは何か、その原因・種類・直し方について、C++を中心としながら他の言語にも共通する観点からわかりやすく解説していきます。エラーメッセージの読み方からデバッグの実践的な手順まで丁寧にお伝えしますので、ぜひ最後まで読み進めてみてください。

目次

ランタイムエラーとは?結論|プログラム実行中に発生するエラーのこと!

それではまず、ランタイムエラーとは何かという定義と全体像について解説していきます。

ランタイムエラー(Runtime Error)とは、プログラムが実行(ランタイム)されている最中に発生するエラーのことです。コンパイルの段階では問題なく通過するのに、実際にプログラムを動かしたときに突然発生するのが特徴です。

エラーの3種類と違い
コンパイルエラー(Compile Error)
→ コードを機械語に変換する段階で発生
→ 文法ミス・型の不一致などで検出される
→ プログラムの実行前に気づける
リンクエラー(Link Error)
→ 複数のファイルをまとめる段階で発生
→ 関数・変数の定義が見つからないときなどに発生
ランタイムエラー(Runtime Error)
→ プログラムが実際に動いている最中に発生
→ コンパイルは成功するが、実行すると突然停止する
→ 発見・修正が最も難しいエラー種別
ランタイムエラーは「コンパイルを通過したのに動かない」という厄介な性質を持ちます。

ランタイムエラーが厄介な理由は、コードの文法的には正しくても、実行中の状態や入力値によって初めて問題が露わになる点にあります。たとえば「0で割る処理」は文法的には問題ありませんが、実行すると即座にエラーになります。

次の見出しからは、ランタイムエラーの具体的な種類と原因を詳しく見ていきましょう。

ランタイムエラーの主な種類と原因

続いては、ランタイムエラーの主な種類とそれぞれの原因を確認していきます。

ランタイムエラーにはさまざまな種類があり、発生する原因も異なります。どの種類のエラーかを把握することが、正しい直し方を選ぶための第一歩です。

セグメンテーションフォルト(Segmentation Fault)

C++を含むC系言語で最も頻繁に遭遇するランタイムエラーのひとつが、「セグメンテーションフォルト(Segfault)」です。アクセスしてはいけないメモリ領域に触れようとしたときに発生します。

セグメンテーションフォルトが起きるコードの例(C++)
int arr[5] = {1, 2, 3, 4, 5};
cout << arr[10]; // 範囲外アクセス → Segfault
int ptr = nullptr;
cout << ptr; // NULLポインタの参照 → Segfault
主な原因
・配列の範囲外アクセス(インデックスが配列サイズを超える)
・NULLポインタ(未初期化ポインタ)の参照
・解放済みのメモリへのアクセス(ダングリングポインタ)
・スタックオーバーフロー(再帰が深すぎてスタックが溢れる)

セグメンテーションフォルトはプログラムを即座に強制終了させます。「コアダンプ(core dumped)」というメッセージが表示された場合も、セグフォルトが原因であることが多いです。

ゼロ除算エラーとオーバーフロー

数値計算に関連するランタイムエラーとして、ゼロ除算とオーバーフローがあります。

ゼロ除算エラーの例(C++)
int a = 10;
int b = 0;
int result = a / b; // ゼロ除算 → ランタイムエラー
整数のゼロ除算は未定義動作(UB)として即クラッシュ
浮動小数点のゼロ除算は inf(無限大)や nan になる場合もある
オーバーフローの例
int maxVal = INT_MAX; // 2147483647(32ビット整数の最大値)
maxVal ++; // オーバーフロー → 未定義動作
符号なし整数のオーバーフローはラップアラウンド(0に戻る)
符号あり整数のオーバーフローは未定義動作(UB)

特にC++では「未定義動作(Undefined Behavior・UB)」という概念が重要です。ゼロ除算や符号あり整数のオーバーフローは未定義動作とされており、コンパイラによって予測不能な動作を引き起こす可能性があります。

その他の代表的なランタイムエラー

セグフォルトとゼロ除算以外にも、よく遭遇するランタイムエラーがあります。

エラーの種類 主な原因 発生しやすい言語
スタックオーバーフロー 再帰の終了条件がない・深すぎる再帰呼び出し C++・Java・Python等
メモリリーク 確保したメモリを解放し忘れる C・C++(手動メモリ管理言語)
型変換エラー 互換性のない型への変換・キャスト失敗 Java・C#・Python等
ファイルアクセスエラー 存在しないファイルを開こうとする 全般
無限ループ ループの終了条件が満たされない 全般
例外の未処理 throw した例外が catch されない C++・Java・C#等

スタックオーバーフローは再帰関数に終了条件(ベースケース)を書き忘れたときに特によく発生します。

競技プログラミングでも頻出のエラーのひとつで、「TLE(Time Limit Exceeded)」と組み合わせて発生することも多くなっています。

C++でのランタイムエラーの直し方と対処法

続いては、C++を中心としたランタイムエラーの具体的な直し方と対処法を確認していきます。

ランタイムエラーを修正するには、まずエラーの種類を特定し、次に問題のある箇所を見つけ、最後にコードを修正するという3つのステップが基本です。エラーメッセージやデバッガを最大限に活用することが、効率的な修正への近道です。

エラーメッセージとスタックトレースの読み方

ランタイムエラーが発生すると、多くの場合エラーメッセージやスタックトレースが出力されます。これを正しく読むことが修正の出発点です。

スタックトレースの例(C++・gdb使用時)
Program received signal SIGSEGV, Segmentation fault.
0x0000000000401176 in printValue (ptr=0x0) at main.cpp:8
8 cout << ptr << endl;
(gdb) bt
0 0x0000000000401176 in printValue (ptr=0x0) at main.cpp:8
1 0x00000000004011a2 in main () at main.cpp:15
読み方
・SIGSEGV → セグメンテーションフォルトが発生したシグナル
・at main.cpp:8 → main.cppの8行目でエラーが発生
・ptr=0x0 → ポインタがNULL(0x0)だった
・bt(バックトレース)→ どの関数から呼ばれたかの履歴
→ main関数の15行目から printValue が呼ばれた

スタックトレースは「一番上(最初に表示される行)が実際にエラーが起きた場所、下に行くほど呼び出し元を示します」という構造になっています。エラー行と呼び出し元の両方を確認することで、問題の全体像を把握できます。

代表的なランタイムエラーの修正方法

エラーの種類別に、具体的な修正アプローチを確認しましょう。

セグメンテーションフォルトの修正
問題のあるコード
int arr[5];
cout << arr[10]; // 範囲外
修正後
int arr[5];
if (index < 5) { // 境界チェックを追加
cout << arr[index];
}
NULLポインタの修正
int ptr = nullptr;
if (ptr != nullptr) { // NULL チェックを追加
cout << ptr;
}
ゼロ除算の修正
if (b != 0) { // ゼロ除算を防ぐ条件チェック
result = a / b;
} else {
cout << “Error:Division by zero” << endl;
}
スタックオーバーフローの修正
// 再帰関数にベースケースを必ず追加する
int factorial(int n) {
if (n <= 1) return 1; // ベースケース(終了条件)
return n factorial(n – 1);
}

修正の共通原則は「問題が起きる前に条件チェックを入れる」ことです。配列アクセスの前にインデックス範囲チェック、ポインタ使用前のNULLチェック、除算前のゼロチェック、再帰関数のベースケース確認が基本の4セットです。

デバッガとツールを使ったエラーの発見方法

手動でのコード確認に加え、専用のツールを活用するとランタイムエラーの発見が格段に効率化されます。

ツール名 用途 特徴
gdb(GNU Debugger) C++のデバッグ全般 ブレークポイント設定・スタックトレース確認・変数の値確認
Valgrind メモリエラーの検出 メモリリーク・範囲外アクセスを詳細に報告
AddressSanitizer(ASan) メモリエラーの高速検出 コンパイル時に-fsanitize=addressを付けるだけで使える
Visual Studio デバッガ Windows環境でのデバッグ GUIで直感的にブレークポイントや変数確認ができる
cout デバッグ 簡易デバッグ 怪しい箇所にcoutを追加して値を確認する方法
C++でランタイムエラーをデバッグするなら、まず「AddressSanitizer(ASan)」を試してみることをおすすめします。コンパイル時に「-fsanitize=address -g」オプションを追加するだけで、メモリ関連のランタイムエラーを詳細なメッセージとともに報告してくれます。従来のデバッグに比べて圧倒的に素早く問題箇所を特定できます。

デバッガを使う習慣をつけることで、「printデバッグ(coutを並べる方法)」に頼らず、より効率的にエラーの根本原因にたどり着けるようになります。gdbやIDEのデバッガは学習コストがありますが、長期的には大きな時間節約につながります。

ランタイムエラーを防ぐためのコーディング習慣と予防策

続いては、ランタイムエラーをそもそも発生させないためのコーディング習慣と予防策を確認していきます。

エラーが出てから修正するよりも、最初からエラーが起きにくいコードを書く習慣を身につけることが、長期的に最も効果的な対策です。

境界チェックと安全なコーディングパターン

C++でランタイムエラーを防ぐための基本的なコーディングパターンを覚えておきましょう。

安全なコーディングパターン集
配列アクセスには at() を使う(範囲外で例外を投げる)
vector v = {1, 2, 3};
v.at(5); // std::out_of_range 例外が発生(クラッシュより安全)
スマートポインタを使う(手動delete不要)
unique_ptr ptr = make_unique(42);
// メモリは自動で解放される
RAII(Resource Acquisition Is Initialization)パターン
リソース(メモリ・ファイル等)の確保と解放を
コンストラクタとデストラクタで管理する設計
assert を使った事前条件チェック
include
void divide(int a, int b) {
assert(b != 0 && “b must not be zero”);
return a / b;
}

スマートポインタ(unique_ptr・shared_ptr)の活用は、C++でのメモリ関連ランタイムエラーを劇的に減らす最も効果的な方法

のひとつです。C++11以降では生ポインタよりもスマートポインタを優先的に使うことが推奨されています。

例外処理を使ったエラーハンドリング

C++では try-catch 構文を使った例外処理がランタイムエラー対策の重要な手段です。

C++ の例外処理の基本
try {
// エラーが発生するかもしれない処理
int result = divide(10, 0);
cout << result << endl;
} catch (const std::runtime_error& e) {
// ランタイムエラーをキャッチ
cerr << “Runtime error:” << e.what() << endl;
} catch (const std::exception& e) {
// その他の標準例外をキャッチ
cerr << “Exception:” << e.what() << endl;
} catch (…) {
// すべての例外をキャッチ
cerr << “Unknown error occurred” << endl;
}
例外を投げる側
void divide(int a, int b) {
if (b == 0) {
throw std::runtime_error(“Division by zero”);
}
return a / b;
}

例外処理を使うことで、エラーが発生してもプログラムが即座に終了せず、適切なエラーメッセージを表示したり代替処理を実行したりすることができます。特に入力値の検証が難しい場合や外部リソースを扱う場合に有効です。

競技プログラミングでのランタイムエラー対策

競技プログラミング(AtCoder・CodeforcesなどのOJ)では、ランタイムエラー(RE)が特有の形で出題に関わります。

競技プログラミングでのRE(ランタイムエラー)対策
よくある原因と対策
配列サイズの不足
→ 問題の制約をよく読み、最大サイズより少し大きく確保する
→ vector を使えばサイズの動的変更が可能
再帰の深さ
→ 深い再帰はスタックオーバーフローを引き起こす
→ 再帰をループに書き直す・スタックサイズを増やす設定を使う
整数オーバーフロー
→ long long(64ビット整数)を使う
→ 積の計算前にオーバーフローしないか確認する
0番目からではなく1番目からのインデックス
→ 配列を1-indexedで使う場合はサイズを+1する
→ int arr[N+1]; のように余裕を持たせる

競技プログラミングでのランタイムエラーは「配列の範囲外アクセス」と「再帰の深さによるスタックオーバーフロー」が原因の大半を占めます。

コードを書いた後に配列サイズと再帰深度を必ず確認する習慣をつけると、REの発生率を大幅に下げることができます。

まとめ

この記事では、ランタイムエラーとは何か、その原因・種類・直し方についてC++を中心に解説しました。

ランタイムエラーとは、プログラムが実行されている最中に発生するエラーのことで、コンパイルは成功するが実行時に突然停止するのが特徴です。コンパイルエラーやリンクエラーとは発生タイミングが異なるため、より発見・修正が難しい種類のエラーといえます。

主な種類はセグメンテーションフォルト・ゼロ除算・スタックオーバーフロー・メモリリーク・未処理例外などです。直し方の基本は「問題が起きる前に条件チェックを追加すること」で、NULLチェック・範囲チェック・ゼロチェック・再帰のベースケース確認が4大対策です。

デバッグにはgdb・Valgrind・AddressSanitizerなどのツールが有効で、特にASanはコンパイルオプション追加だけで使える手軽さが魅力です。さらに予防策として、スマートポインタの活用・例外処理の実装・事前条件のassert確認を習慣にすることで、ランタイムエラーの発生率を大幅に下げることができます。

この記事を参考に、ランタイムエラーへの対処力を高めていただければ幸いです。