娘のためにその5:スタック[20分]

娘に読ませる以外の意図はなく、よって質問や指摘には対応していません。すなわちネットの浄化作用が働いていない不正確な内容になりますので、正しい情報を求める方は閲覧をご遠慮ください。公開しているのは、通信手段としての利便性のためです。
void ffff()
{
   int a = 100;
   int b = a * 20;
   int c = a * b;
}

前回こんなプログラムを書いた。ここで疑問に思って欲しいのは、ここにある変数 a 、b 、c が格納されているメモリは正しく確保されているのか、ということだ。あくまでメモリを気にしていこう。プログラムは好きなだけ書け書けるし、どう関数を呼び出すかは事前に確定することができない。事前に使用する変数のぶんだけ確保していたとしたら、簡単に破綻してしまうだろう。

このために人類が編み出した方法が、スタックと呼ばれるメモリ管理法だ。これはコンピュータの黎明期から現在に至るまで、プログラムの動作原理の中核をなすアイデアだ。

スタックには積み重ねる、という意味がある。このプログラム ffff が実行される直前に変数 a 、b 、c のメモリが確保され(積み上げられ)、ffff を終了した直後に積み上げたぶんだけのメモリが解放される。

void ffff()
{
    int a = 100;
    int b = a * 20;
    gggg();
    int c = a * b;
}

void gggg()
{
    int a = 20;
    int b = 30;
    int c = a / b;
}

さて、スタックの動作をより詳しく見ていこう。だんだん複雑になってきているので、読み飛ばさず、頭を使いながら読んで欲しい。ここでいちど深呼吸しようか。

よろしいかな。このプログラムには ffff と gggg という二つの関数が定義されている。そして gggg が ffff の中から呼びだされているのを確認して欲しい。このとき、ffff のために確保された、ffff の中にある変数 a, b, c のメモリは gggg の実行中にどうなっているべきだろうか?

このメモリが解放されるのはまずい。それを納得して欲しい。ffff の中では、gggg を呼んだ後に a および b を使用した int c = a * b; という記述があるからだ。これがあるからには、gggg の実行中も a と b の値は残っていなければならない。ただし冒頭に述べたとおり、何もかも残しておくわけには行かない。メモリの使用は必要最小限にしたいので、必要なくなったら開放したいのだ。

また、gggg の中にも a, b, c があることに注目したい。これらは ffff の中の a, b, c と同じ名前であっても、無関係であって欲しい。関数は、それを呼び出す側の都合を考えたくはないからだ。

よって、ffff に入るときに a, b, c 用に用意されたメモリは gggg 実行中も確保されたままであり、ffff の中の a, b, c とは無関係である必要がある。そして gggg の中の a, b, c, は gggg を実行する直前に確保され、ggggを抜けた直後に解放される。

void setup()
{
 Serial.begin(9600);
 ffff();
}

void loop()
{
}

void ffff()
{
 int a = 100;
 Serial.println((int)&a);
 int b = a * 20;
 Serial.println((int)&b);
 gggg();
 int c = a * b;
}

void gggg()
{
 int a = 20;
 Serial.println((int)&a);
 int b = 30;
 int c = a / b;
}

試しに、スタックのアドレスを表示するプログラムを書いてみた。実行すると3行に渡ってアドレスが表示されるだろう。手元ではこうなった。

536887264
536887268
536887252

Serial.println((int)&a);

この文を簡単に説明すると、「aという変数のアドレスを整数とみなして出力する」となる。ここでは変数のアドレスを調べることができることを覚えておこう。

このようにアドレスを出力してみると、スタックメモリが存在する実感が沸くだろう。a と b の差が 4byte しかないところにも注目だ。プログラムを書き換えて、他の変数のアドレスも表示してみるといい。

このように、関数に入るたびに確保され、抜けるたびに解放されるメモリがスタックだ。正式に言えばスタックメモリだが、単にスタックと言えば普通はスタックメモリを指す。スタックメモリは、関数を実行するたびにそれまで確保されたメモリに追加で確保され、実行が終われば追加したぶんだけ解放される。

画像1

当然ながら、スタックで使用できるメモリには上限がある。関数を深く呼べば、スタックはその間開放するタイミングがないため、スタック用に必要なメモリが増える。このスタック用のメモリはあらかじめ用意する必要がある。経験的に言えば、4Kbyte もあれば大抵のプログラムは動作するが、現代の環境ではもっと多い。数ギガのメモリがある現代では、スタックのためのメモリはとても少ないと言える。ともかくプログラム実行前にあらかじめ決められたサイズのスタックメモリが確保されていて、それによりプログラムが動作していることを理解しよう。

なお、スタックという概念はC言語特有のものではない。たいていの言語には関数という概念があり、それを駆動するときに使用するメモリはスタックと呼ばれる。とにかく必要なだけ積んで、必要なくなったら元の状態に戻る。これはプログラムの基本中の基本で、おそらく永遠に使われる概念だろう。

つづく

この記事が気に入ったらサポートをしてみませんか?