今回はメモリの動的確保。 これも結構重要な内容です。
メモリの動的確保とは
例えば極端な話ですが学校で人数が多いクラスは60人、少ないクラスは25人だとします。
この時配列を60人分確保すると、25人のクラスの場合は35人分が無駄になります。
他にも文字列の入力を受け取る配列の大きさにも困りますね。
長い文字列を入力される事を想定して1000もの大きさを確保したとしても
10文字も入力されないなんてこともよくあるのではないかと思います。
これは相当もったいないですね…。
逆に大体10文字だからといって10文字分しか用意しないとそれ以上の入力があった時
対応できません。 これでは駄目ですね。
このような時に役立つのが「メモリの動的確保」です。
これまでのプログラムでは配列は事前に大きさを決めてソースコードを書いていました。
しかしメモリの領域を動的に確保できると上記のように
もったいないような事になること、足りなくなることもなくなります。
このメモリの動的確保を行なうのに最もよく使われていると思われるのがmalloc関数です。
数学関数を使う際にはmath.hが、文字列関数を使う際にはstring.hというヘッダファイルを
インクルードする必要がありました。
malloc関数のようなメモリを操作する関数を使うには
stdlib.hをインクルードする必要があります。
これはソースコードの上部に
#includeと書くだけでいいので問題ないかと思います。
ちなみにmalloc関数のプロトタイプ宣言は void *malloc(size_t);となっています。
「size_t」というのはsizeof演算子が返す型だと定められています。まぁ負の値は返さないので
unsigned intと同じだと思えば問題ないかと思います。
「void *」は基本的に他のポインタ型へキャストをして使用します。
他のポインタ型とは違って、void型のポインタは別のポインタ型へ全く問題なくキャストが
できるというのが主な使用法です。 まぁこのvoid *についてはあまり覚えなくても問題ないです。
malloc関数とfree関数
さて、それではmalloc関数に戻ります。void *malloc(size_t); この関数は
引数に確保する領域のバイト数を指定します。 そしてその確保した領域の先頭のアドレスを
戻り値として返します。 例を示します。
| ソースコード |
|---|
 |
| 実行結果 |
|---|
 |
こうしてmalloc関数は使います。引数にはsizeof(int) * 5として、int型の大きさを5個確保しています。
戻り値を代入しているのはint型のポインタですね。
(int *)として、int型のポインタへとキャストしてから代入しています。
このキャストは今のコンパイラなら必要ないのですが
古いコンパイラであった場合、キャストしないといけないということがあるので
一応(int *)をつけることにします。
また、滅多にないことなのですが、malloc関数は残りのメモリが確保しようとしている量よりも
少ない場合にはメモリが確保できず、NULLを返すことになっているので
if(p == NULL)として戻り値がNULLの場合の対策をしています。
ここでexit関数を使っています。 これを実行するとここでプログラムを終了します。
return文の場合はmain以外の関数で実行した場合、プログラムが終了することはありませんが
exit関数はmain以外の関数で実行した場合でも、プログラムを終了させます。
引数に入れる値は正直適当です。 ここでは1を指定しています。
ただ、0は正常に終了する という事を表すそうなので0以外にした方が良いようです。
(例:return 0; ← main関数の最後、0で終了している)
最後になりますが、free関数は重要です。
これを使って確保したメモリを解放しないと
無駄に使わない領域を増やす結果になってしまいます。
まぁプログラムが終了する際にはちゃんと確保した領域は解放されることが多いのですが
解放されない事も稀にあるようですし、malloc関数と対にfree関数を使うようにというのはよく言われています。
このfree関数というのはしばしば使うのを忘れてしまうのですが、絶対につけて下さい。
まぁこう書いている私自身もよく忘れてしまっているわけですがorz
まぁ色々と面倒な事を書いてきましたが、簡単にいうと
p = malloc(sizeof(int) * 5);というのは
int a[5], *p;
p = a;
としているのと同じ事です。 そして上の5というのは変数にする事もできるので
| ソースコード |
|---|
 |
| 実行結果 |
|---|
 |
ということができます。 これでメモリの動的確保ができるようになりました。
realloc関数
malloc関数でメモリを動的に確保して、free関数で解放する。
これだけでも十分に問題ないのですが、ついでにrealloc関数というのも説明します。
これはメモリを動的に再度確保する関数です。
この関数のプロトタイプはvoid *realloc(void *, size_t);となっています。
引数の1つ目にはmalloc(またはrealloc)で確保した領域のアドレスが代入されているポインタを指定
2つ目には確保する領域のバイト数を指定します。
そして確保した領域の先頭アドレスを戻り値として返します。
ちなみに
1つ目の引数にNULLを指定した場合はmalloc関数と同じ働きをします。
2つ目の引数に0を指定した場合はfree関数と同じ働きをします。
これはrealloc関数が1つ目の引数で指定した領域の内容を別の場所にコピーした後に
free関数で解放して、2つ目の引数で指定したバイト数分だけ領域を確保し
コピーした内容を、この確保した領域にコピーし直すという
働きをしているためです。
そのため1つ目の引数がNULLだった場合はmalloc関数と同じに
2つ目の引数が0だった場合はfree関数と同じ働きをします。
またこの時、1つ目の引数に指定したアドレスと戻り値のアドレスは同じである場合が多いかと思いますが
違う事もあります。 これによって問題が起こる事もあります。
例えば関数に引数としてmallocで確保したメモリ領域の先頭アドレスを渡したとします。
要はこういう状況です。 長くなるのでソースは画像で示せませんが…。
ソースコード

この例ではreallocで確保しているのは10です。 この程度の領域確保の場合なら引数に指定したアドレスと
戻り値のアドレスは同じだと思います。 これは指定した領域の大きさが問題になる事が多いですので。
そこで、reallocで確保する量を100000にしてみます。 結果がこれです。

このように関数に渡したpとアドレスが同じなのならば問題なく上の例のように関数内で
代入された値が表示されるはずなのですが、されていません。
これは確保した領域が違う場所であるために起こってしまう現象です。
確保した場所が違っても再確保する前の場所のデータは新しいアドレスの方に
コピーされるので基本的には問題ないのですが、関数に引数として渡す場合などにこの
問題が起こります。
また、realloc関数がfunc関数に渡したアドレスの領域を解放しているのでp[0]は14525ですらありません。
そしてさらにこの問題の厄介な点は100000確保したメモリはfree関数で解放されていないという点です。
main関数で解放されているのは全く違う場所が解放されている事になります。
この全く違う場所を解放するという事自体が既にかなり不味いことです。
領域を確保したわけでもない場所を解放すると何が起こるかが分からないので
コンピュータに問題が起きる事すらありえるかもしれません。
それに100000ものメモリ領域を確保したままになってしまっている可能性もありますしね…。
そういうことから私はrealloc関数で領域を別の関数内で再確保する場合には
このようにするべきなのではないかと考えます。
ソースコード
このようにp = func(p);とすることで、realloc関数で確保された領域の先頭アドレスが
1つ目の引数のアドレスと同じでも違っていても戻り値としてその値を返しているので
main関数内で全く問題なく表示できるようにできます。
それゆえに確保した領域の先頭アドレスがpに代入されているという事になるので
free(p);は全く問題ありません。
ですので別の関数で再度メモリを確保しようという時にはその確保した領域の先頭アドレスを
戻り値として返すべきだと思います。 これなら確実な処理が実行できますからね。
しかし… この辺の内容絶対に相当難しいですね。 そのため完全に理解するのは厄介だと思います。
ここについて理解したい方はじっくりと時間をかけて理解して下さい。
細かい点についてまで理解しなくてもいいという方はp = func(p)のように
戻り値として返して受け取るということだけ覚えて下さい。
文字数の関係でここで一旦中断します。
後半は
こちらです。
| このブログのURL
|この記事のURL