メモリの確保と言えば配列。これは事前に使用するメモリ量が分かっている時に使う便利な宣言です。これを静的確保とも呼びます。では、実行するまでサイズが分からない場合は、どうすれば良いのでしょうか。それが今回解説する動的確保の手法です。
--
いきなりここに飛んで来ちゃった人は、よろしければ下記からご覧ください。
- 動的確保の必要性
動的確保は、プログラムを実行しないとサイズが分からない場合に用います。例えばファイルを読み込む時とか、敵の弾を移動処理するとかです。配列で予想される最大サイズを予め確保しておくという方法もありますが、もしも予想されているサイズを超えた場合は、越えた分が消えてしまうか、あるいはメモリオーバーで意図しない動作をしてしまうかもしれません。
そこで動的確保です。事前にサイズを調べてからある程度まとめて確保する方法と、その都度確保する方法があります。その都度確保する方法は便利に見えるのですが、メモリの確保の時間分動作が遅くなったり、あるいはメモリの断片化により本来取得できるメモリが取得不能になる危険性があります。
さらに、確保したメモリは OSから借りている状態ですので、使い終わったら全て返却する必要があります。最近のOSはアプリ終了時に強制的に貸し出したメモリを回収する機能がありますが、やはり自分で借りたものは自分で返すのが礼儀だと思います(OSに余分な負荷をかけない)。
int* pInteger = (int*)malloc(sizeof(int) * 8);
これで int型の変数を 8個保存できるメモリ空間を確保します。pInteger には、そのメモリ空間の先頭アドレスが返ります。このアドレスは最後に確保したメモリを返却する際に使用しますので、最後まで値を壊さないように注意してください。
malloc に指定するサイズの単位がバイトなので、sizeof でその型のバイト数を取得した上で、必要な個数分乗算で増やしています。また、malloc で返却されるポインタは、型がない型である void ポインタ(void*)ですので、返却値に対してキャストして型を指定して使うことになります。
メモリ確保はバイト単位ですが、キャストして使う前提であるため、キャストに指定できる型であれば、全て指定することが出来ます。例えば、C言語020で学んだ構造体でもそのまま使用することが出来ます。
これで TShip 構造体を 8個格納できるメモリを確保することが出来ました。オマケで typedef の使い方の応用も記載しておきました。構造体宣言と同時に別名定義してしまったほうが便利です。
enum EType { CARRIER, BATTLE, CRUISER, DESTROY }; typedef struct TShip { char Name[10]; // 名前 EType Type; // タイプ int PosX; // X座標 int PosY; // Y座標 bool IsVertical; // 縦方向か? } SHIP, *PSHIP; PSHIP pShip = (PSHIP)malloc(sizeof(SHIP) * 8);
※ 電気事情が怪しい昨今、廊下に一つこれを差し込んでおくだけで、いざという時に真っ暗闇にならずとても安心できます。

【楽天】
サンワダイレクト 人感センサーライト 室内 コンセント 停電時自動点灯ライト 電球色 常夜灯 懐中電灯 非常灯 にも 800-LED043
- 動的確保したメモリにデータを格納する
まず、int型動的配列である pInteger の場合は、普通に宣言した配列と同じように使用することが出来ます
const int max = 8 - 1; pInteger[0] = (rand() % 10000) + 1; pInteger[max] = (rand() % 10000) + 1; printf("最初=%d\n", pInteger[0]); printf("最後=%d(%d番目)\n", pInteger[max], max);
構造体配列も基本的には同じように使うことが出来ます。
const int max = 8 - 1;
pShip[0].Type = (EType)(rand() % 4); strcpy_s(pShip[0].Name, sizeof(pShip[0].Name), "ゆきかぜ"); pShip[max].Type = (EType)(rand() % 4); strcpy_s(pShip[max].Name, sizeof(pShip[max].Name), "アンドロメダ"); printf("最初=%d,%s\n", pShip[0].Type, pShip[0].Name); printf("最後=%d,%s(%d番目)\n", pShip[max].Type, pShip[max].Name, max);
strcpy_s の動作テストも兼ねて、わざと名称が配列から溢れる長い名前で指定してみました。結果は以下のようになります。

アンドロメダという文字列が長すぎてコピー出来ないというエラーが発生しました。これを正しく動作させるにはアンドロメダをはまかぜ等と全角4文字以内に変更する必要がありますね…。

さて、構造体の場合は直接ポインタアドレスから構造体の各メンバを呼び出す仕組みが用意されています。それが -> です。アロー演算子と呼ばれています。これを使うことでポインタから直接メンバを使用することが出来ます。例えば、
C言語では、ポインタで直接扱うほうが処理が完結で高速に動作することも多いので、アロー演算子を多用する場面も多くなります。
const char* ppName[] = { "はまかぜ", "すずかぜ", "いそかぜ", "ふゆづき", "ゆきかぜ", "あさしも", "かすみ", "はつしも", }; PSHIP ptr = pShip; for (int i = 0; i < _countof(ppName); ++i, ptr += sizeof(SHIP)) { strcpy_s(ptr->Name, sizeof(ptr->Name), ppName[i]); printf("No.%d: %s\n", i, ptr->Name); }
- 動的確保を使う上で注意すること
動的確保はプログラム側よりメモリをOSから借りる動作となります。そのため、もしかするとメモリが足りずに動的確保に失敗する可能性があります。動的確保に失敗した場合は、メモリが割り当てられませんので、返却値は NULL になります。そのため、malloc の結果は常に NULL ではないことを確認するようにします。
int* pInteger = (int*)malloc(sizeof(int) * 8); if (pInteger == NULL) exit(EXIT_FAILURE);
この例ではメモリの確保に失敗したら、いきなり終了しています。本当はこの時点でも別の確保済みメモリがあるかもなので、できれば強制終了関数を別途用意して、そちらを呼び出すようにしましょう。
あと、取得したメモリは OS から借り物なので、使わなくなったら OS に返却する必要があります。そのための関数が free です。例えば取得した pInteger を開放する場合は、
free(pInteger); pInteger = NULL;
free でメモリの開放を行った後で、pInteger を NULL にしていますが、これは間違って開放後のメモリをそのまま使い続けることがないようにする安全措置です。OS によっては、開放後もしばらくそのまま使えてしまうことがありますが、それはたまたま使えているだけの状態です。これはメモリリークしている状態となりますので、大変よろしくない状態です。pInteger が NULL になっていれば pInteger[0] = 100; なんてやろうものならとたんに落ちるのでバグが分かります。
・・・落ちちゃマズイだろうって?いいえ、開発中は落ちてもらったほうが、バグがすぐに分かるので安心安全なのです。開発中にメモリリークに気が付かず、ユーザーの手元で予期しない事態、例えば HDD のクラッシュが起きたとしたら目も当てられませんよね。そのため、バグの早期発見のためにも、使い終わったポインタ変数は NULL にしておくのが安全なのです。
最後に malloc は処理としては軽くないです。どちらかと言えば重い部類に入ります。そのため、頻繁にメモリの確保と開放を繰り返すと、プログラムのパフォーマンスが著しく落ちます。また、メモリの断片化によりそのうちメモリの動的確保に失敗することがあるかもしれません。そのため、最初になるべく大きめにドカンと確保した上で、自前でメモリ管理を行い、本当に足りなくなったらまたドカンと追加確保するような動作がベターとなります。
自前メモリ管理は上級の部類に入ると思いますので、ここでの説明は省きます。興味があれば自前実装を検討してみると良いでしょう。
※ 気が向いたらサンプル作ります…^^;
※ 停電時の自動点灯をより重視するならこちら。パナソニックのは工事が必要ですが、こちらは既存のコンセントに差し込むだけです。ただ、ホコリには少し弱いかもなので、同時にトラッキングガードを使用すると良いかもです。
【楽天】
東芝(TOSHIBA) LED保安灯センサ付ナイトライト NDG9632(WW)
コメント