C++
 Computer >> コンピューター >  >> プログラミング >> C++

ヒープオーバーフローとスタックオーバーフローの違いと発生原因を解説

ヒープオーバーフローとは

ヒープは、動的に確保される変数を格納するためのメモリ領域で、プロセスのメモリ空間の一部です。C言語では malloc()calloc() といった標準ライブラリ関数を使用することで、実行時に必要なサイズのメモリをヒープから取得できます。

ヒープオーバーフローは、主に以下のようなケースで発生します。

1. 巨大な動的メモリを一括で確保した場合

int main() {
    float *ptr = (float *)malloc(sizeof(float) * 1000000);
}

一度に非常に大きなサイズのメモリを要求すると、システムが提供できるヒープ領域を超えてしまい、確保に失敗したりオーバーフローが発生したりします。

2. メモリを確保し続けて解放しない場合(メモリリーク)

int main() {
    for (int i = 0; i < 100000000000; i++) {
        int *p = (int *)malloc(sizeof(int));
        /* free(p) を呼び忘れるとメモリリークが蓄積する */
    }
}

使い終わったメモリを free() で解放しないまま新しいメモリを確保し続けると、未解放のメモリが次々と溜まっていき、最終的に利用可能なヒープ領域を使い果たしてしまいます。これがいわゆる「メモリリーク」です。

スタックオーバーフローとは

スタックは「後入れ先出し(LIFO: Last In First Out)」というデータ構造で、関数内で使用されるローカル変数や、関数に渡される引数、そして関数の戻りアドレスなどを格納するために使われます。

コンピュータのメモリにおいてスタックのサイズには上限があるため、プログラムが必要とするメモリがその限界を超えると、スタックオーバーフローが発生します。

1. 関数が無限に再帰呼び出しを行った場合

関数が自分自身を際限なく呼び出し続けると、呼び出しが行われるたびにローカル変数や戻りアドレスがスタックに積み重なり、やがてスタック領域を使い切ってしまいます。

void calculate(int a) {
    if (a == 0)
        return;
    a = 6;
    calculate(a); /* a が 0 にならず無限再帰となる */
}

int main() {
    int a = 5;
    calculate(a);
}

上記の例では、引数 a が常に 6 に置き換えられるため終了条件に到達できず、無限に再帰呼び出しが続きます。

2. 巨大なローカル変数や多次元配列を宣言した場合

関数内で非常に大きなサイズのローカル配列や高次元の行列を宣言すると、スタック領域を一気に消費し、オーバーフローを引き起こす可能性があります。

int main() {
    int A[20000][20000]; /* スタックに収まりきらない巨大な配列 */
}

両者の違いと対策のポイント

ヒープオーバーフローは「動的メモリの管理ミス」が原因であるのに対し、スタックオーバーフローは「スタック領域の上限超過」が原因です。それぞれ以下のような対策が有効です。

  • malloc()calloc() で確保したメモリは、使用後に必ず free() で解放する
  • 再帰処理には適切な終了条件を設けるか、状況に応じてループによる反復処理へ書き換える
  • 大きな配列が必要な場合は、ローカル変数としてではなくグローバル変数として宣言するか、動的にメモリを確保する

これらのポイントを意識することで、メモリ関連の深刻なバグやクラッシュを未然に防ぐことができます。

  1. C++で円と長方形の重なりを判定するアルゴリズム

    問題の概要円を (radius, xc, yc) という形式で表します。ここで (xc, yc) は円の中心座標です。同様に、軸に平行な長方形(軸平行境界ボックス)を (x1, y1, x2, y2) という形式で表し、(x1, y1) が左下隅の座標、(x2, y2) が右上隅の座標とします。このとき、円と長方形が互いに重なっているかどうかを判定する必要があります。たとえば、次のような入力が与えられた場合を考えてみましょう。この場合、出力は true(重なりあり)となります。解決のアプローチこの問題を解く鍵は、「長方形の中で円の中心に最も近い点」を見つけることです。その点と円の中心との距離が

  2. C++で解くドミノとトロミノを使ったタイル敷き詰め問題(2×Nボード)

    問題の概要本記事では、「ドミノ」と「トロミノ」という2種類の形状を使ったタイル敷き詰め(タイリング)問題をC++で解く方法を解説します。これらのピースは、以下のように回転させて使用することができます。タイリングでは、盤面上のすべてのマスを必ずタイルで覆わなければなりません。また、2つのタイリング方法は、盤上の4方向に隣接する2つのセルにおいて、片方のタイリングだけがその両方のマスを同じタイルで占有している場合に限り「異なる」とみなされます。入力と出力の例整数Nが与えられたとき、2×Nのボードを敷き詰める方法が何通りあるかを求めます。例えば、入力が3の場合、出力は5となります。敷き詰め方は以下の