C言語⑰(2025/08/27)

動的なメモリ確保

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]関数が終わったら消滅
  • pfreeしない限り残り続ける

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:コピー元のアドレス
(例えば)既にある配列の途中のデータを破棄して切り詰める、等も可能。

コメント

タイトルとURLをコピーしました