C++プログラムがクラッシュする原因とは?代表的な5つの理由を解説
C++プログラムの異常動作は、しばしばプログラムのクラッシュ(強制終了)につながります。Segmentation fault(セグメンテーション違反)、Aborted、Floating point exception(浮動小数点例外)といったエラーに遭遇した経験がある方も多いのではないでしょうか。
本記事では、C++プログラムがクラッシュする代表的な原因を、サンプルコードとともにわかりやすく解説します。
1. 例外(Exception)
C++における例外とは、プログラムが異常な状態に遭遇した際に発生する反応のことです。例外がtry-catchブロックによって適切に処理されない場合、プログラムはクラッシュします。
以下は、ゼロ除算の例外によってクラッシュするプログラムの例です。
サンプルコード
#include <iostream>
int main(){
int num1=10;
int num2=0;
int quotient=num1/num2;
printf("\n Quotient is: %d",quotient);
return 0;
}実行結果
Floating point exception (core dumped)
この例では、変数num2が0であるにもかかわらず除算を行っているため、浮動小数点例外が発生しプログラムが異常終了します。
2. バッファオーバーフロー
バッファとは、データを一時的に保管するための記憶領域です。プログラムがバッファにデータを書き込む際、そのバッファが保持できるサイズを超えると、余分なデータがバッファの境界外にはみ出します。この結果、隣接するメモリ領域が上書きされてしまいます。
以下のプログラムでは、入力が変数numが保持できるサイズを超えた場合に、プログラムの挙動が変わります。
サンプルコード
#include <iostream>
#include <string.h>
int main(){
int num=100;
std::cout<<"\nValue for num:"<<num;
char c[2];
strcpy(c,"abcdefghijklmnopqrstuvwxyz");
std::cout<<"\nValue for c:"<<c;
return 0;
}実行結果
Value for num:100 Segmentation fault (core dumped)
サイズ2の文字配列cに対して26文字の文字列をコピーしているため、バッファオーバーフローが発生し、セグメンテーション違反でクラッシュします。
3. スタックオーバーフロー
スタックオーバーフローは、コールスタックポインタがスタックの上限を超えたときに発生する問題です。スタックには限られた容量しか割り当てられておらず、プログラムが利用可能なスタック領域を超えて使用すると、スタックがあふれてプログラムがクラッシュします。
最も一般的な原因は無限再帰です。以下のプログラムでは、factorial()関数が無限に呼び出され続けます。return文の記述が適切でないことが原因です。
サンプルコード
#include <iostream>
#include <string.h>
int factorial(int num){
if(num==0)
return 1;
else
return(factorial(num));
}
int main(){
int n=10;
int fact=factorial(n);
std::cout<<fact;
}実行結果
Segmentation fault (core dumped)
本来であれば「factorial(num - 1)」のように引数を減らす必要がありますが、このコードでは同じ値のまま再帰呼び出しを行うため、終了条件に到達せずスタックオーバーフローとなります。
4. セグメンテーション違反(Segfault)
セグメンテーション違反やコアダンプは、プログラムが自分に割り当てられていないメモリ領域へアクセスしようとしたときに発生します。
以下のプログラムでは、ポインタstrが際限なくインクリメントされながらメモリへ書き込みを続けています。
サンプルコード
#include <iostream>
int main(){
char *str;
char name[]="iostream";
str=name;
while(1)
(*str++)='a';
}実行結果
Segmentation fault (core dumped)
無限ループ内で配列の範囲を超えてメモリに書き込み続けるため、プログラム自身の管理外の領域にアクセスし、セグメンテーション違反が発生します。
5. メモリリーク
メモリリークは、動的に確保したメモリが解放されないまま放置されることで発生します。使用しなくなったメモリは必ず解放しなければなりません。メモリの確保を繰り返すと、リークが蓄積していき、最終的にはプログラムのクラッシュを引き起こします。
以下のような不適切なコードの繰り返しが、メモリリークの原因となります。
サンプルコード
#include <iostream>
int main(){
int *node;
node = (int *) malloc(9999999);
// free(node);
}この例では、malloc()で確保したメモリに対するfree()がコメントアウトされているため、確保されたメモリが解放されません。C++では、生ポインタの代わりにstd::unique_ptrやstd::vectorなどのスマートポインタ・コンテナを活用することで、メモリリークを効果的に防止できます。
まとめ
C++プログラムのクラッシュは、例外の未処理、バッファオーバーフロー、スタックオーバーフロー、セグメンテーション違反、メモリリークなど、さまざまな原因で発生します。いずれも適切なエラーハンドリングやメモリ管理の習慣によって防ぐことが可能です。エラーメッセージの意味を正しく理解し、原因に応じた対処を行うことが、安定したC++プログラム開発の鍵となります。
-
C++でピラミッドの体積を計算するプログラムの作り方|底面の形状別の公式と実装例
ピラミッドの底面の種類に応じた辺の長さが与えられたとき、そのピラミッドの体積を計算するのが本記事のテーマです。 ピラミッドとは、外側の面がすべて三角形で構成され、それらが共通の一点(頂点)で交わることで鋭い角を形成する3次元図形です。ピラミッドの体積は、底面がどのような形状であるかによって異なります。 ピラミッドの底面にはさまざまな種類があり、代表的なものは以下の通りです。 底面の形状別の体積の求め方 三角形の底面(三角錐) 底面が三角形の場合、ピラミッドの体積は次の公式で求められます。 体積 = (1/6) × a × b × h 正方形の底面(四角錐) 底面が正方形の場合、ピラミッドの体
-
C++で学ぶクイックソート(QuickSort)の仕組みと実装方法
クイックソートとはクイックソート(Quicksort)は、比較に基づいて未ソートのリスト(配列)を並べ替えるソートアルゴリズムの一つです。「パーティション交換ソート(partition exchange sort)」とも呼ばれます。クイックソートは安定ソートではありません。これは、等しい値を持つ要素同士の相対的な順序が保持されないためです。ただし、配列に対してごくわずかな追加メモリだけで動作するため、メモリ効率に優れています。選択ソートと非常に似ていますが、常に最悪のパーティションを選んでしまうわけではない点が異なり、より洗練された形の選択ソートと捉えることもできます。クイックソートは最も効率