C言語をしっかりと学びたい! (その3 メモリ①)

今回のテーマ

普段扱うメモリについての裏話

始めに

メモリについてはプログラムをしていく上でやはり知っておきたいところ。なのでヒープ領域やスタック領域について解説していきたいのですが、その前に。
我々が普段特に意識しないで扱うメモリですが、実はその裏では頑張ってくれている存在があります。今回はそういった、なぜ我々がメモリを扱えるのかといったところを話していきたいと思います。ですが、正直C言語にはあまり関係のない話になってしまうので興味のない方は飛ばしちゃって大丈夫です。

同じアドレスでも違う値!?

まずは次の二つのプログラムを用意してみてください。

#include <stdio.h>

int main(void)
{
    char c = 'a';

    printf("%p\n", &c);
    while (1)
        ;
    return (0);
}
#include <stdio.h>

int main(void)
{
    char *c =   ;        //ここに書く

    printf("%c", *c);
    return (0);
}

そして、とりあえず1つ目のプログラムを実行します。
次に、そこで表示された値を二つ目のプログラムの *c に代入して2つ目のプログラムを実行してみます。

するとどうなるでしょう?
同じアドレスだから「a」が出力されるはず?

しかし実行結果は異なると思います(実際に何が出力されるかは不明です)。

これはどういうことかというと、一言でいえば世界線が違うんです。
まあこれだけ言われてもよくわからないと思うのでちゃんと解説。

実は、C言語の中で出てくるアドレスは実際の(物理的な)メモリのアドレスではありません!そういったお話をするためにもまずはプログラムの実行前に行われることを知る必要があります。

プログラム実行の流れ

プログラムが実行されるまでの動きとしては大雑把にいうと
①プログラムを実行するためのメモリを確保する。
②プログラムをメモリにロードする。
③ロードされたプログラムに沿ってプログラムを実行する
④適宜、変数などのためのメモリを確保していく。
こんな感じです。(②、③のメモリはすべて①で確保されたメモリの中で行います。)

要するに、最初に自由に使っていいメモリを与えられるので、その一部をプログラムを置くのに使って、他の一部を変数とかのために使って、、、という感じですね。

この①のメモリの割り当てはOSの仕事なのですが、ここで困ったことがあります。自由に使っていいメモリをもらえるといっても、割り当てられたメモリが飛び飛びだったらすごく不便じゃないでしょうか?

例えば、1~4、8~11、13~14、19~の(アドレスの)メモリは他のプログラムが使用中だけど、5~7、12、15~18は使っていいよ。みたいな感じを想像してください。

キャプチャ (2)

char s[6];の場合とにかく6個のメモリが必要ですが、連続した6個のメモリは割り当ててもらえなかったので、
s[0]~s[2]は5~7のアドレスで、s[3]は12、s[4]~s[5]は15~16のアドレス、ということになります。

こんなことになったら絶望しかありませんよね。配列はアドレスが連続していることに意味があるのにこれではもうどうしようもありません。これを解消するために編み出されたのが「仮想アドレス」という仕組みです。

仮想アドレス

一言でいえば、もともとバラバラ飛び飛びだったアドレスに、1から連番の新しい(仮想的な)アドレスをつけてあげる、ということです。

キャプチャ

こうすればプログラマは無事、メモリは1から連続したものとして気兼ねなく扱うことができます。
なお、仮想アドレスから実際のメモリのアドレスへの対応表のことを「ページテーブル」と言い、それに基づいてMMU(Memory Management Unit)が対応させます。現在MMUはCPUに組み込まれているそう。

これで分かったと思いますが、
普段C言語の中で出てくるアドレスとは物理アドレスではなく仮想アドレスであるため、仮想アドレスが同じでも、そもそも住んでいる世界が違ったら同じ物理アドレスにはなりえない、ということです。

また、この仮想的な連続したメモリのことを「仮想メモリ」といいます。

もう一つの仮想メモリ

ついでに、もうひとつ紹介したいものがあります。
その名も「仮想メモリ」です!、、、あれ?今紹介したばかりじゃない?と思うかもしれませんが、なんとややこしいことに「仮想メモリ」という用語は二つの使われ方があります。その2つ目の意味を一言でいうなら
「主記憶装置が足りないなら補助記憶装置を使えばいいじゃない」
です。

どういうことかというと、もしも物理メモリをすべて使い果たしてしまった場合にさらにメモリが欲しくなった場合は、現在あまり使っていないメモリの部分を補助記憶装置のほうにコピーしてしまってそこの部分のメモリを使えるようにしてしまうということです。もちろん、補助記憶装置はメモリと比べて速度が圧倒的に遅いのであまりやりたくはないのですが、足りないなら背に腹は代えられないということですね。これはOSがやってくれます。

これでやっと、プログラマは特に苦労することなく便利なメモリを使えるようになりました。

次回は、その連続したメモリをどのようにして使っていくのかのお話です。ヒープ領域とスタック領域についてなどになると思います。


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