{
    "componentChunkName": "component---src-templates-post-js",
    "path": "/imasarali-jie-suru-sutateitukurinku-to-dainamitukurinku-webapurikesiyonenzinianotamenodi-reiyaru-men-sirizu-sono1/",
    "result": {"data":{"ghostPost":{"id":"Ghost__Post__69c3346171fa390001b3661c","title":"いまさら理解する「メモリとは？」- Webアプリケーションエンジニアのための低レイヤー入門シリーズ その２","slug":"imasarali-jie-suru-sutateitukurinku-to-dainamitukurinku-webapurikesiyonenzinianotamenodi-reiyaru-men-sirizu-sono1","featured":false,"feature_image":null,"excerpt":"先日、とあるサービスで OOMKilled \nが発生しまして、メモリリミットに引っかかったんですが、「そもそもなんでこんなにメモリ使ってるんだ？」を調査するのに、メモリの基礎知識が改めて大事だなと思いました。\n\nということで、「低レイヤー入門シリーズ」の第二弾はメモリについて深掘りしていきます！\n\n前回の「スタティックリンクとダイナミックリンク」\n[https://tech.anti-pattern.co.jp/imasarali-jie-surusutateitukurinkutodainamitukurinkunituite/]\nでは、プログラムがどのように実行ファイルになるかを学びました。今回は、その実行ファイルが動くときに欠かせないメモリの仕組み\nを、C言語を使って体感していきましょう。\n\n\n--------------------------------------------------------------------------------\n\nなぜWebエンジニアがメモリを理解すべきか？\n普段、Ruby on\nRailsやNode.js、Goなどで開発していると、メモ","custom_excerpt":null,"visibility":"public","created_at_pretty":"25 March, 2026","published_at_pretty":"08 April, 2026","updated_at_pretty":"08 April, 2026","created_at":"2026-03-25T10:03:29.000+09:00","published_at":"2026-04-08T14:35:32.000+09:00","updated_at":"2026-04-08T14:35:32.000+09:00","meta_title":null,"meta_description":null,"og_description":null,"og_image":null,"og_title":null,"twitter_description":null,"twitter_image":null,"twitter_title":null,"authors":[{"name":"Akihiro YAGASAKI","slug":"akihiro","bio":null,"profile_image":"https://ghost.tech.anti-pattern.co.jp/content/images/2022/04/yagasaki--2-.jpeg","twitter":null,"facebook":null,"website":null}],"primary_author":{"name":"Akihiro YAGASAKI","slug":"akihiro","bio":null,"profile_image":"https://ghost.tech.anti-pattern.co.jp/content/images/2022/04/yagasaki--2-.jpeg","twitter":null,"facebook":null,"website":null},"primary_tag":null,"tags":[],"plaintext":"先日、とあるサービスで OOMKilled \nが発生しまして、メモリリミットに引っかかったんですが、「そもそもなんでこんなにメモリ使ってるんだ？」を調査するのに、メモリの基礎知識が改めて大事だなと思いました。\n\nということで、「低レイヤー入門シリーズ」の第二弾はメモリについて深掘りしていきます！\n\n前回の「スタティックリンクとダイナミックリンク」\n[https://tech.anti-pattern.co.jp/imasarali-jie-surusutateitukurinkutodainamitukurinkunituite/]\nでは、プログラムがどのように実行ファイルになるかを学びました。今回は、その実行ファイルが動くときに欠かせないメモリの仕組み\nを、C言語を使って体感していきましょう。\n\n\n--------------------------------------------------------------------------------\n\nなぜWebエンジニアがメモリを理解すべきか？\n普段、Ruby on\nRailsやNode.js、Goなどで開発していると、メモリを意識することは少ないかもしれません。しかし、こんな場面に遭遇したことはありませんか？\n\n * 「メモリリーク」でアプリケーションが落ちた\n * 「OutOfMemoryError」に悩まされた\n * Dockerのメモリ制限で苦労した\n * パフォーマンスチューニングで「ヒープ」「スタック」という言葉が出てきた\n\nこれらを理解するためには、メモリの基礎知識が必要です。C言語は、メモリを直接操作できる言語なので、メモリの仕組みを学ぶのに最適なんです。\n\n\n--------------------------------------------------------------------------------\n\nメモリとは何か？\n概念図で理解する\nまず、メモリの全体像を把握しましょう。\n\n┌─────────────────────────────────────────────────────────────────┐\n│                        物理メモリ（RAM）                          │\n│                                                                 │\n│  メモリは「番地（アドレス）がついた巨大なロッカー」のようなもの      │\n│                                                                 │\n│  ┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐      │\n│  │ 0x00 │ 0x01 │ 0x02 │ 0x03 │ 0x04 │ 0x05 │ 0x06 │ 0x07 │ ...  │\n│  │      │      │      │      │      │      │      │      │      │\n│  │ 1byte│ 1byte│ 1byte│ 1byte│ 1byte│ 1byte│ 1byte│ 1byte│      │\n│  └──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘      │\n│     ↑                                                           │\n│  アドレス（16進数で表される）                                     │\n└─────────────────────────────────────────────────────────────────┘\n\nポイントは3つです。\n\n 1. メモリには「アドレス」という番地がある\n 2. 1つのアドレスには（基本は）1バイト（8ビット）のデータが格納できる\n 3. アドレスは通常、16進数（0x...）で表現される\n\n駅のコインロッカーを想像してみてください。ロッカー番号がアドレスで、中に入れる荷物がデータです。ただし、1つのロッカーに入る荷物は1バイト分だけ。大きなデータは、複数のロッカーにまたがって入れる必要があります。\n\nプログラムが使うメモリ領域\nプログラムが実行されると、OSはそのプログラム専用のメモリ空間を割り当てます。このメモリ空間は、いくつかの領域に分かれています。\n\n┌─────────────────────────────────────────┐  高いアドレス\n│              スタック領域                │  ← 関数のローカル変数など\n│                  ↓                      │    （自動で確保・解放）\n│                  ↓                      │\n├─────────────────────────────────────────┤\n│                                         │\n│              空き領域                    │\n│                                         │\n├─────────────────────────────────────────┤\n│                  ↑                      │\n│                  ↑                      │\n│              ヒープ領域                  │  ← 動的に確保するメモリ\n│                                         │    （mallocなど）\n├─────────────────────────────────────────┤\n│              BSS領域                     │  ← 初期化されていない\n│          (未初期化データ)                 │    グローバル変数\n├─────────────────────────────────────────┤\n│              データ領域                  │  ← 初期化された\n│           (初期化済みデータ)              │    グローバル変数\n├─────────────────────────────────────────┤\n│              テキスト領域                │  ← プログラムのコード\n│            (コード領域)                  │    （機械語）\n└─────────────────────────────────────────┘  低いアドレス\n\n今回は特にスタック領域とヒープ領域に注目します。この2つの違いがわかると、「なぜメモリリークが起きるのか」がスッキリ理解できるようになります。\n\n\n--------------------------------------------------------------------------------\n\n環境準備\n前回と同じく、実際にコードを動かしていきましょう。GCCが必要です。GCCが入っていない方は、ご自身の環境に合わせてググってインストールしてください！\n\n# バージョン確認\ngcc --version\n\n\n--------------------------------------------------------------------------------\n\n変数とメモリアドレス\n変数はメモリ上のどこにある？\nまず、変数がメモリ上のどこに配置されるかを確認してみましょう。\n\nファイル名: address.c\n\n#include <stdio.h>\n\nint main() {\n    int number = 42;\n    char letter = 'A';\n    double pi = 3.14159;\n    \n    printf(\"=== 変数の値とアドレス ===\\n\\n\");\n    \n    printf(\"int型変数 number\\n\");\n    printf(\"  値: %d\\n\", number);\n    printf(\"  アドレス: %p\\n\", (void*)&number);\n    printf(\"  サイズ: %zu バイト\\n\\n\", sizeof(number));\n    \n    printf(\"char型変数 letter\\n\");\n    printf(\"  値: %c\\n\", letter);\n    printf(\"  アドレス: %p\\n\", (void*)&letter);\n    printf(\"  サイズ: %zu バイト\\n\\n\", sizeof(letter));\n    \n    printf(\"double型変数 pi\\n\");\n    printf(\"  値: %f\\n\", pi);\n    printf(\"  アドレス: %p\\n\", (void*)&pi);\n    printf(\"  サイズ: %zu バイト\\n\", sizeof(pi));\n    \n    return 0;\n}\n\nコンパイルと実行\ngcc -o address address.c\n./address\n\n実行結果の例\n=== 変数の値とアドレス ===\n\nint型変数 number\n  値: 42\n  アドレス: 0x7ffd5c8b3a4c\n  サイズ: 4 バイト\n\nchar型変数 letter\n  値: A\n  アドレス: 0x7ffd5c8b3a4b\n  サイズ: 1 バイト\n\ndouble型変数 pi\n  値: 3.141590\n  アドレス: 0x7ffd5c8b3a40\n  サイズ: 8 バイト\n\nここで注目すべきなのは、型によってサイズが違うこと。intは4バイト、charは1バイト、double\nは8バイト。そしてアドレスを見ると、変数ごとにちゃんと別々の場所が割り当てられているのがわかります。\n\n解説\n * &演算子：変数の前に&をつけると、その変数のメモリアドレスが取得できます\n * %p：アドレスを表示するためのフォーマット指定子です\n * sizeof：変数や型のサイズ（バイト数）を返します\n\nメモリのイメージ（上記の例の場合）\n\nアドレス        値              変数名\n0x7ffd5c8b3a40  [3.14159....]   pi (8バイト)\n    :\n0x7ffd5c8b3a4b  ['A']           letter (1バイト)\n0x7ffd5c8b3a4c  [42......]      number (4バイト)\n    ↓\nスタック領域（高いアドレスから低いアドレスへ伸びる）\n\n> 💡 Webエンジニア向けメモ\n> JavaScriptではtypeofで型を調べますが、メモリサイズは完全に隠蔽されています。 C言語では、型によってメモリの使用量が明確に決まります。\nこれが「型を意識する」ということの本質です。 Goのunsafe.Sizeof()やRustのstd::mem::size_of()\nでも同じようにサイズを確認できます。\n\n--------------------------------------------------------------------------------\n\nポインタの超基礎\nポインタとは？\nC言語といえばポインタ！…そして、挫折ポイントでもありますよね。でも恐れることはありません。一言でいうと、\n\n> ポインタ = メモリアドレスを格納する変数\nそれだけです。「値そのもの」ではなく「値がある場所（住所）」を覚えておく変数、というだけ。\n\n┌────────────────────────────────────────────────────────────┐\n│                                                            │\n│   通常の変数                      ポインタ変数              │\n│   ┌─────────┐                   ┌─────────────┐            │\n│   │   42    │                   │ 0x7ffd...4c │            │\n│   └─────────┘                   └──────┬──────┘            │\n│   number                               │                   │\n│   (int型)                              │ 「ここを見て！」   │\n│                                        ↓                   │\n│                                   ┌─────────┐              │\n│                                   │   42    │              │\n│                                   └─────────┘              │\n│                                   number                   │\n│                                                            │\n└────────────────────────────────────────────────────────────┘\n\nポインタを使ってみよう\nファイル名: pointer_basic.c\n\n#include <stdio.h>\n\nint main() {\n    int number = 42;\n    int *ptr = &number;  // numberのアドレスをptrに格納\n    \n    printf(\"=== ポインタの基本 ===\\n\\n\");\n    \n    // 元の変数\n    printf(\"number の値: %d\\n\", number);\n    printf(\"number のアドレス: %p\\n\\n\", (void*)&number);\n    \n    // ポインタ変数\n    printf(\"ptr の値（= numberのアドレス）: %p\\n\", (void*)ptr);\n    printf(\"ptr が指す先の値（*ptr）: %d\\n\", *ptr);\n    printf(\"ptr 自身のアドレス: %p\\n\\n\", (void*)&ptr);\n    \n    // ポインタを使って値を変更\n    printf(\"--- ポインタ経由で値を変更 ---\\n\");\n    *ptr = 100;  // ポインタを通じてnumberの値を変更\n    printf(\"*ptr = 100 を実行後:\\n\");\n    printf(\"number の値: %d\\n\", number);\n    printf(\"*ptr の値: %d\\n\", *ptr);\n    \n    return 0;\n}\n\n実行結果\ngcc -o pointer_basic pointer_basic.c\n./pointer_basic\n\n=== ポインタの基本 ===\n\nnumber の値: 42\nnumber のアドレス: 0x7ffd5c8b3a4c\n\nptr の値（= numberのアドレス）: 0x7ffd5c8b3a4c\nptr が指す先の値（*ptr）: 42\nptr 自身のアドレス: 0x7ffd5c8b3a40\n\n--- ポインタ経由で値を変更 ---\n*ptr = 100 を実行後:\nnumber の値: 100\n*ptr の値: 100\n\n*ptr = 100 しただけなのに、numberの値も100に変わっています。同じメモリを指しているから当然なんですが、初めて見るとちょっと不思議ですよね。\n\nポインタの記号まとめ\n* が宣言時と使用時で意味が変わるのがハマりポイントです。\n\n記号意味使用例* (宣言時)「これはポインタ変数です」int *ptr;* (使用時)デリファレンス（参照先の値を取得）*ptr = 100;&アドレスを取得\n&number宣言と使用の違い\n\nint *ptr = &number;   ← 宣言時の * は「ポインタ型」を示す\n     ↓\n*ptr = 100;           ← 使用時の * は「参照先にアクセス」を示す\n\n> 💡 Webエンジニア向けメモ\n> 「ポインタ」って難しそうに聞こえますが、実はJavaScriptやRubyでも似た概念はあります。\nオブジェクトを変数に代入すると、値のコピーではなく「参照」が渡される、あれです。 C言語のポインタは、その参照を明示的に操作できるというだけの話なんです。\n\n--------------------------------------------------------------------------------\n\nメモリの動的確保（malloc / free）\nスタックとヒープ\nここまで見てきた変数は、すべてスタック領域に確保されていました。スタック領域の変数は、関数が終了すると自動的に解放されます。\n\n一方、ヒープ領域は、プログラマが明示的に確保・解放するメモリです。\n\n┌─────────────────────────────────────────────────────────────┐\n│                                                             │\n│   スタック領域                    ヒープ領域                 │\n│   ─────────────                  ─────────────              │\n│                                                             │\n│   ✅ 自動で確保・解放              ⚠️  手動で確保・解放       │\n│   ✅ 高速                         ⚠️  やや低速               │\n│   ❌ サイズはコンパイル時に決定     ✅ サイズを実行時に決定可能 │\n│   ❌ 関数内でのみ有効              ✅ 関数をまたいで使える     │\n│                                                             │\n│   用途: ローカル変数               用途: 可変長データ、        │\n│        関数の引数                      長期間保持するデータ   │\n│                                                             │\n└─────────────────────────────────────────────────────────────┘\n\nRubyやJavaScriptでは new \nするだけでヒープにオブジェクトが作られますが、C言語では自分で「これだけのメモリをください！」とOSにお願いする必要があります。それが malloc() です。\n\nmalloc と free\nファイル名: malloc_basic.c\n\n#include <stdio.h>\n#include <stdlib.h>  // malloc, free に必要\n\nint main() {\n    printf(\"=== 動的メモリ確保 ===\\n\\n\");\n    \n    // 整数1つ分のメモリを確保\n    // ※C言語ではmallocの戻り値(void*)は暗黙的にキャストされるため、\n    //   キャストは省略可能です。ここでは意図を明示するために記載しています。\n    //  （C++ではキャストが必須です）\n    int *ptr = (int *)malloc(sizeof(int));\n    \n    // 確保できたか確認（重要！）\n    if (ptr == NULL) {\n        printf(\"メモリの確保に失敗しました\\n\");\n        return 1;\n    }\n    \n    printf(\"メモリを確保しました\\n\");\n    printf(\"確保したアドレス: %p\\n\", (void*)ptr);\n    printf(\"確保したサイズ: %zu バイト\\n\\n\", sizeof(int));\n    \n    // 確保したメモリに値を格納\n    *ptr = 12345;\n    printf(\"格納した値: %d\\n\\n\", *ptr);\n    \n    // メモリを解放\n    free(ptr);\n    printf(\"メモリを解放しました\\n\");\n    \n    // 解放後のポインタはNULLにするのが安全\n    ptr = NULL;\n    \n    return 0;\n}\n\n実行結果\ngcc -o malloc_basic malloc_basic.c\n./malloc_basic\n\n=== 動的メモリ確保 ===\n\nメモリを確保しました\n確保したアドレス: 0x55a8b6c4a2a0\n確保したサイズ: 4 バイト\n\n格納した値: 12345\n\nメモリを解放しました\n\nmalloc → 使う → free の3ステップ。これがC言語のメモリ管理の基本です。free を忘れると大変なことになりますが、それは後ほど\n\n配列を動的に確保する\nスタック上の配列は int array[5]; のようにサイズをコンパイル時に決める必要がありますが、malloc を使えば実行時にサイズを決められます。\n\nファイル名: malloc_array.c\n\n#include <stdio.h>\n#include <stdlib.h>\n\nint main() {\n    int n;\n    printf(\"配列の要素数を入力してください: \");\n    scanf(\"%d\", &n);\n    \n    // n個のint配列を動的に確保\n    int *array = (int *)malloc(sizeof(int) * n);\n    \n    if (array == NULL) {\n        printf(\"メモリの確保に失敗しました\\n\");\n        return 1;\n    }\n    \n    printf(\"\\n%d個のint型配列を確保しました\\n\", n);\n    printf(\"確保したサイズ: %zu バイト\\n\", sizeof(int) * n);\n    printf(\"先頭アドレス: %p\\n\\n\", (void*)array);\n    \n    // 配列に値を格納\n    for (int i = 0; i < n; i++) {\n        array[i] = i * 10;\n    }\n    \n    // 配列の内容とアドレスを表示\n    printf(\"インデックス | 値   | アドレス\\n\");\n    printf(\"-------------|------|------------------\\n\");\n    for (int i = 0; i < n; i++) {\n        printf(\"    %d        | %3d  | %p\\n\", i, array[i], (void*)&array[i]);\n    }\n    \n    // メモリを解放\n    free(array);\n    array = NULL;\n    \n    printf(\"\\nメモリを解放しました\\n\");\n    \n    return 0;\n}\n\n同様に実行すると？\n\n実行結果\n配列の要素数を入力してください: 5\n\n5個のint型配列を確保しました\n確保したサイズ: 20 バイト\n先頭アドレス: 0x55a8b6c4a2a0\n\nインデックス | 値   | アドレス\n-------------|------|------------------\n    0        |   0  | 0x55a8b6c4a2a0\n    1        |  10  | 0x55a8b6c4a2a4\n    2        |  20  | 0x55a8b6c4a2a8\n    3        |  30  | 0x55a8b6c4a2ac\n    4        |  40  | 0x55a8b6c4a2b0\n\nメモリを解放しました\n\nアドレスが4バイトずつ増えているのがわかりますか？ これは int \n型が4バイトだからです。メモリ上にきれいに並んでいるのが見えると、配列の正体がよくわかりますね。\n\n> 💡 Webエンジニア向けメモ\n> GoやRustでは make([]int, n) や Vec::with_capacity(n) のように、\n動的にサイズを指定して配列やスライスを作ることがよくあります。 裏側では似たようなヒープへのメモリ確保が行われています。\n\n--------------------------------------------------------------------------------\n\nメモリリークを体験する\nメモリリークとは？\nメモリリーク\nとは、確保したメモリを解放し忘れることで、使えるメモリが徐々に減っていく現象です。Webアプリの運用でよくある「なぜかメモリ使用量が右肩上がりで、定期的に再起動しないといけない」の原因、だいたいコレです。\n\nファイル名: memory_leak.c\n\n#include <stdio.h>\n#include <stdlib.h>\n\nvoid bad_function() {\n    // メモリを確保するが、解放しない（これがメモリリーク！）\n    int *ptr = (int *)malloc(sizeof(int) * 1000);\n    if (ptr == NULL) return;\n    *ptr = 42;\n    // free(ptr); ← これがない！\n}\n\nvoid good_function() {\n    int *ptr = (int *)malloc(sizeof(int) * 1000);\n    if (ptr == NULL) return;\n    *ptr = 42;\n    free(ptr);  // ちゃんと解放\n}\n\nint main() {\n    printf(\"=== メモリリークのデモ ===\\n\\n\");\n    \n    printf(\"bad_function を1000回呼び出します...\\n\");\n    for (int i = 0; i < 1000; i++) {\n        bad_function();\n    }\n    printf(\"完了（約4,000,000バイトがリークしています）\\n\\n\");\n    \n    printf(\"good_function を1000回呼び出します...\\n\");\n    for (int i = 0; i < 1000; i++) {\n        good_function();\n    }\n    printf(\"完了（メモリリークなし）\\n\");\n    \n    return 0;\n}\n\nbad_function は1回呼ぶたびに sizeof(int) * 1000 = 4,000バイト \nをリークします。1,000回呼ぶと約4,000,000バイト（約3.8MB）の漏れです。たった数行の free \nの書き忘れで、これだけのメモリが無駄になるんですね。\n\nValgrindでメモリリークを検出\nC言語にはメモリリークを検出する定番ツール Valgrind があります。Linuxでは、以下で確認できます！\n\n# Valgrindのインストール（Ubuntu）\nsudo apt install valgrind\n\n# コンパイル（デバッグ情報付き）\ngcc -g -o memory_leak memory_leak.c\n\n# Valgrindで実行\nvalgrind --leak-check=full ./memory_leak\n\n検出結果\n==12345== LEAK SUMMARY:\n==12345==    definitely lost: 4,000,000 bytes in 1,000 blocks\n==12345==    indirectly lost: 0 bytes in 0 blocks\n==12345==      possibly lost: 0 bytes in 0 blocks\n==12345==    still reachable: 0 bytes in 0 blocks\n==12345==         suppressed: 0 bytes in 0 blocks\n\ndefinitely lost: 4,000,000 bytes — バッチリ検出されました！ こんなふうに、C言語ではメモリの問題を直接確認できます。\n\n> 💡 Webエンジニア向けメモ\n> Node.jsでも同様のメモリリークは起こります。例えば、グローバル変数に配列を追加し続けたり、イベントリスナーを解除し忘れたり、クロージャで大きなオブジェクトを参照し続けたり。原理は同じで、「使わなくなったメモリが解放されない」ことが問題です。\n> Node.jsの場合は --inspect フラグをつけて起動し、Chrome DevToolsの「Memory」タブでHeap\nSnapshotを取ると、Valgrindのようにリークを調査できます。Goなら pprof が同様のツールになります。\n\n--------------------------------------------------------------------------------\n\nスタックとヒープの違いを実感する\nダングリングポインタの危険\n最後に、スタックとヒープの違いが生む典型的なバグを見てみましょう。\n\nダングリングポインタとは、すでに無効になったメモリを指すポインタのことです。「存在しない住所が書かれたメモ」みたいなものですね。\n\nファイル名: dangling.c\n\n#include <stdio.h>\n#include <stdlib.h>\n\n// ダメな例：スタック上の変数のアドレスを返す\nint* bad_get_number() {\n    int local = 42;\n    return &local;  // ⚠️ 関数終了後、localは無効になる\n}\n\n// 良い例：ヒープ上にメモリを確保して返す\nint* good_get_number() {\n    int *ptr = (int *)malloc(sizeof(int));\n    if (ptr == NULL) return NULL;\n    *ptr = 42;\n    return ptr;  // ✅ ヒープ上なので関数終了後も有効\n}\n\nint main() {\n    printf(\"=== ダングリングポインタのデモ ===\\n\\n\");\n    \n    printf(\"【危険】スタック上の変数を返す場合:\\n\");\n    int *bad = bad_get_number();\n    printf(\"アドレス: %p\\n\", (void*)bad);\n    // ⚠️ この行は未定義動作です！何が表示されるかわかりません\n    // コンパイラの最適化レベルによって挙動が変わります\n    printf(\"値（未定義動作！）: %d\\n\\n\", *bad);\n    \n    printf(\"【安全】ヒープ上のメモリを返す場合:\\n\");\n    int *good = good_get_number();\n    if (good != NULL) {\n        printf(\"アドレス: %p\\n\", (void*)good);\n        printf(\"値: %d\\n\", *good);\n        free(good);  // 使い終わったら解放\n    }\n    \n    return 0;\n}\n\nコンパイル時の警告\n# 最適化なしでコンパイル（未定義動作の挙動を確認するため）\ngcc -Wall -O0 -o dangling dangling.c\n\ndangling.c: In function 'bad_get_number':\ndangling.c:7:12: warning: function returns address of local variable [-Wreturn-local-addr]\n    7 |     return &local;\n      |            ^~~~~~\n\nコンパイラが警告してくれますね！ -Wall オプションをつけてコンパイルすると、こうした危険なコードを事前にキャッチできます。\n\nこの例がまさに「スタック vs ヒープ」の違いです。スタック上の変数（local）は関数を抜けた瞬間に無効になりますが、ヒープ上のメモリ（malloc \nで確保したもの）は free \nするまで有効。だからこそ、ヒープに確保して返すのが正しいパターンなんです。ただし、前述のメモリリークのように、freeを忘れないようにしないといけません！\n\n> 💡 Webエンジニア向けメモ\n> GoやRustでは、この種のバグはコンパイラが防いでくれます。 Goでは「エスケープ解析」によって、関数外に渡される変数は自動的にヒープに配置されます。 go\nbuild -gcflags=\"-m\" で確認できるので、興味のある方はぜひ試してみてください。\nRustではさらに厳密で、所有権とライフタイムの仕組みによって、ダングリングポインタの発生をコンパイル時に完全に防いでいます。\n\n--------------------------------------------------------------------------------\n\nまとめ\n今回学んだことを整理しましょう。\n\n┌─────────────────────────────────────────────────────────────────┐\n│                        今回のまとめ                             │\n├─────────────────────────────────────────────────────────────────┤\n│                                                                 │\n│  1. メモリにはアドレスがある                                     │\n│     → & でアドレスを取得、%p で表示                              │\n│                                                                 │\n│  2. ポインタ = アドレスを格納する変数                            │\n│     → int *ptr = &number;                                       │\n│     → *ptr でポインタが指す値にアクセス                          │\n│                                                                 │\n│  3. 動的メモリ確保                                               │\n│     → malloc() で確保、free() で解放                             │\n│     → 解放を忘れるとメモリリーク                                 │\n│                                                                 │\n│  4. スタック vs ヒープ                                           │\n│     → スタック：自動管理、関数内で有効                           │\n│     → ヒープ：手動管理、プログラム全体で有効                     │\n│                                                                 │\n└─────────────────────────────────────────────────────────────────┘\n\nWeb系っぽい言語との対応\nC言語で学んだ概念は、普段使っている言語にも対応しています。\n\nC言語Web系っぽい言語での対応malloc()new Object() / オブジェクト生成free()GC（ガベージコレクション）が自動で行うポインタ\n参照（Reference）スタックプリミティブ型の値（※言語による）ヒープオブジェクト / 配列（※言語による）※\nスタックとヒープの割り当ては言語や実装によって異なります。例えばGoではエスケープ解析によって、見た目はローカル変数でもヒープに配置されることがあります。上記はJavaでの一般的なイメージとして参考にしてください。\n\n\n前回のリンクの話と今回のメモリの話で、だいぶコンピュータの「裏側」が見えてきたのではないでしょうか？\nこの知識があると、Dockerのリソース制限とかOOMKilledとか、「あ、あれのことね」とピンとくるようになりますよ！\n\nたぶん！\n\nこちらからは以上です。","html":"<p>先日、とあるサービスで <code>OOMKilled</code> が発生しまして、メモリリミットに引っかかったんですが、「そもそもなんでこんなにメモリ使ってるんだ？」を調査するのに、メモリの基礎知識が改めて大事だなと思いました。</p><p>ということで、「低レイヤー入門シリーズ」の第二弾は<strong>メモリ</strong>について深掘りしていきます！</p><p><a href=\"https://tech.anti-pattern.co.jp/imasarali-jie-surusutateitukurinkutodainamitukurinkunituite/\">前回の「スタティックリンクとダイナミックリンク」</a>では、プログラムがどのように実行ファイルになるかを学びました。今回は、その実行ファイルが動くときに欠かせない<strong>メモリの仕組み</strong>を、C言語を使って体感していきましょう。</p><hr><h2 id=\"%E3%81%AA%E3%81%9Cweb%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2%E3%81%8C%E3%83%A1%E3%83%A2%E3%83%AA%E3%82%92%E7%90%86%E8%A7%A3%E3%81%99%E3%81%B9%E3%81%8D%E3%81%8B%EF%BC%9F\">なぜWebエンジニアがメモリを理解すべきか？</h2><p>普段、Ruby on RailsやNode.js、Goなどで開発していると、メモリを意識することは少ないかもしれません。しかし、こんな場面に遭遇したことはありませんか？</p><ul><li>「メモリリーク」でアプリケーションが落ちた</li><li>「OutOfMemoryError」に悩まされた</li><li>Dockerのメモリ制限で苦労した</li><li>パフォーマンスチューニングで「ヒープ」「スタック」という言葉が出てきた</li></ul><p>これらを理解するためには、メモリの基礎知識が必要です。C言語は、メモリを<strong>直接操作できる</strong>言語なので、メモリの仕組みを学ぶのに最適なんです。</p><hr><h2 id=\"%E3%83%A1%E3%83%A2%E3%83%AA%E3%81%A8%E3%81%AF%E4%BD%95%E3%81%8B%EF%BC%9F\">メモリとは何か？</h2><h3 id=\"%E6%A6%82%E5%BF%B5%E5%9B%B3%E3%81%A7%E7%90%86%E8%A7%A3%E3%81%99%E3%82%8B\">概念図で理解する</h3><p>まず、メモリの全体像を把握しましょう。</p><pre><code>┌─────────────────────────────────────────────────────────────────┐\n│                        物理メモリ（RAM）                          │\n│                                                                 │\n│  メモリは「番地（アドレス）がついた巨大なロッカー」のようなもの      │\n│                                                                 │\n│  ┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐      │\n│  │ 0x00 │ 0x01 │ 0x02 │ 0x03 │ 0x04 │ 0x05 │ 0x06 │ 0x07 │ ...  │\n│  │      │      │      │      │      │      │      │      │      │\n│  │ 1byte│ 1byte│ 1byte│ 1byte│ 1byte│ 1byte│ 1byte│ 1byte│      │\n│  └──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘      │\n│     ↑                                                           │\n│  アドレス（16進数で表される）                                     │\n└─────────────────────────────────────────────────────────────────┘</code></pre><p>ポイントは3つです。</p><ol><li><strong>メモリには「アドレス」という番地がある</strong></li><li><strong>1つのアドレスには（基本は）1バイト（8ビット）のデータが格納できる</strong></li><li><strong>アドレスは通常、16進数（0x...）で表現される</strong></li></ol><p>駅のコインロッカーを想像してみてください。ロッカー番号がアドレスで、中に入れる荷物がデータです。ただし、1つのロッカーに入る荷物は1バイト分だけ。大きなデータは、複数のロッカーにまたがって入れる必要があります。</p><h3 id=\"%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E3%81%8C%E4%BD%BF%E3%81%86%E3%83%A1%E3%83%A2%E3%83%AA%E9%A0%98%E5%9F%9F\">プログラムが使うメモリ領域</h3><p>プログラムが実行されると、OSはそのプログラム専用のメモリ空間を割り当てます。このメモリ空間は、いくつかの領域に分かれています。</p><pre><code>┌─────────────────────────────────────────┐  高いアドレス\n│              スタック領域                │  ← 関数のローカル変数など\n│                  ↓                      │    （自動で確保・解放）\n│                  ↓                      │\n├─────────────────────────────────────────┤\n│                                         │\n│              空き領域                    │\n│                                         │\n├─────────────────────────────────────────┤\n│                  ↑                      │\n│                  ↑                      │\n│              ヒープ領域                  │  ← 動的に確保するメモリ\n│                                         │    （mallocなど）\n├─────────────────────────────────────────┤\n│              BSS領域                     │  ← 初期化されていない\n│          (未初期化データ)                 │    グローバル変数\n├─────────────────────────────────────────┤\n│              データ領域                  │  ← 初期化された\n│           (初期化済みデータ)              │    グローバル変数\n├─────────────────────────────────────────┤\n│              テキスト領域                │  ← プログラムのコード\n│            (コード領域)                  │    （機械語）\n└─────────────────────────────────────────┘  低いアドレス</code></pre><p>今回は特に<strong>スタック領域</strong>と<strong>ヒープ領域</strong>に注目します。この2つの違いがわかると、「なぜメモリリークが起きるのか」がスッキリ理解できるようになります。</p><hr><h2 id=\"%E7%92%B0%E5%A2%83%E6%BA%96%E5%82%99\">環境準備</h2><p>前回と同じく、実際にコードを動かしていきましょう。GCCが必要です。GCCが入っていない方は、ご自身の環境に合わせてググってインストールしてください！</p><pre><code class=\"language-bash\"># バージョン確認\ngcc --version</code></pre><hr><h2 id=\"%E5%A4%89%E6%95%B0%E3%81%A8%E3%83%A1%E3%83%A2%E3%83%AA%E3%82%A2%E3%83%89%E3%83%AC%E3%82%B9\">変数とメモリアドレス</h2><h3 id=\"%E5%A4%89%E6%95%B0%E3%81%AF%E3%83%A1%E3%83%A2%E3%83%AA%E4%B8%8A%E3%81%AE%E3%81%A9%E3%81%93%E3%81%AB%E3%81%82%E3%82%8B%EF%BC%9F\">変数はメモリ上のどこにある？</h3><p>まず、変数がメモリ上のどこに配置されるかを確認してみましょう。</p><p><strong>ファイル名: <code>address.c</code></strong></p><pre><code class=\"language-c\">#include &lt;stdio.h&gt;\n\nint main() {\n    int number = 42;\n    char letter = 'A';\n    double pi = 3.14159;\n    \n    printf(\"=== 変数の値とアドレス ===\\n\\n\");\n    \n    printf(\"int型変数 number\\n\");\n    printf(\"  値: %d\\n\", number);\n    printf(\"  アドレス: %p\\n\", (void*)&amp;number);\n    printf(\"  サイズ: %zu バイト\\n\\n\", sizeof(number));\n    \n    printf(\"char型変数 letter\\n\");\n    printf(\"  値: %c\\n\", letter);\n    printf(\"  アドレス: %p\\n\", (void*)&amp;letter);\n    printf(\"  サイズ: %zu バイト\\n\\n\", sizeof(letter));\n    \n    printf(\"double型変数 pi\\n\");\n    printf(\"  値: %f\\n\", pi);\n    printf(\"  アドレス: %p\\n\", (void*)&amp;pi);\n    printf(\"  サイズ: %zu バイト\\n\", sizeof(pi));\n    \n    return 0;\n}</code></pre><h3 id=\"%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%AB%E3%81%A8%E5%AE%9F%E8%A1%8C\">コンパイルと実行</h3><pre><code class=\"language-bash\">gcc -o address address.c\n./address</code></pre><h3 id=\"%E5%AE%9F%E8%A1%8C%E7%B5%90%E6%9E%9C%E3%81%AE%E4%BE%8B\">実行結果の例</h3><pre><code>=== 変数の値とアドレス ===\n\nint型変数 number\n  値: 42\n  アドレス: 0x7ffd5c8b3a4c\n  サイズ: 4 バイト\n\nchar型変数 letter\n  値: A\n  アドレス: 0x7ffd5c8b3a4b\n  サイズ: 1 バイト\n\ndouble型変数 pi\n  値: 3.141590\n  アドレス: 0x7ffd5c8b3a40\n  サイズ: 8 バイト</code></pre><p>ここで注目すべきなのは、<strong>型によってサイズが違う</strong>こと。<code>int</code>は4バイト、<code>char</code>は1バイト、<code>double</code>は8バイト。そしてアドレスを見ると、変数ごとにちゃんと別々の場所が割り当てられているのがわかります。</p><h3 id=\"%E8%A7%A3%E8%AA%AC\">解説</h3><ul><li><strong><code>&amp;</code>演算子</strong>：変数の前に<code>&amp;</code>をつけると、その変数の<strong>メモリアドレス</strong>が取得できます</li><li><strong><code>%p</code></strong>：アドレスを表示するためのフォーマット指定子です</li><li><strong><code>sizeof</code></strong>：変数や型のサイズ（バイト数）を返します</li></ul><pre><code>メモリのイメージ（上記の例の場合）\n\nアドレス        値              変数名\n0x7ffd5c8b3a40  [3.14159....]   pi (8バイト)\n    :\n0x7ffd5c8b3a4b  ['A']           letter (1バイト)\n0x7ffd5c8b3a4c  [42......]      number (4バイト)\n    ↓\nスタック領域（高いアドレスから低いアドレスへ伸びる）</code></pre><blockquote><strong>💡 Webエンジニア向けメモ</strong></blockquote><blockquote>JavaScriptでは<code>typeof</code>で型を調べますが、メモリサイズは完全に隠蔽されています。 C言語では、型によってメモリの使用量が明確に決まります。 これが「型を意識する」ということの本質です。 Goの<code>unsafe.Sizeof()</code>やRustの<code>std::mem::size_of()</code>でも同じようにサイズを確認できます。</blockquote><hr><h2 id=\"%E3%83%9D%E3%82%A4%E3%83%B3%E3%82%BF%E3%81%AE%E8%B6%85%E5%9F%BA%E7%A4%8E\">ポインタの超基礎</h2><h3 id=\"%E3%83%9D%E3%82%A4%E3%83%B3%E3%82%BF%E3%81%A8%E3%81%AF%EF%BC%9F\">ポインタとは？</h3><p>C言語といえばポインタ！…そして、挫折ポイントでもありますよね。でも恐れることはありません。一言でいうと、</p><blockquote><strong>ポインタ = メモリアドレスを格納する変数</strong></blockquote><p>それだけです。「値そのもの」ではなく「値がある場所（住所）」を覚えておく変数、というだけ。</p><pre><code>┌────────────────────────────────────────────────────────────┐\n│                                                            │\n│   通常の変数                      ポインタ変数              │\n│   ┌─────────┐                   ┌─────────────┐            │\n│   │   42    │                   │ 0x7ffd...4c │            │\n│   └─────────┘                   └──────┬──────┘            │\n│   number                               │                   │\n│   (int型)                              │ 「ここを見て！」   │\n│                                        ↓                   │\n│                                   ┌─────────┐              │\n│                                   │   42    │              │\n│                                   └─────────┘              │\n│                                   number                   │\n│                                                            │\n└────────────────────────────────────────────────────────────┘</code></pre><h3 id=\"%E3%83%9D%E3%82%A4%E3%83%B3%E3%82%BF%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%81%BF%E3%82%88%E3%81%86\">ポインタを使ってみよう</h3><p><strong>ファイル名: <code>pointer_basic.c</code></strong></p><pre><code class=\"language-c\">#include &lt;stdio.h&gt;\n\nint main() {\n    int number = 42;\n    int *ptr = &amp;number;  // numberのアドレスをptrに格納\n    \n    printf(\"=== ポインタの基本 ===\\n\\n\");\n    \n    // 元の変数\n    printf(\"number の値: %d\\n\", number);\n    printf(\"number のアドレス: %p\\n\\n\", (void*)&amp;number);\n    \n    // ポインタ変数\n    printf(\"ptr の値（= numberのアドレス）: %p\\n\", (void*)ptr);\n    printf(\"ptr が指す先の値（*ptr）: %d\\n\", *ptr);\n    printf(\"ptr 自身のアドレス: %p\\n\\n\", (void*)&amp;ptr);\n    \n    // ポインタを使って値を変更\n    printf(\"--- ポインタ経由で値を変更 ---\\n\");\n    *ptr = 100;  // ポインタを通じてnumberの値を変更\n    printf(\"*ptr = 100 を実行後:\\n\");\n    printf(\"number の値: %d\\n\", number);\n    printf(\"*ptr の値: %d\\n\", *ptr);\n    \n    return 0;\n}</code></pre><h3 id=\"%E5%AE%9F%E8%A1%8C%E7%B5%90%E6%9E%9C\">実行結果</h3><pre><code class=\"language-bash\">gcc -o pointer_basic pointer_basic.c\n./pointer_basic</code></pre><pre><code>=== ポインタの基本 ===\n\nnumber の値: 42\nnumber のアドレス: 0x7ffd5c8b3a4c\n\nptr の値（= numberのアドレス）: 0x7ffd5c8b3a4c\nptr が指す先の値（*ptr）: 42\nptr 自身のアドレス: 0x7ffd5c8b3a40\n\n--- ポインタ経由で値を変更 ---\n*ptr = 100 を実行後:\nnumber の値: 100\n*ptr の値: 100</code></pre><p><code>*ptr = 100</code> しただけなのに、<code>number</code>の値も100に変わっています。同じメモリを指しているから当然なんですが、初めて見るとちょっと不思議ですよね。</p><h3 id=\"%E3%83%9D%E3%82%A4%E3%83%B3%E3%82%BF%E3%81%AE%E8%A8%98%E5%8F%B7%E3%81%BE%E3%81%A8%E3%82%81\">ポインタの記号まとめ</h3><p><code>*</code> が宣言時と使用時で意味が変わるのがハマりポイントです。</p><!--kg-card-begin: html--><table class=\"min-w-full border-collapse text-sm leading-[1.7] whitespace-normal\"><thead class=\"text-left\"><tr><th scope=\"col\" class=\"text-text-100 border-b-0.5 border-border-300/60 py-2 pr-4 align-top font-bold\">記号</th><th scope=\"col\" class=\"text-text-100 border-b-0.5 border-border-300/60 py-2 pr-4 align-top font-bold\">意味</th><th scope=\"col\" class=\"text-text-100 border-b-0.5 border-border-300/60 py-2 pr-4 align-top font-bold\">使用例</th></tr></thead><tbody><tr><td class=\"border-b-0.5 border-border-300/30 py-2 pr-4 align-top\"><code class=\"bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">*</code> (宣言時)</td><td class=\"border-b-0.5 border-border-300/30 py-2 pr-4 align-top\">「これはポインタ変数です」</td><td class=\"border-b-0.5 border-border-300/30 py-2 pr-4 align-top\"><code class=\"bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">int *ptr;</code></td></tr><tr><td class=\"border-b-0.5 border-border-300/30 py-2 pr-4 align-top\"><code class=\"bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">*</code> (使用時)</td><td class=\"border-b-0.5 border-border-300/30 py-2 pr-4 align-top\">デリファレンス（参照先の値を取得）</td><td class=\"border-b-0.5 border-border-300/30 py-2 pr-4 align-top\"><code class=\"bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">*ptr = 100;</code></td></tr><tr><td class=\"border-b-0.5 border-border-300/30 py-2 pr-4 align-top\"><code class=\"bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">&amp;</code></td><td class=\"border-b-0.5 border-border-300/30 py-2 pr-4 align-top\">アドレスを取得</td><td class=\"border-b-0.5 border-border-300/30 py-2 pr-4 align-top\"><code class=\"bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">&amp;number</code></td></tr></tbody></table><!--kg-card-end: html--><pre><code>宣言と使用の違い\n\nint *ptr = &amp;number;   ← 宣言時の * は「ポインタ型」を示す\n     ↓\n*ptr = 100;           ← 使用時の * は「参照先にアクセス」を示す</code></pre><blockquote><strong>💡 Webエンジニア向けメモ</strong></blockquote><blockquote>「ポインタ」って難しそうに聞こえますが、実はJavaScriptやRubyでも似た概念はあります。 オブジェクトを変数に代入すると、値のコピーではなく「参照」が渡される、あれです。 C言語のポインタは、その参照を<strong>明示的に操作できる</strong>というだけの話なんです。</blockquote><hr><h2 id=\"%E3%83%A1%E3%83%A2%E3%83%AA%E3%81%AE%E5%8B%95%E7%9A%84%E7%A2%BA%E4%BF%9D%EF%BC%88malloc-free%EF%BC%89\">メモリの動的確保（malloc / free）</h2><h3 id=\"%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%81%A8%E3%83%92%E3%83%BC%E3%83%97\">スタックとヒープ</h3><p>ここまで見てきた変数は、すべて<strong>スタック領域</strong>に確保されていました。スタック領域の変数は、関数が終了すると自動的に解放されます。</p><p>一方、<strong>ヒープ領域</strong>は、プログラマが<strong>明示的に確保・解放</strong>するメモリです。</p><pre><code>┌─────────────────────────────────────────────────────────────┐\n│                                                             │\n│   スタック領域                    ヒープ領域                 │\n│   ─────────────                  ─────────────              │\n│                                                             │\n│   ✅ 自動で確保・解放              ⚠️  手動で確保・解放       │\n│   ✅ 高速                         ⚠️  やや低速               │\n│   ❌ サイズはコンパイル時に決定     ✅ サイズを実行時に決定可能 │\n│   ❌ 関数内でのみ有効              ✅ 関数をまたいで使える     │\n│                                                             │\n│   用途: ローカル変数               用途: 可変長データ、        │\n│        関数の引数                      長期間保持するデータ   │\n│                                                             │\n└─────────────────────────────────────────────────────────────┘</code></pre><p>RubyやJavaScriptでは <code>new</code> するだけでヒープにオブジェクトが作られますが、C言語では自分で「これだけのメモリをください！」とOSにお願いする必要があります。それが <code>malloc()</code> です。</p><h3 id=\"malloc-%E3%81%A8-free\">malloc と free</h3><p><strong>ファイル名: <code>malloc_basic.c</code></strong></p><pre><code class=\"language-c\">#include &lt;stdio.h&gt;\n#include &lt;stdlib.h&gt;  // malloc, free に必要\n\nint main() {\n    printf(\"=== 動的メモリ確保 ===\\n\\n\");\n    \n    // 整数1つ分のメモリを確保\n    // ※C言語ではmallocの戻り値(void*)は暗黙的にキャストされるため、\n    //   キャストは省略可能です。ここでは意図を明示するために記載しています。\n    //  （C++ではキャストが必須です）\n    int *ptr = (int *)malloc(sizeof(int));\n    \n    // 確保できたか確認（重要！）\n    if (ptr == NULL) {\n        printf(\"メモリの確保に失敗しました\\n\");\n        return 1;\n    }\n    \n    printf(\"メモリを確保しました\\n\");\n    printf(\"確保したアドレス: %p\\n\", (void*)ptr);\n    printf(\"確保したサイズ: %zu バイト\\n\\n\", sizeof(int));\n    \n    // 確保したメモリに値を格納\n    *ptr = 12345;\n    printf(\"格納した値: %d\\n\\n\", *ptr);\n    \n    // メモリを解放\n    free(ptr);\n    printf(\"メモリを解放しました\\n\");\n    \n    // 解放後のポインタはNULLにするのが安全\n    ptr = NULL;\n    \n    return 0;\n}</code></pre><h3 id=\"%E5%AE%9F%E8%A1%8C%E7%B5%90%E6%9E%9C-1\">実行結果</h3><pre><code class=\"language-bash\">gcc -o malloc_basic malloc_basic.c\n./malloc_basic</code></pre><pre><code>=== 動的メモリ確保 ===\n\nメモリを確保しました\n確保したアドレス: 0x55a8b6c4a2a0\n確保したサイズ: 4 バイト\n\n格納した値: 12345\n\nメモリを解放しました</code></pre><p><code>malloc</code> → 使う → <code>free</code> の3ステップ。これがC言語のメモリ管理の基本です。<code>free</code> を忘れると大変なことになりますが、それは後ほど</p><h3 id=\"%E9%85%8D%E5%88%97%E3%82%92%E5%8B%95%E7%9A%84%E3%81%AB%E7%A2%BA%E4%BF%9D%E3%81%99%E3%82%8B\">配列を動的に確保する</h3><p>スタック上の配列は <code>int array[5];</code> のようにサイズをコンパイル時に決める必要がありますが、<code>malloc</code> を使えば<strong>実行時にサイズを決められます</strong>。</p><p><strong>ファイル名: <code>malloc_array.c</code></strong></p><pre><code class=\"language-c\">#include &lt;stdio.h&gt;\n#include &lt;stdlib.h&gt;\n\nint main() {\n    int n;\n    printf(\"配列の要素数を入力してください: \");\n    scanf(\"%d\", &amp;n);\n    \n    // n個のint配列を動的に確保\n    int *array = (int *)malloc(sizeof(int) * n);\n    \n    if (array == NULL) {\n        printf(\"メモリの確保に失敗しました\\n\");\n        return 1;\n    }\n    \n    printf(\"\\n%d個のint型配列を確保しました\\n\", n);\n    printf(\"確保したサイズ: %zu バイト\\n\", sizeof(int) * n);\n    printf(\"先頭アドレス: %p\\n\\n\", (void*)array);\n    \n    // 配列に値を格納\n    for (int i = 0; i &lt; n; i++) {\n        array[i] = i * 10;\n    }\n    \n    // 配列の内容とアドレスを表示\n    printf(\"インデックス | 値   | アドレス\\n\");\n    printf(\"-------------|------|------------------\\n\");\n    for (int i = 0; i &lt; n; i++) {\n        printf(\"    %d        | %3d  | %p\\n\", i, array[i], (void*)&amp;array[i]);\n    }\n    \n    // メモリを解放\n    free(array);\n    array = NULL;\n    \n    printf(\"\\nメモリを解放しました\\n\");\n    \n    return 0;\n}</code></pre><p>同様に実行すると？</p><h3 id=\"%E5%AE%9F%E8%A1%8C%E7%B5%90%E6%9E%9C-2\">実行結果</h3><pre><code>配列の要素数を入力してください: 5\n\n5個のint型配列を確保しました\n確保したサイズ: 20 バイト\n先頭アドレス: 0x55a8b6c4a2a0\n\nインデックス | 値   | アドレス\n-------------|------|------------------\n    0        |   0  | 0x55a8b6c4a2a0\n    1        |  10  | 0x55a8b6c4a2a4\n    2        |  20  | 0x55a8b6c4a2a8\n    3        |  30  | 0x55a8b6c4a2ac\n    4        |  40  | 0x55a8b6c4a2b0\n\nメモリを解放しました</code></pre><p>アドレスが4バイトずつ増えているのがわかりますか？ これは <code>int</code> 型が4バイトだからです。メモリ上にきれいに並んでいるのが見えると、配列の正体がよくわかりますね。</p><blockquote><strong>💡 Webエンジニア向けメモ</strong></blockquote><blockquote>GoやRustでは <code>make([]int, n)</code> や <code>Vec::with_capacity(n)</code> のように、 動的にサイズを指定して配列やスライスを作ることがよくあります。 裏側では似たようなヒープへのメモリ確保が行われています。</blockquote><hr><h2 id=\"%E3%83%A1%E3%83%A2%E3%83%AA%E3%83%AA%E3%83%BC%E3%82%AF%E3%82%92%E4%BD%93%E9%A8%93%E3%81%99%E3%82%8B\">メモリリークを体験する</h2><h3 id=\"%E3%83%A1%E3%83%A2%E3%83%AA%E3%83%AA%E3%83%BC%E3%82%AF%E3%81%A8%E3%81%AF%EF%BC%9F\">メモリリークとは？</h3><p><strong>メモリリーク</strong>とは、確保したメモリを解放し忘れることで、使えるメモリが徐々に減っていく現象です。Webアプリの運用でよくある「なぜかメモリ使用量が右肩上がりで、定期的に再起動しないといけない」の原因、だいたいコレです。</p><p><strong>ファイル名: <code>memory_leak.c</code></strong></p><pre><code class=\"language-c\">#include &lt;stdio.h&gt;\n#include &lt;stdlib.h&gt;\n\nvoid bad_function() {\n    // メモリを確保するが、解放しない（これがメモリリーク！）\n    int *ptr = (int *)malloc(sizeof(int) * 1000);\n    if (ptr == NULL) return;\n    *ptr = 42;\n    // free(ptr); ← これがない！\n}\n\nvoid good_function() {\n    int *ptr = (int *)malloc(sizeof(int) * 1000);\n    if (ptr == NULL) return;\n    *ptr = 42;\n    free(ptr);  // ちゃんと解放\n}\n\nint main() {\n    printf(\"=== メモリリークのデモ ===\\n\\n\");\n    \n    printf(\"bad_function を1000回呼び出します...\\n\");\n    for (int i = 0; i &lt; 1000; i++) {\n        bad_function();\n    }\n    printf(\"完了（約4,000,000バイトがリークしています）\\n\\n\");\n    \n    printf(\"good_function を1000回呼び出します...\\n\");\n    for (int i = 0; i &lt; 1000; i++) {\n        good_function();\n    }\n    printf(\"完了（メモリリークなし）\\n\");\n    \n    return 0;\n}</code></pre><p><code>bad_function</code> は1回呼ぶたびに <code>sizeof(int) * 1000 = 4,000バイト</code> をリークします。1,000回呼ぶと約4,000,000バイト（約3.8MB）の漏れです。たった数行の <code>free</code> の書き忘れで、これだけのメモリが無駄になるんですね。</p><h3 id=\"valgrind%E3%81%A7%E3%83%A1%E3%83%A2%E3%83%AA%E3%83%AA%E3%83%BC%E3%82%AF%E3%82%92%E6%A4%9C%E5%87%BA\">Valgrindでメモリリークを検出</h3><p>C言語にはメモリリークを検出する定番ツール <strong>Valgrind</strong> があります。Linuxでは、以下で確認できます！</p><pre><code class=\"language-bash\"># Valgrindのインストール（Ubuntu）\nsudo apt install valgrind\n\n# コンパイル（デバッグ情報付き）\ngcc -g -o memory_leak memory_leak.c\n\n# Valgrindで実行\nvalgrind --leak-check=full ./memory_leak</code></pre><h3 id=\"%E6%A4%9C%E5%87%BA%E7%B5%90%E6%9E%9C\">検出結果</h3><pre><code>==12345== LEAK SUMMARY:\n==12345==    definitely lost: 4,000,000 bytes in 1,000 blocks\n==12345==    indirectly lost: 0 bytes in 0 blocks\n==12345==      possibly lost: 0 bytes in 0 blocks\n==12345==    still reachable: 0 bytes in 0 blocks\n==12345==         suppressed: 0 bytes in 0 blocks</code></pre><p><code>definitely lost: 4,000,000 bytes</code> — バッチリ検出されました！ こんなふうに、C言語ではメモリの問題を直接確認できます。</p><blockquote><strong>💡 Webエンジニア向けメモ</strong></blockquote><blockquote>Node.jsでも同様のメモリリークは起こります。例えば、グローバル変数に配列を追加し続けたり、イベントリスナーを解除し忘れたり、クロージャで大きなオブジェクトを参照し続けたり。原理は同じで、「使わなくなったメモリが解放されない」ことが問題です。</blockquote><blockquote>Node.jsの場合は <code>--inspect</code> フラグをつけて起動し、Chrome DevToolsの「Memory」タブでHeap Snapshotを取ると、Valgrindのようにリークを調査できます。Goなら <code>pprof</code> が同様のツールになります。</blockquote><hr><h2 id=\"%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF%E3%81%A8%E3%83%92%E3%83%BC%E3%83%97%E3%81%AE%E9%81%95%E3%81%84%E3%82%92%E5%AE%9F%E6%84%9F%E3%81%99%E3%82%8B\">スタックとヒープの違いを実感する</h2><h3 id=\"%E3%83%80%E3%83%B3%E3%82%B0%E3%83%AA%E3%83%B3%E3%82%B0%E3%83%9D%E3%82%A4%E3%83%B3%E3%82%BF%E3%81%AE%E5%8D%B1%E9%99%BA\">ダングリングポインタの危険</h3><p>最後に、スタックとヒープの違いが生む典型的なバグを見てみましょう。</p><p><strong>ダングリングポインタ</strong>とは、すでに無効になったメモリを指すポインタのことです。「存在しない住所が書かれたメモ」みたいなものですね。</p><p><strong>ファイル名: <code>dangling.c</code></strong></p><pre><code class=\"language-c\">#include &lt;stdio.h&gt;\n#include &lt;stdlib.h&gt;\n\n// ダメな例：スタック上の変数のアドレスを返す\nint* bad_get_number() {\n    int local = 42;\n    return &amp;local;  // ⚠️ 関数終了後、localは無効になる\n}\n\n// 良い例：ヒープ上にメモリを確保して返す\nint* good_get_number() {\n    int *ptr = (int *)malloc(sizeof(int));\n    if (ptr == NULL) return NULL;\n    *ptr = 42;\n    return ptr;  // ✅ ヒープ上なので関数終了後も有効\n}\n\nint main() {\n    printf(\"=== ダングリングポインタのデモ ===\\n\\n\");\n    \n    printf(\"【危険】スタック上の変数を返す場合:\\n\");\n    int *bad = bad_get_number();\n    printf(\"アドレス: %p\\n\", (void*)bad);\n    // ⚠️ この行は未定義動作です！何が表示されるかわかりません\n    // コンパイラの最適化レベルによって挙動が変わります\n    printf(\"値（未定義動作！）: %d\\n\\n\", *bad);\n    \n    printf(\"【安全】ヒープ上のメモリを返す場合:\\n\");\n    int *good = good_get_number();\n    if (good != NULL) {\n        printf(\"アドレス: %p\\n\", (void*)good);\n        printf(\"値: %d\\n\", *good);\n        free(good);  // 使い終わったら解放\n    }\n    \n    return 0;\n}</code></pre><h3 id=\"%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%AB%E6%99%82%E3%81%AE%E8%AD%A6%E5%91%8A\">コンパイル時の警告</h3><pre><code class=\"language-bash\"># 最適化なしでコンパイル（未定義動作の挙動を確認するため）\ngcc -Wall -O0 -o dangling dangling.c</code></pre><pre><code>dangling.c: In function 'bad_get_number':\ndangling.c:7:12: warning: function returns address of local variable [-Wreturn-local-addr]\n    7 |     return &amp;local;\n      |            ^~~~~~</code></pre><p>コンパイラが警告してくれますね！ <code>-Wall</code> オプションをつけてコンパイルすると、こうした危険なコードを事前にキャッチできます。</p><p>この例がまさに「スタック vs ヒープ」の違いです。スタック上の変数（<code>local</code>）は関数を抜けた瞬間に無効になりますが、ヒープ上のメモリ（<code>malloc</code> で確保したもの）は <code>free</code> するまで有効。だからこそ、ヒープに確保して返すのが正しいパターンなんです。ただし、前述のメモリリークのように、freeを忘れないようにしないといけません！</p><blockquote><strong>💡 Webエンジニア向けメモ</strong></blockquote><blockquote>GoやRustでは、この種のバグはコンパイラが防いでくれます。 Goでは「エスケープ解析」によって、関数外に渡される変数は自動的にヒープに配置されます。 <code>go build -gcflags=\"-m\"</code> で確認できるので、興味のある方はぜひ試してみてください。 Rustではさらに厳密で、所有権とライフタイムの仕組みによって、ダングリングポインタの発生をコンパイル時に完全に防いでいます。</blockquote><hr><h2 id=\"%E3%81%BE%E3%81%A8%E3%82%81\">まとめ</h2><p>今回学んだことを整理しましょう。</p><pre><code>┌─────────────────────────────────────────────────────────────────┐\n│                        今回のまとめ                             │\n├─────────────────────────────────────────────────────────────────┤\n│                                                                 │\n│  1. メモリにはアドレスがある                                     │\n│     → &amp; でアドレスを取得、%p で表示                              │\n│                                                                 │\n│  2. ポインタ = アドレスを格納する変数                            │\n│     → int *ptr = &amp;number;                                       │\n│     → *ptr でポインタが指す値にアクセス                          │\n│                                                                 │\n│  3. 動的メモリ確保                                               │\n│     → malloc() で確保、free() で解放                             │\n│     → 解放を忘れるとメモリリーク                                 │\n│                                                                 │\n│  4. スタック vs ヒープ                                           │\n│     → スタック：自動管理、関数内で有効                           │\n│     → ヒープ：手動管理、プログラム全体で有効                     │\n│                                                                 │\n└─────────────────────────────────────────────────────────────────┘</code></pre><h3 id=\"web%E7%B3%BB%E3%81%A3%E3%81%BD%E3%81%84%E8%A8%80%E8%AA%9E%E3%81%A8%E3%81%AE%E5%AF%BE%E5%BF%9C\">Web系っぽい言語との対応</h3><p>C言語で学んだ概念は、普段使っている言語にも対応しています。</p><!--kg-card-begin: html--><table class=\"min-w-full border-collapse text-sm leading-[1.7] whitespace-normal\"><thead class=\"text-left\"><tr><th scope=\"col\" class=\"text-text-100 border-b-0.5 border-border-300/60 py-2 pr-4 align-top font-bold\">C言語</th><th scope=\"col\" class=\"text-text-100 border-b-0.5 border-border-300/60 py-2 pr-4 align-top font-bold\">Web系っぽい言語での対応</th></tr></thead><tbody><tr><td class=\"border-b-0.5 border-border-300/30 py-2 pr-4 align-top\"><code class=\"bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">malloc()</code></td><td class=\"border-b-0.5 border-border-300/30 py-2 pr-4 align-top\"><code class=\"bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">new Object()</code> / オブジェクト生成</td></tr><tr><td class=\"border-b-0.5 border-border-300/30 py-2 pr-4 align-top\"><code class=\"bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]\">free()</code></td><td class=\"border-b-0.5 border-border-300/30 py-2 pr-4 align-top\">GC（ガベージコレクション）が自動で行う</td></tr><tr><td class=\"border-b-0.5 border-border-300/30 py-2 pr-4 align-top\">ポインタ</td><td class=\"border-b-0.5 border-border-300/30 py-2 pr-4 align-top\">参照（Reference）</td></tr><tr><td class=\"border-b-0.5 border-border-300/30 py-2 pr-4 align-top\">スタック</td><td class=\"border-b-0.5 border-border-300/30 py-2 pr-4 align-top\">プリミティブ型の値（※言語による）</td></tr><tr><td class=\"border-b-0.5 border-border-300/30 py-2 pr-4 align-top\">ヒープ</td><td class=\"border-b-0.5 border-border-300/30 py-2 pr-4 align-top\">オブジェクト / 配列（※言語による）</td></tr></tbody></table><!--kg-card-end: html--><p>※ スタックとヒープの割り当ては言語や実装によって異なります。例えばGoではエスケープ解析によって、見た目はローカル変数でもヒープに配置されることがあります。上記はJavaでの一般的なイメージとして参考にしてください。</p><h3></h3><p>前回のリンクの話と今回のメモリの話で、だいぶコンピュータの「裏側」が見えてきたのではないでしょうか？ この知識があると、Dockerのリソース制限とかOOMKilledとか、「あ、あれのことね」とピンとくるようになりますよ！</p><p>たぶん！</p><p>こちらからは以上です。</p>","url":"https://ghost.tech.anti-pattern.co.jp/imasarali-jie-suru-sutateitukurinku-to-dainamitukurinku-webapurikesiyonenzinianotamenodi-reiyaru-men-sirizu-sono1/","canonical_url":null,"uuid":"cf1eb7b4-97b2-48b1-bd1f-52c311a96dc8","page":null,"codeinjection_foot":null,"codeinjection_head":null,"codeinjection_styles":null,"comment_id":"69c3346171fa390001b3661c","reading_time":9}},"pageContext":{"slug":"imasarali-jie-suru-sutateitukurinku-to-dainamitukurinku-webapurikesiyonenzinianotamenodi-reiyaru-men-sirizu-sono1"}},
    "staticQueryHashes": ["176528973","2358152166","2561578252","2731221146","4145280475"]}