C言語入門|配列の限界とヒープ利用の必要性

以下は、ご指定どおり ホームページ掲載用の記事本文 です。
章立てはせず、見出しのみを使って構成し、文調は親しみやすくフレンドリーでやわらかくしています。
サンプルプログラムは 内容を類似させつつ差し替え、表示メッセージも変更しています。

ここまでで、配列やポインタをかなり自由に扱えるようになってきましたね。
「配列って便利だなぁ」「もう何でも作れそう!」
そんな気分になっている頃かもしれません。

でも実は、普通の配列には超えられない限界があります。
そしてこの限界に気づかないまま使い続けると、
C言語で最も危険な不具合に直結してしまいます。

今回はその限界をしっかり理解したうえで、
なぜヒープ領域を使う必要があるのかを見ていきましょう。

「普通の配列」が持つ2つの限界

まず、結論から整理します。
関数の中で宣言する普通の配列には、次の2つの大きな限界があります。

配列の限界その1

大きな領域を確保できない

配列の限界その2

関数が終了すると寿命が尽きる

どちらも、
「配列そのものが悪い」わけではありません。
原因は、どこに確保されているかにあります。

配列はスタック領域に確保される

通常、次のように宣言した配列は、

void func(void)
{
    int data[100];
}

スタック領域 に確保されます。

スタック領域は、

  • 関数が使う一時的な作業場所
  • 関数の呼び出しと終了に合わせて
    自動的に確保・解放される

という特徴を持っています。

スタック領域は意外と小さい

ここが最初の落とし穴です。

たとえ、

  • PC全体のメモリが数GBあっても
  • スタックとして使える領域は数MB程度

という環境がほとんどです。

何が起きるかというと…

int huge[100000];

こうした大きなサイズの配列をいくつも、うっかり宣言すると、

  • スタック領域があふれる
  • プログラムが即座に異常終了

という スタックオーバーフロー が発生します。

つまり、

配列のサイズは「メモリ全体」ではなく
「スタックの容量」に縛られている

というわけです。

関数が終わると配列は消える

次に、もう一つの限界です。

スタック領域の最大の特徴は、

関数が終わると、確保された領域は無効になる

という点です。

これを理解していないと、
C言語で最も怖いバグに遭遇します。

危険な例:配列を返す関数

次のコードを見てください。

プロジェクト名:10-9-1 ソースファイル名: sample10-9-1.c

#include <stdio.h>

int* createScores(void)
{
    int scores[4];   // 仮に2000~2015番地
    return scores;   // 先頭アドレスを返す
}                   // ← ここで scores の寿命は終了

int main(void)
{
    int* p = createScores();
    p[0] = 90;       // もう使えない領域に書き込み!
    return 0;
}

一見すると、
「配列を作って、その先頭アドレスを返している」
ように見えます。

でも実際には、
関数が終了した瞬間に scores は消滅しています。

何がそんなに危険なのか

ポイントはここです。

  • メモリはすでに解放されている。
  • でも、アドレス値だけは残っている。

そのため、

  • アクセスできてしまう。
  • エラーも出ない
  • たまたま動くこともある。

という、最悪の状態になります。

これは、

「もう存在しないものを指しているポインタ」

であり、
ダングリングポインタ と呼ばれます。

なぜC言語は止めてくれないのか

ここで改めて、C言語の性格を思い出しましょう。

  • C言語は実行時チェックをほとんどしない
  • メモリ管理はすべてプログラマの責任

つまり、

危険なことができる
でも止めてはくれない

という言語です。

このコードも、

  • コンパイルは通る。
  • 実行もできる。
  • でも動作は未定義

という、非常に危険な状態です。

配列の限界を超えるために必要なもの

ここまでの話をまとめます。

スタック上の配列の問題点

問題内容
サイズ制限大きな配列を確保できない。
寿命関数終了と同時に消える。

これらの問題を解決するために必要なのが、

ヒープ領域の利用

です。

ヒープ領域とは何か(次の記事への導入)

ヒープ領域は、

  • 必要なサイズを
  • 必要なタイミングで確保でき
  • プログラマが明示的に解放する

という、スタックとは正反対の性質を持っています。

ヒープを使えば、

  • 大きな配列を安全に確保できる。
  • 関数をまたいでデータを保持できる。

ようになります。

ここで覚えておきたい感覚

この章で一番大事なのは、次の感覚です。

配列そのものが悪いのではない
どこに、どの寿命で置かれているかが重要

「この配列はいつまで生きているのか?」
これを常に意識できるようになると、
あなたはもう C言語中級者の入口 に立っています。