動的なメモリ確保
malloc()関数
malloc(確保するメモリのサイズ(バイト数));
引数で指定したバイト数分のメモリを確保し、型を持たない(void型)ポインタ値(アドレス)を返す。受ける側で使用する変数型を指定してやる必要がある。
※使い終わった領域はfree()で開放する必要があるため、受ける側(ポインタ変数)は変更しないこと!どうしてもポインタ操作をしたい場合、たとえばpをmallocで確保した領域のポインタとして、
int* wp = p;
などのように、作業用のポインタ変数にコピーしてから、作業用の変数を使って行うこと。
free()関数
解放したいメモリ領域の先頭アドレス(ポインタ)を引数として渡す。
※渡すポインタは、malloc()を含むメモリ確保用の関数で取得したアドレスである必要がある。
今日のコード①
int* p;
p = (int*)malloc(sizeof(int) * 2000);
char* cp = (char *)malloc(sizeof(int)* 2000);
printf("ポインタp = 0x%p\n", p);
printf("ポインタcp = 0x%p\n", cp);
free(p);
free(cp);
int* p = (int *)malloc(sizeof(int)*2000);
↑ ↑
| 引数で指定したバイト数のメモリを確保して先頭アドレスを返す。
mallocの戻り値はvoid型のため、キャストが必要
int* p = (int *)malloc(sizeof(int)*2000);
int array[2000];
この二つの領域へのプロセス上のアクセス手順は同じ。(int型の変数が2000個分確保される)
int *p = (int *)malloc(sizeof(int) * 2000);
int array[2000];
●int *p … 「int型へのポインタ」
●malloc(...) … 指定サイズ分のメモリをヒープ領域に確保する関数
●array[2000] … スタック領域に置かれる固定長配列
② メモリの置き場所
array[2000];
コンパイル時に必要なサイズ(2000×sizeof(int))が決まる
実行時に スタック領域に確保される
関数を抜けると自動で解放される
サイズは固定(関数に入った時点で2000個分確定)
p = malloc(sizeof(int) * 2000);
実行時に「2000個分ほしい」と要求 → ヒープ領域から確保
free(p); を呼ぶまで解放されない
サイズは実行時に可変(たとえばユーザー入力で決められる)
長く保持したいデータに向いている
③ 扱い方の違い
array
直接 array[i] で使える
サイズが固定なので、柔軟性は低いけど速くて安全
p
使う前に malloc で確保、使い終わったら free が必要
p[i] でもアクセスできる(配列とほぼ同じ書き方)
ただし、malloc 失敗時は NULL が返るのでチェックが必要
④ まとめ
●固定配列 int array[2000];
→ 一時的でサイズが決まっているときに使う。
●動的配列 int *p = malloc(...);
→ 実行時にサイズを決めたい、関数をまたいで使いたいときに使う。
📝 授業でよく出る「実用的な違い」は、**「実行時にサイズを決められるか」「解放が必要か」**です。
今日のコード②
int型2000個とchar型8000個のメモリ確保領域を計算・比較してみる
int size1 = (sizeof(int) * 2000);
int size2 = (sizeof(char) * 8000);
printf("size1 = %d\n", size1);
printf("size2 = %d\n", size2);
size1 = 8000
size2 = 8000
どちらも8000byteの領域を確保する。
1. 配列アクセスとポインタの関係
int array[5] = {10,20,30,40,50};
int *p = array; // arrayは「先頭要素のポインタ」に暗黙変換される
array[2]
=*(array + 2)
= *(先頭アドレス + 2番目に進んだアドレス)p[2]
=*(p + 2)
= *(pの指すアドレス + 2番目)
つまり、
array[i] == *(array + i)
p[i] == *(p + i)
が常に成り立ちます。
2. 「身体感覚」に落とすなら
- 配列アクセス → 机の上に並んだ箱を番号で指定する
- ポインタアクセス → 指を箱の位置に動かして、中身を直接触る
たとえば:
int *p = array; // pは array[0] を指している
*p = 100; // array[0] を100にする
*(p+3) = 200; // array[3] を200にする
配列の「箱番号アクセス」を、ポインタは「指を動かして触る」に置き換えただけ。
3. 配列とポインタの違い(微妙だけど大事)
- 配列変数
array
- メモリ上に「2000個の連続した箱」がドンと置かれる
- 代入不可(
array = ...;
はエラー) - 関数に渡すときは自動的に「先頭のポインタ」に崩れる
- ポインタ変数
p
- ただの「指し示すアドレスの値を持った変数」
p = malloc(...)
で指し先を変えられるp++
で次の箱に移動できる
4. 腹落ちポイント
- 配列のアクセスは全部ポインタ演算の別名
- 違うのは「置き場所(スタックorヒープ)」と「動かせるかどうか」
👉 ゆきさんが「身体感覚」と言ったの、すごく的確です。
C言語では 「配列アクセス」≒「ポインタ操作」≒「アドレス計算」 が腹に落ちた瞬間に一気に霧が晴れます。
1. 「変数宣言」と「メモリ確保」
実は両方とも メモリを確保する 行為です。
ただし「どこに」「どのタイミングで」置かれるかが違う。
int x;
→ コンパイラが「この関数のスタックにintサイズの箱を用意せよ」と決める。
→ 実行時には自動でスタックに確保される。int array[2000];
→ 同様に、スタック領域に2000個分をドンと確保。
→ 「宣言」と同時に確保完了。
→ 解放は自動。
2. malloc の「メモリ確保」との違い
malloc
は「ヒープ領域」から動的に取ってくる。- 「どのくらい確保するか」は 実行時にプログラム自身が決める。
- 解放は自動じゃなくて
free
が必要。
3. 参考書が混乱させる理由
- 変数宣言も配列宣言もメモリを確保してるのに
→ 初学者向けには「変数=箱を用意する」としか言わない。
→malloc
に来て突然「メモリ確保」と言い始める。
→ 「え、変数のときは確保じゃなかったの?」ってなる。
実際には両方ともメモリ確保。
ただし:
- 宣言=コンパイラが勝手に配置決めてくれる確保
- malloc=プログラマが明示的に要求する確保
4. 腹落ちする表現にすると
- 変数宣言
→ 「スタックに自動でスペースを借りる」 - malloc
→ 「ヒープに自分でスペースを取りに行く」
両方とも「確保」なんだけど、
- 宣言=自動販売機でジュースを買ったら缶が勝手に出てくる感じ
- malloc=倉庫に行って係員に「このサイズの箱ちょうだい」って頼む感じ
ゆきさんの「断りなくスライドしている」って気づきは正しくて、
授業的には「変数=静的確保、malloc=動的確保」って まとめて『確保』の話として連続させるべきなんですよ。
(by ChatGPT)
1. メモリには大きく分けて「置き場」がある
プログラムが動くとき、使えるメモリはざっくりこんな感じに分けられます👇
┌───────────┐
│ スタック │ ← 関数の変数とか、一時的に使う箱
├───────────┤
│ ヒープ │ ← mallocでお願いしてもらう箱
├───────────┤
│ その他 │ ← プログラム本体や定数など
└───────────┘
2. スタック (stack)
- 関数を呼ぶたびに「一時的な箱」が積み重なっていく場所。
- 例:
void func() { int x = 10; // ← xはスタックに置かれる }
- 関数を抜けたら自動で片付けられる(解放不要)。
- 配列もここに置かれる:
int array[2000]; // スタックに2000個分まとめて置かれる
- 特徴 → 自動で出し入れされる、寿命は関数の中だけ
3. ヒープ (heap)
- プログラムが「自由に使っていい倉庫」みたいな場所。
malloc
で「2000個分ちょうだい!」とお願いしてもらう。int *p = malloc(sizeof(int) * 2000); // ヒープに2000個分置く
- 自動では片付かない。使い終わったら
free(p);
が必要。 - 特徴 → 長期間データを持ちたいときに便利(関数を超えても残る)
4. 例で比べる
void func() {
int a[10]; // スタックに置かれる(関数が終われば消える)
int *p = malloc(10*4); // ヒープに置かれる(関数が終わっても残る)
}
a[10]
は 関数が終わったら消滅。p
は freeしない限り残り続ける。
5. まとめ
- スタック → 短期・自動・便利(ただしサイズ固定、関数の外には持ち出せない)
- ヒープ → 長期・手動・柔軟(ただしfree忘れ注意)
💡 易しい比喩
- スタック = コンビニのカゴ(入れてレジ通ったら返却される)
- ヒープ = 倉庫を間借り(借りたら返すのを忘れないように!)
calloc()関数
calloc(要素数,要素のサイズ);
メモリを確保して、要素をすべて”0”に初期化する。
char* cp;
cp = (char *)calloc(10, sizeof(char));
printf("領域の先頭アドレス = %p\n", cp);
for (int ii = 0; ii < 20; ii++) {
printf("%d,", cp[ii]);
}
実行結果
領域の先頭アドレス = 0000013EA8B300F0
0,0,0,0,0,0,0,0,0,0,-3,-3,-3,-3,0,0,0,0,0,0,
10要素分だけ初期化されている。
realloc()関数
一度確保したメモリを違うサイズで確保しなおす。
realloc(以前のメモリ領域の先頭アドレス,新しく確保するメモリのバイト数);
引数:元のメモリの先頭アドレス/新しく確保するメモリのバイト数
戻り値:新しいメモリ領域の先頭アドレス(void*)
※中身のデータは引き継ぐ。
(拡張した場合、拡張部分にはランダムな値が入り、切り詰めた場合には、あふれたデータは当然破棄される)
メモリ操作関数
memset()関数
memset(メモリ領域の先頭アドレス,設定する値,値を設定するメモリのバイト数);
指定したメモリ領域の内容をすべて指定した値に設定する
ZeroMemory()関数 ※マクロ:Windows.hのインクルードが必要
指定した領域に0をセットする
ZeroMemory(セットする領域の先頭アドレス,バイト数)
memcpy()関数
メモリの内容をコピーする
memcpy(Destination,Source,コピーするメモリのバイト数)
Destination:コピー先アドレス
Source:コピー元のアドレス
(例えば)既にある配列の途中のデータを破棄して切り詰める、等も可能。
コメント