こんにちは､ そしてようこそ｡ さて､ 動的メモリ割り当てを理解したところで､
それを実装しなければなりません｡
そこで､ 本当に重要な3つの関数､ malloc､ calloc､
reallocについて説明します｡
メモリ割り当てのcallocとreallocationに名前がついているのがわかると思います｡
メモリを確保する最も単純で一般的な方法は､ mallocという標準ライブラリ関数を使用することです｡
これは､ 実行時にダイナミックメモリを確保することです｡
この関数を呼び出すには､ 標準のlibヘッダーファイルをインクルードする必要があります｡
その引数として､ 割り当てたいメモリのバイト数を指定する｡
この関数から返されるのは､ 割り当てられたメモリの最初の1バイトのアドレスである｡
アドレスが返ってくるので､ ポインターはそれを置く唯一の場所です｡
つまり､ アドレスを返すからこそ､ このポインタの概念によってのみ､
動的なメモリ割り当てが可能になるわけです｡
ポインターにはアドレスしか入れられない､ それがポインターにはアドレスが格納されているのです｡
では､ その方法を紹介しましょう｡ とても､ シンプルな構文です｡
通常､ ポインターを作成する場合､ その必要はありません｡
ポインターを作成して､ それをnullに設定すればいいのです｡
そして､ 次の行でmallocすることができます｡
しかし､ ここでは標準的な構文で､ int型ポインタ､ int型へのポインタを作成します｡
そして､ mallocを呼び出していることに気がつくでしょう｡
そして､ ここではmallocと言っていますが､ malloc用に100バイトを作成し､
それをintポインタにキャストしています｡
mallocはアドレスを返すだけなので､ そのアドレスはintポインタを格納しているため､
エンドポインタにキャストする必要があります｡
これが標準的なやり方です｡
今度は100バイトのメモリを要求して､ それをメモリブロックのアドレスにp番号に割り当てています｡
pNumberは､ 割り当てられた100バイトの最初のintの場所を指すことになります｡
つまり､ これはどういうことかというと､ 整数は1個4バイトなので､ 25個のint値を保持することができるのです｡
また､ これは4バイトの場合を想定しています｡
お使いのシステムによっては､ もっと大きいかもしれませんし､ 小さいかもしれません｡
だから､ これの問題点がわかるはずです｡ このコードは､ 異なるシステムで動作しません｡
なぜなら､ 仮定に基づいて､ intサイズの仮定に基づいて､ アロケートしているからです｡
ですから､ このように100という定数でmallocを使うのは､
あまり良いことではありません｡
そこで､ sizeof演算子を使います｡
ですから､ intが4バイトであるという前提を外した方が良いと思います｡
その代わり､ mallocの引数の中に25個の整数のための領域を確保したい場合は､
確保したい整数の数を指定し､
それに整数のサイズを掛けてください｡
そして､ これによって､ コードの移植性が格段に向上します｡
このコードは､ 異なるシステム､ 異なるオペレーティングシステム､ 異なるアーキテクチャで動作するようになります｡
返り値はint型を指す必要があるので､ int starと言うように､
やはりアドレスをキャストします｡ ここでの唯一の違いは､ sizeof演算子を使うことです｡
sizeof演算子は､ mallocやcalloc､ reallocを使ってメモリを確保するときにとても重要です｡
その型に対応するsizeof演算子を使用し､
それに必要な数を乗じて､ 25個のint型を格納するのであれば25個とします｡
つまり､ 上記のmallocの引数は､ int型の値25個を収容するのに十分なバイト数を確保する必要があることを明確に示しているのです｡
また､ 先ほども申し上げましたが､ キャストにもご注目ください｡ これは､ 関数から返されたアドレスをint型ポインタとint型ポインタに変換するものです｡
Mallocはvoid型のポインタを返すので､ それがとても便利なのです｡
そのアドレスが返ってくるが､ それはボイドポインタである｡
voidポインタの話をしましたが､ なぜvoidポインタが便利かというと､ 異なるパラメータを取ることができる関数があり､
その関数はvoidポインタを返すことができるからです｡
しかし､
それを使って実際にデータを割り当てたり､
参照を解除したりするには､ それをキャストしなければなりません｡
もしこれを鋳造したくないのであれば､ void star p number と言えばよいでしょう｡
キャストしていないのですが､ そうすると､ 参照を解除しようと思ったら､ いずれキャストしなければならなくなります｡
しかし､ そのためにmallocからキャストする必要があるのです｡ アドレスを返しているのでint型を指してほしい､
void型を返してほしい｡
任意のバイト数を要求することができます｡
ただし､ お使いのシステムや搭載しているメモリ容量に依存します｡
そのため､ 大量のバイトを作成しようとしたり､ 要求しようとしたり､ 何らかの制限があると､
失敗する可能性があります｡
そして､ 失敗すると､ mallocはnullという値を返します｡
ポインタがNULLを指している場合､ それはどのメモリも指していないことを意味するので､
不用意にメモリを破損したり変更したりすることができないからです｡
ですから､ mallocを行った後は､ データを代入するときやポインタの参照を解除するときと同じように､
使う前にすぐにNULLかどうかをチェックするのが常に良いアイデアです｡
実際に何かを指し示していることを確認したいのです｡
つまり､ ここではポインタのp番号のためにメモリを確保し､ p番号でない場合にこう言っているのです｡
ヌルはゼロであることを忘れないでください｡
だから､ その上でnotを使えばいいんです｡ 実際にnullと比較する必要はない｡
nullと比較することで､ より読みやすく､ より明確にすることができます｡
でも､ not p numberと言えばいいわけで､ それは実はnot oneと言っているわけで､
nullはゼロなんです｡ つまり､ これはNULLをチェックすることになります｡
また､ NULLの場合は､ 基本的にメモリが確保できなかったことを意味します｡
プログラムでメモリを確保できないと､ 大変なことになります｡
そして､ このコードでメモリ割り当ての失敗に対処するために通常行うべきことは､
アボート､ exitの呼び出し､ プログラムから抜けることです｡
なぜなら､ プログラムがメモリを割り当てられないと､ プログラムが台無しになるからです｡
つまり､ 通常はメモリを確保できない場合にアクシデントアボートを呼び出すということです｡
少なくとも､ これを実行すると､ プログラムを終了させるメッセージを表示することができます｡
メモリを確保できていない状態でプログラムを続行させるより､
ずっといい｡
なぜなら､ そのメモリを使おうとするとNULLになってしまい､
プログラムがクラッシュしてしまいますが､ その理由がわからないからです｡
コールスタックを見なければならない｡ そのエラーのデバッグに時間がかかってしまうのです｡
記憶を解放する｡ 今､ プログラマーはメモリを割り当てる責任があると言いましたが､
特に組み込みシステムではメモリは重要なリソースなので､ 使わなくなったらメモリを処分しなければなりません｡
必要ないメモリは使うべきではない｡ だから､ メモリを解放する必要があるのです｡
不要になったメモリは必ず解放したいものです｡
ヒープ上に確保したメモリは､ プログラムが存在するときに自動的に解放されます｡ これは､
クリーンアップを行うので良いことです｡
しかし､ たとえ終了する直前であっても､ 使い終わったら明示的にメモリを解放する方がずっと良い｡
その方がすっきりする､ 必ずfreeでやる､
メモリを確保したら必ず解放する､ このルールは守りたいものです｡
これは厳しい厳密なルールです｡
メモリを割り当てた場合は必ず解放する｡
メモリリークは､ あるメモリを動的に確保したときに､ その参照を保持しないため､
そのメモリを解放できなくなることで発生します｡
その参考のためにあなたは範囲外かもしれません......できません｡ 発売しないんですね｡
それをメモリリークといいます｡ メモリは確保されているが､ もう使用されていない｡
これは､ ループの中でメモリを確保しているときによく起こります｡
ループに入るんですね｡ このメモリーを割り当てるのです｡ ループの中で使って､
ループから飛び出すと､ もう解放されない｡
ループの繰り返しごとに割り当てている｡
そのため､ 動的に割り当てたメモリを解放するためには､ そのアドレスにまだアクセスできる必要があります｡
ですから､ 通常は使い終わったらメモリを解放してほしいし､ 通常は割り当てた方法で解放してほしいのです｡
しかし､ そのメモリを他の関数に渡して､ その関数の責任になる場合はともかく､
通常はメモリとそれを割り当てた関数を解放したいものです｡
ポインタに格納された動的割り当てメモリブロックのメモリを解放するには､
free関数を呼び出すだけでよい｡
これは標準のlibにも入っています｡ フリー関数へのポインタを渡すだけです｡
そして通常､ そのポインタを渡した後は､ そのポインタにメモリが割り当てられていないことを示すために､
nullを設定します｡
free関数の正式なパラメータはvoid star型なので､
どんなポインタ型でも渡すことができ､ 動作します｡
実際にキャストしたりする必要はありません｡
p番号にメモリ割り当て時に返されたアドレスが含まれている限り､ メモリブロック全体がその後の使用のために解放されます｡
他のプログラムもそのメモリを使うことができ､
自分のプログラムもそのメモリを使うことができるようになり､ 組み込みシステムの場合には重要な意味を持ちます｡
そして､ 先ほど述べたように､ そのポインタがもはや何も指していないことを示すために､
常にポインタをnullに設定する必要があるのです｡
その記憶は解放されました｡
もう一つ､ callocという素敵な関数があります｡
これはmallocと比較していくつかの利点があります｡ mallocとよく似ていますが､
何をするかというと､ 与えられたサイズの要素数としてメモリを確保し､ メモリの初期化も行います｡
そこが違う､ 初期化｡ 初期化の重要性をお話ししました｡
だから､ データを初期化して､
そこに期待するデータがあることを理解できるようにしたいのです｡
データを初期化すると､ そこには何の手がかりもないデータが入っている可能性があります｡
ですから､ データは必ず何かで初期化してください｡
そしてcallocはそれをやってくれるからちょっといい｡
2つの引数を取り､ 2つのパラメータを持つ｡
容量が必要なデータ項目数､ 各データ項目のサイズ｡
標準のlibヘッダで宣言されています｡
mallocと同じように呼び出すのです｡ 見た目はほとんど同じです｡
この例では､ 整数ポインタを作成し､ sizeof
intの75個の整数を割り当てています｡
つまり､ 2つのパラメータが必要なのです｡
実際に掛け算をする必要がないので､ 少しすっきりします｡
mallocでは､ 75回のサイズイベントを行う必要がありました｡
ここでは､ 75個のintを作成し､
それぞれのサイズは4バイトまたはシステムが演算子のサイズから返す値です｡
しかし､ これの重要な点は､ すべてのデータを初期化することであり､ メモリを確保できない場合は戻り値がnullになる可能性があり､
その場合はアボートしてはいけません｡
しかし､ この方法はmallocを使うのと非常に似ていますが､ 大きな違いはこの初期化です｡
すべてのintはゼロに初期化され､ これは本当に大きな､ 大きな利点です｡
パラメータが2つあるのも､ ちょっと使い勝手がいいんです｡
もう一つのreallocという関数について説明しましょう｡
realloc関数は､ mallocやcallocで確保したメモリを再利用したり､
拡張したりすることができる関数です｡
つまり､ これは実行中にメモリの割り当て量を動的に変更する方法なんだ｡
だから､ 最初はたくさんメモリを作っても､ 気がついたらメモリが足りなくなっていた､
ということもあり得るのです｡ さて､ そこでできることは､ reallocを呼び出せば､
メモリを再割り当てすることができます｡
引数は2つで､ 1つはmallocの呼び出しによって先に回されたアドレスを含むポインタである｡
つまり､ あるメモリが割り当てられたポインタ､ またはcallocと､ それを割り当てる新しいメモリのサイズとバイトを持っています｡
つまり､ これは第2引数で指定した量のメモリを確保する､
ということですね｡ ポインターで参照される､ 以前に割り当てられたメモリの内容を転送するため､
ポインターを渡すので､ 以前に割り当てたメモリの量を自動的に知ることができるのです｡
そして､ 実際に新しく割り当てられたメモリをそこに追加していくのです｡
そして､ 新しいメモリへのvoidポインタを返します｡
だから､ 他のものと同じようにreallocを呼び出すときにリキャストしなければならない｡
reallocできなかった場合はnullを返しますが､
これもおそらく致命的なエラーで､ 中止すべきです｡
この操作の最大の特徴は､ reallocは元のメモリ領域の内容を保持し､
そのメモリを拡張しているに過ぎないということです｡
順番にできなかったので断片的かもしれませんが､ それでも今データに割り当てられるメモリは全部あります｡
例を見てみましょう｡
この例では､ mallocを使ってから､ メモリを再確保し､
解放しています｡
そこで､ ここでは文字ポインタを作成しています｡
nullに初期化していない｡ おそらく初期化する必要がある｡
しかし､ それは､ それが指しているもののために割り当てられているメモリを持っていません｡
ポインタを格納するためのメモリが割り当てられており､
これは単なるアドレスである｡ しかし､ そのアドレスに行こうとすると､ プログラムがホストになってしまいます｡
そこで､ mallocを呼び出すわけです｡
ここでは､ malloc 15と言っています｡
これにより､ キャラクタポインタが15バイト作成されます｡
voidポインタを返すので､ キャストしています｡ つまり､ strはポインターであり､
そのポインターの指す先に対してメモリが割り当てられているのです｡
そして､ str copyを使って､ それが指しているもの､ つまり文字列に何らかのデータを渡すことができ､
我々はアドレスにある文字列をプリントアウトすることができるのです｡
そうすると､ 15バイトを割り当てたのでは足りないことに気づきます｡
そこでreallocを呼び出すと､
ポインタがstrで､ さらに25バイトを再割り当てすることになります｡
今はまだ､ インメモリージェイソンがあります｡
文字列の連結を呼び出すと､ 文字列comに連結され､ Jasonになります｡
comで､ それをプリントアウトしています｡
そして最後に､ おそらくこのスライドの中で最も重要なことですが､
メモリリークが起きないようにするために､ freeを呼び出すのです｡
mallocを呼ぶときは必ずfreeを呼びます｡
では､ この例をコードブロックの中で見てみましょう｡
以下は､ コードブロックエディタ内のコード例です｡
まず最初に気づくべきことは､
標準のlibヘッダーファイルが含まれていることです｡
また､ str関数､ concatenation､ copyをいくつか使っているので､
stringを含めています｡ h.
さて､ 8行目ではキャラクターポインタを作成します｡
そのために割り当てられているメモリはありません｡
まだ何も指していないので､ これをnullに初期化する必要があります｡
11行目でmallocを呼び出しています｡
そして､ charのバイトサイズが異なる場合に備えて､
charの15倍のサイズと言うべきでしょう｡ そして､
mallocはvoidを返すので､ それをキャラクタ・ポインタにキャストしています｡
そして､ それをコピーするのです｡
もしこれをコピーしようとして､ 11行目で何もしなかったとしたら､
プログラムはクラッシュしてしまうでしょう｡
あとは､ 文字列とアドレスをプリントアウトするだけです｡
ここで､ reallocを呼び出す｡
15バイトより多くのデータを保存する必要があるので､
25バイトで再割り当てし､ おそらく一貫性を保つためにもう一度sizeof
charを行いたいと思います｡
これをキャストして､ jasonを含む既存のメモリロケーションに連結します｡
comで､ ジェイソンをプリントアウトします｡ comを使用しています｡
そして最後に､ 20行目でメモリを解放しているのですが､ これは本当に重要なことです｡
では､ これを実行してみましょう｡ そして､ 2回目にプリントアウトするときには､
連結が行われていることがわかります｡
最初の文字列は､
メモリが割り当てられ､ そのアドレスは000C2918です｡
そして､ 引数として渡したポインタの既存のサイズを再割り当てして取ることで､ 新しいアドレスを作成したのです｡
つまり､ 000C0CD8には､ jasonの既存のデータと､
さらに25バイトのデータが割り当てられているのです｡
そして､ 気づいたことは､ 連結がうまくいっていることです｡
なぜなら､ 再割り当ての際に､
そのメモリに内容物のjasonを入れ､ それを拡張しただけだからです｡
このように､ malloc､ calloc､ free reallocコールを使用することには多くの利点があるのです｡
これを使いたい､ これを理解したい｡ かなりシンプルな機能で使いやすい｡
最後のスライドでは､ いくつかのガイドラインについて説明します｡
少量のメモリをたくさん割り当てるのは避けたい､ 効率的でない｡
ヒープ上にメモリを確保すると､ それなりにオーバーヘッドが発生します｡
また､ 小さなメモリブロックを割り当てる場合､ より大きなブロックを少数割り当てるよりもはるかに多くのオーバーヘッドを持つことになります｡
だから､ 必要なメモリを確保するのですが､
再割り当てで2文ごとにやるのではなく､ 大きなチャンクでやってください｡
必要な分だけメモリにしがみつく｡ メモリは希少な資源です｡
ヒープになると､ さらに品薄になる｡
だから､ メモリブロックを使い終わったら､ freeを呼び出す｡
割り当てたメモリを解放する方法を必ず用意すること｡ メモリを割り当てるコードを書いたら､
コードのどこでメモリを解放するかを決めておくこと｡
そのポインターにアクセスできることを確認してください｡
ポインターをグローバルにする必要があるのなら､ そうする必要があります｡
しかし､ 通常､ その関数で割り当てた場所は､ その関数で解放する必要があります｡
他の機能がメモリを解放してくれるのであれば､
その時点で良くないと判断しているのでしょう｡ そこには､ あまりにも多くのカップリングと依存関係が存在するのです｡
作成した人が､ 削除する人であるべきです｡
他の機能などに責任を押し付けてはいけない｡
ヒープ上に確保したメモリを解放する前に､ 不用意にアドレスを上書きしてしまうと､
メモリリークの原因になりますので､ ご注意ください｡
そのため､ あまり多くのバイトを書き込もうとしたり､ オーバーフローが発生したりすると､ 問題が発生する可能性があります｡
そのため､ ループ内でメモリを確保する場合は､ バッファオーバーフローの問題が発生する可能性があるので､
特に注意が必要です｡
参考になったでしょうか｡ ダイナミックメモリーアロケーションは､ 大きな､ 大きな問題です｡
これに慣れるための課題を出しますが､ この考え方をしっかり理解しておいてください｡
ありがとうございます｡