【2026年7月最新】C言語ポインタ完全ガイド|基本・配列・関数・構造体の使い方とClaude Code活用

【2026年7月最新】C言語ポインタ完全ガイド|基本・配列・関数・構造体の使い方とClaude Code活用

C言語の学習で多くの人が最初にぶつかる壁がポインタです。「何を指しているのかわからない」「*と&が混乱する」「セグメンテーションフォールトが出てプログラムが落ちる」という悩みは、C言語学習者のあるあるです。

ポインタが難しく感じる最大の理由は、「変数の値」ではなく「変数のメモリアドレス」を扱うという概念の転換が必要だからです。この記事では、メモリアドレスの視覚的な理解から始めて、配列・関数・構造体でのポインタ活用パターン・よくあるエラーの対策・Claude Codeでのデバッグ支援まで解説します。

AI鬼管理山崎 AI鬼管理山崎
ポインタは「箱の場所を覚えておく紙切れ」とイメージすると理解しやすいです。変数が「値の入った箱」だとすると、ポインタは「その箱がどこにあるかの住所が書かれた紙切れ」です。住所を使って箱の中身を読み書きするのが間接参照(*演算子)です。
✔️ポインタとは何か——メモリアドレスを格納する変数という理解
✔️&演算子(アドレス取得)*演算子(間接参照)の使い方
✔️配列とポインタの等価性——array[i]と*(array+i)が同じ理由
✔️関数へのポインタ渡しで値を呼び出し元に返す方法
✔️構造体ポインタとアロー演算子(->)の使い方
✔️ダブルポインタ(**ptr)の概念と使い所
✔️セグメンテーションフォールト」の主な原因と防止策
✔️Claude Codeでポインタ問題を診断・修正する方法
📌 この記事の結論
【2026年7月最新】C言語ポインタ完全ガイド|基本・配列・関数・構造体の使い方とClaude Code活用
C言語のポインタを図解で完全解説。変数とメモリアドレスの基本、&演算子と*演算子の意味、ポインタと配列の等価性、関数へのポインタ渡し、構造体ポインタ、ダブルポインタ、よくあるセグメンテーションフォールトの原因と対策、Claude Codeでのデバッグ支援まで網羅。

01 変数とメモリアドレス|ポインタが指すもの メモリの仕組みとポインタ変数の意味を図解で理解する

プログラムが実行されると、変数はメモリ(RAM)上の特定の場所に格納されます。各メモリの場所にはアドレス(番地)と呼ばれる一意の番号があります。ポインタ変数は、この「アドレス(番号)」を値として格納する特別な変数です。

📚 用語解説

メモリアドレス:メモリ(RAM)の各バイトに割り当てられた一意の番号。通常16進数で表示される(例: 0x7ffee4b)。プログラムが変数を宣言すると、OSがメモリ上の空きアドレスを割り当てて変数を格納する。int型は4バイト、char型は1バイト、double型は8バイトなど、型によって占有するバイト数が決まる。ポインタはこのアドレスを値として保持する変数で、「そのアドレスにある値」を読み書きできる。

#include 

int main(void) {
    int x = 42;        // 変数xに42を格納
    int *p;            // int型のポインタ変数pを宣言
    p = &x;            // &x = xのアドレスをpに代入

    printf("xの値: %d\n", x);          // → 42
    printf("xのアドレス: %p\n", &x);   // → 0x7ffee4b2c9ac(例)
    printf("pの値: %p\n", p);          // → 0x7ffee4b2c9ac(xと同じ)
    printf("pが指す値: %d\n", *p);     // → 42(間接参照)

    return 0;
}
アドレス
説明
0x7ffe...ac
42
int x(変数xの実体)
0x7ffe...b0
0x7ffe...ac
int *p(xのアドレスを格納)

このメモリ図のポイントを押さえましょう。xはアドレス0x7ffe...acに値42を格納しています。pは別のアドレス0x7ffe...b0xのアドレス(0x7ffe...ac)を格納しています。*pは「pが指すアドレスに行って値を読む」という意味です。

02 &演算子(アドレス取得)と*演算子(間接参照)の使い方 宣言と使用で意味が違う*演算子の二重の役割を理解する

⚠️ *の2つの意味を混同しないこと

* 演算子はC言語で2つの意味を持ちます。①ポインタ宣言: int *p; のように型名の後に書くと「pはint型ポインタ変数」という宣言になります。②間接参照: 式の中で *p と書くと「pが指すアドレスの値」を意味します。どちらの*かは文脈(宣言か式か)で判断します。

#include 

int main(void) {
    int x = 10;
    int y = 20;

    /* --- ポインタ宣言と代入 --- */
    int *p;         // pはint型ポインタ(宣言時の*)
    p = &x;         // &x = xのアドレスをpに代入

    /* --- 間接参照(値の読み書き) --- */
    printf("%d\n", *p);   // → 10(xの値を間接参照)
    *p = 99;               // pが指すアドレスの値を変更(xが99になる)
    printf("%d\n", x);    // → 99(xが書き換えられた)

    /* --- ポインタの差し替え --- */
    p = &y;          // pがyを指すように変更
    printf("%d\n", *p);   // → 20(yの値)

    /* --- アドレス演算 --- */
    int arr[3] = {10, 20, 30};
    int *ap = arr;   // 配列の先頭アドレス
    printf("%d\n", *ap);       // → 10 (arr[0])
    printf("%d\n", *(ap + 1)); // → 20 (arr[1])
    printf("%d\n", *(ap + 2)); // → 30 (arr[2])

    return 0;
}

📚 用語解説

アドレス演算子(&)と間接参照演算子(*):&変数名: 変数のメモリアドレスを返す演算子。「の住所を取得する」という意味。*ポインタ変数: ポインタが指すアドレスの値を読み書きする演算子。「その住所の家(メモリ)の中身を見る」という意味。NULLポインタ(何も指していない状態)に*を適用するとセグメンテーションフォールトが発生するため、*の前にNULLチェックが必要。

操作記述例意味
ポインタ宣言int *p;pはint型を指すポインタ変数
アドレス取得p = &x;xのアドレスをpに代入
間接参照(読み)*ppが指すアドレスの値を読む
間接参照(書き)*p = 99;pが指すアドレスに99を書く
ポインタの移動p++;pを次の要素のアドレスに進める(int型なら4バイト進む)

03 ポインタと配列の等価性 array[i]は*(array+i)と同じ。配列名は先頭要素へのポインタ

C言語では配列名は先頭要素へのポインタとして機能します。これによりarray[i]*(array+i)は完全に等価です。この「配列とポインタの等価性」はC言語の重要な概念です。

📚 用語解説

配列名とポインタの等価性:C言語では配列名(例: arr)はその配列の先頭要素(arr[0])のアドレスを表す定数ポインタとして扱われる。ただし変数ではないためarrに別のアドレスを代入したり(arr = &x は不可)、arr++もできない。int *p = arr; と書くとpはarrと同じ先頭アドレスを指す変数ポインタになる(p++やp = &x は可能)。配列要素arr[i]へのアクセスはコンパイラが内部的に*(arr+i)に変換する。

#include 

int main(void) {
    int arr[5] = {10, 20, 30, 40, 50};
    int *p = arr;   // 先頭要素のアドレスをpに代入

    /* --- 3つの等価な書き方 --- */
    printf("%d\n", arr[2]);     // → 30(添え字記法)
    printf("%d\n", *(arr + 2)); // → 30(ポインタ演算)
    printf("%d\n", *(p + 2));   // → 30(ポインタ変数使用)
    printf("%d\n", p[2]);       // → 30(ポインタ変数への添え字)

    /* --- ポインタ変数はインクリメントできる --- */
    p = arr;      // 先頭に戻す
    printf("%d\n", *p);    // → 10
    p++;
    printf("%d\n", *p);    // → 20
    p++;
    printf("%d\n", *p);    // → 30

    /* --- ポインタで配列を逆順に出力 --- */
    p = &arr[4];  // 末尾を指す
    int i;
    for (i = 4; i >= 0; i--) {
        printf("%d ", *p);
        p--;
    }
    printf("\n");  // → 50 40 30 20 10

    return 0;
}
配列名arr
先頭要素arr[0]の
アドレスを保持
arr+i でアドレス計算
i番目要素のアドレス
= 先頭 + i×型サイズ
*(arr+i) で値取得
arr[i]と完全に同じ
コンパイラが変換

04 関数へのポインタ渡し(call by reference) 値渡しと参照渡しの違い。swap関数で確実に理解する

C言語の関数は通常値渡し(call by value)で動作します。これは「引数の値のコピーが関数に渡される」ということで、関数内で引数を変更しても呼び出し元の変数には影響しません。ポインタ渡し(call by reference)を使うと、関数内から呼び出し元の変数を直接変更できます。

#include 

/* --- 値渡し(関数内の変更が呼び出し元に反映されない)--- */
void swap_wrong(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
    /* ここで交換されるのはaとbのコピー。呼び出し元は変わらない */
}

/* --- ポインタ渡し(関数内から呼び出し元を変更できる)--- */
void swap_correct(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
    /* *a, *bは呼び出し元の変数を直接操作している */
}

int main(void) {
    int x = 10, y = 20;

    /* 値渡し(交換されない)*/
    swap_wrong(x, y);
    printf("値渡し後: x=%d, y=%d\n", x, y);  // → x=10, y=20(変わらない)

    /* ポインタ渡し(正しく交換される)*/
    swap_correct(&x, &y);   // アドレスを渡す
    printf("ポインタ渡し後: x=%d, y=%d\n", x, y);  // → x=20, y=10(交換された)

    return 0;
}
💡 関数から複数の値を返す方法

C言語の関数はreturnで1つの値しか返せません。複数の値を「返す」には①ポインタ引数で呼び出し元の変数を直接変更する(swap_correctパターン)②構造体を返す の2つが一般的です。例えば「最大値と最小値を同時に返す」関数は void minmax(int arr[], int n, int *min, int *max) のようにポインタ引数2つを受け取る形で実装します。

05 構造体ポインタとアロー演算子 (*ptr).memberより ptr->memberが簡潔で推奨

📚 用語解説

アロー演算子(->):構造体ポインタのメンバへアクセスする演算子。ptr->memberと書くと(*ptr).memberと同じ意味になる。「まずポインタを間接参照して構造体にアクセスし、そのメンバを取得する」という2ステップを1つの演算子で表現する。(*ptr).memberは括弧が必要(.の優先順位が*より高いためドット演算子から先に評価されるのを防ぐため)。->のほうが読みやすいため実務ではほぼ->を使う。

#include 
#include 

typedef struct {
    char name[50];
    int age;
    double score;
} Student;

void print_student(Student *s) {
    /* アロー演算子でメンバにアクセス */
    printf("名前: %s\n", s->name);
    printf("年齢: %d\n", s->age);
    printf("スコア: %.1f\n", s->score);

    /* (*s).name と同じ意味(()が必要)*/
    /* printf("名前: %s\n", (*s).name); */
}

void update_score(Student *s, double new_score) {
    s->score = new_score;   /* 呼び出し元の構造体を直接変更 */
}

int main(void) {
    Student taro;
    strcpy(taro.name, "田中太郎");
    taro.age = 20;
    taro.score = 85.5;

    /* ポインタで関数に渡す(構造体全体のコピーを避けられる)*/
    print_student(&taro);
    update_score(&taro, 92.0);

    printf("更新後スコア: %.1f\n", taro.score);  /* → 92.0 */

    /* ポインタ変数を使う場合 */
    Student *sp = &taro;
    printf("spからアクセス: %s, %d歳\n", sp->name, sp->age);

    return 0;
}
代表菅澤 代表菅澤
構造体をポインタで関数に渡すメリットは「コピーのコスト削減」です。構造体が大きくなると値渡しは毎回構造体全体をコピーするため遅くなります。ポインタ渡しはアドレス(8バイト)だけを渡すため高速です。ただし、呼び出し先で誤って変更されないよう、読み取り専用の場合はconst Student *sと宣言します。

06 ポインタのポインタ(ダブルポインタ) ポインタ自体のアドレスを保持する**ptrの使い所

📚 用語解説

ダブルポインタ(**ptr):ポインタのアドレスを格納するポインタ。int **pp; と宣言するとppはint*型ポインタ変数のアドレスを保持する。*ppはポインタ変数(アドレス)、**ppはそのポインタが指す値にアクセスできる。主な使い所: ①関数内でポインタ変数自体を変更したい場合(関数引数でint **を受け取り*pp = 新しいアドレスとする)②2次元配列の動的確保 ③ポインタ配列(char *argv[]はchar **argvと等価)

#include 
#include 

/* 関数内でポインタを変更する(ダブルポインタが必要)*/
void allocate_buffer(int **pp, int size) {
    *pp = (int *)malloc(sizeof(int) * size);  /* *ppに新しいアドレスを代入 */
    if (*pp == NULL) {
        return;
    }
    int i;
    for (i = 0; i < size; i++) {
        (*pp)[i] = i * 10;  /* 0, 10, 20, ... */
    }
}

int main(void) {
    int x = 42;
    int *p = &x;    /* pはxを指す */
    int **pp = &p;  /* ppはpのアドレスを指す */

    printf("xの値: %d\n", x);      /* → 42 */
    printf("*p: %d\n", *p);         /* → 42 */
    printf("**pp: %d\n", **pp);     /* → 42 */

    /* **ppを通じてxの値を変更 */
    **pp = 99;
    printf("変更後x: %d\n", x);    /* → 99 */

    /* 動的メモリ確保(ダブルポインタで関数から返す)*/
    int *buf = NULL;
    allocate_buffer(&buf, 5);  /* &bufを渡してbuf自体を変更させる */

    if (buf != NULL) {
        int i;
        for (i = 0; i < 5; i++) {
            printf("%d ", buf[i]);   /* → 0 10 20 30 40 */
        }
        printf("\n");
        free(buf);   /* 動的確保したメモリは必ずfreeで解放 */
    }

    return 0;
}

07 よくあるセグメンテーションフォールトと対策 初期化忘れ・NULLポインタ・範囲外アクセスの3大原因を防ぐ

⚠️ Segmentation fault の主な原因

①未初期化ポインタ(ゴミアドレス)への間接参照: int *p; *p = 10; ②NULLポインタへの間接参照: int *p = NULL; *p = 10; ③配列の範囲外アクセス: int arr[5]; arr[10] = 0; ④解放済みメモリへのアクセス(use-after-free): free(p); *p = 10; これらはすべてプログラムが不正なメモリアドレスにアクセスしようとしているため、OSによって強制終了させられる。

#include 
#include 

int main(void) {

    /* --- NG例1: 未初期化ポインタ --- */
    /* int *p;            pはゴミアドレス(不定) */
    /* *p = 10;           → Segmentation fault */

    /* OK: 必ず初期化する */
    int x = 10;
    int *p = &x;      /* 有効なアドレスを代入 */
    *p = 20;          /* OK */

    /* --- NG例2: NULLポインタ参照 --- */
    /* int *q = NULL;     NULLは無効なアドレス(0番地)*/
    /* *q = 10;           → Segmentation fault */

    /* OK: NULLチェックをしてから使う */
    int *q = NULL;
    if (q != NULL) {
        *q = 10;
    } else {
        printf("qはNULLです\n");
    }

    /* --- NG例3: malloc失敗時 --- */
    int *buf = (int *)malloc(sizeof(int) * 1000000000);
    /* mallocが失敗するとNULLが返る */
    if (buf == NULL) {
        printf("メモリ確保失敗\n");
        return 1;   /* 異常終了 */
    }
    /* NULLチェック後にアクセスする */
    buf[0] = 42;
    free(buf);

    /* --- NG例4: 解放済みメモリへのアクセス --- */
    int *fp = (int *)malloc(sizeof(int));
    *fp = 100;
    free(fp);
    /* *fp = 200;  → use-after-free(未定義動作)*/
    fp = NULL;    /* freeの直後にNULLを代入する習慣 */

    return 0;
}
ポインタを宣言
int *p;
有効アドレスを代入
&変数 or malloc()
NULLのまま使わない
NULLチェック
if (p != NULL)
してから参照
範囲内アクセス
配列は0〜n-1
オーバーラン禁止
free後はNULL代入
fp = NULL
use-after-free防止

08 Claude Codeでポインタ問題をデバッグする エラーメッセージとコードを貼るだけで原因と修正箇所を特定

C言語のポインタ周りのバグは「エラーメッセージが出ない(Segfault)」「コンパイルは通るが動作がおかしい」という形で表れることが多く、原因特定が難しいです。Claude Codeにエラーメッセージとコードを貼り付けることで、ポインタの問題を素早く診断できます。

症状・質問Claude Codeへの指示例
Segmentation fault「以下のCコードがSegmentation faultで落ちます。原因と修正箇所を教えてください。[コードを貼り付け]」
ポインタの書き方「Cでswap関数をポインタ渡しで実装したい。引数の受け取り方と呼び出し元での使い方を教えて。」
メモリリーク確認「このCコードでmalloc/freeが正しく対応しているか確認して。メモリリークがあれば指摘して。[コードを貼り付け]」
構造体ポインタ「Cでtypedef structした構造体のポインタを関数に渡して内部でメンバを更新するコードを書いて。」
AI鬼管理山崎 AI鬼管理山崎
ポインタのSegfaultはClaude Codeが特に得意とするデバッグです。「Segmentation fault(core dumped)が出る」「NULLポインタ参照のようです」という一言と一緒にコードを貼るだけで、どの行が問題か・なぜそうなるか・どう修正するかまで返ってきます。

09 まとめ:C言語ポインタ理解の確認チェックリスト 基本から応用まで理解のポイントを整理

✔️ポインタは変数のメモリアドレスを格納する変数。*と&の意味を区別する
✔️&xはxのアドレスを取得。*pはpが指すアドレスの値を読み書き
✔️配列名は先頭要素へのポインタ。array[i]と*(array+i)は等価
✔️関数内で呼び出し元の変数を変更するにはポインタ渡し(&変数)が必要
✔️構造体ポインタのメンバアクセスは->演算子((*ptr).memberと同じ)
✔️ダブルポインタ**はポインタ変数自体を関数から変更したいときに使う
✔️Segfaultの主因は未初期化・NULL・範囲外・use-after-freeの4つ
✔️Segfaultはコードと症状をClaude Codeに貼れば原因と修正箇所が即座に返ってくる

C言語・組み込み開発をAIで効率化したい方へ

ポインタのデバッグに限らず、C言語・組み込み開発でのバグ診断・コードレビュー・テスト生成はClaude Codeとの組み合わせで大幅に効率化できます。
AI鬼管理では、ソフトウェア開発のAI活用設計・実装支援を行っています。

代表菅澤 代表菅澤
「Segfaultが出るけど原因が分からない」「ポインタを渡して値を変えたいがうまくいかない」という相談は開発現場でも多くあります。まずは無料相談でお気軽にご相談ください。

NEXT STEP

この記事の内容を、あなたのビジネスで
実践してみませんか?

AI活用を自社で回せるようになりたい方へ

AI鬼管理

Claude Code・Cowork導入支援から業務設計・社内浸透まで実践ベースで伴走。「自社で回せる組織」を90日で作る経営者向けトレーニング。

よくある質問

Q. C言語のポインタとは何ですか?わかりやすく教えてください。

A. ポインタは「変数のメモリアドレス(番地)を格納する変数」です。変数が「値の入った箱」だとすれば、ポインタは「その箱がどこにあるかの住所が書かれた紙切れ」のイメージです。int x = 42; と宣言するとxはメモリ上の特定の場所(例: アドレス0x1000)に42を格納します。int *p = &x; と書くとpは「xがあるアドレス(0x1000)」を値として保持します。*pで「pが指すアドレスの値(42)」を読み書きできます。

Q. C言語の&と*の違いは何ですか?

A. &は「アドレスを取得する演算子(アドレス演算子)」です。&xと書くとxが格納されているメモリのアドレスが返ります。*は2つの意味があります。①ポインタ宣言: int *p; のように型名の後に書くと「pはintのポインタ」という宣言。②間接参照: 式の中で*pと書くと「pが指すアドレスの値を読み書きする」という操作。宣言か式かで意味が変わることが混乱の原因です。

Q. C言語でSwap関数を作るにはどうすればいいですか?

A. 値渡しではなくポインタ渡しで実装します。void swap(int *a, int *b) { int temp = *a; *a = *b; *b = temp; } と書き、呼び出し元ではswap(&x, &y);のようにアドレスを渡します。値渡し(void swap(int a, int b))だと関数内でコピーが交換されるだけで呼び出し元のxとyは変わりません。引数に&を付けてアドレスを渡し、関数内で*a, *bとして間接参照することで呼び出し元の変数を直接変更できます。

Q. Segmentation faultとは何ですか?どうすれば防げますか?

A. 「セグメンテーションフォールト(Segfault)」はプログラムが不正なメモリアドレスにアクセスしようとしたときにOSが強制終了させるエラーです。主な原因は①未初期化ポインタ(int *p; *p = 10;)②NULLポインタへの参照(int *p = NULL; *p = 10;)③配列の範囲外アクセス(int arr[5]; arr[10] = 0;)④free後のメモリアクセスの4つです。防止策はポインタを必ず有効なアドレスで初期化すること、使う前にNULLチェックすること、free直後にNULLを代入することです。

Q. 構造体ポインタでのアロー演算子(->)はなぜ必要ですか?

A. 構造体ポインタのメンバにアクセスするには「ポインタを間接参照して構造体にアクセスし、そのメンバを取得する」という2ステップが必要です。ドット演算子.の優先順位が*より高いため、*ptr.memberと書くと*(ptr.member)と解釈されてしまいます。正しくは(*ptr).memberですが、括弧が必要で読みにくいです。->演算子は(*ptr).memberを簡潔に書くための糖衣構文で、ptr->memberと書けます。実務ではほぼ->を使います。

Q. C言語のポインタの問題をClaude Codeでデバッグするにはどうすれば良いですか?

A. コンパイルエラーやSegfaultが出た場合は「以下のCコードでSegmentation faultが発生します。原因と修正方法を教えてください」という一文と一緒にコード全体を貼り付けます。警告メッセージ(gcc -Wallの出力)も貼ると原因特定が速くなります。「ポインタを関数に渡して値を変えたいがうまくいかない」という場合は現在のコードと期待する動作を説明するだけで、値渡しとポインタ渡しの違いを含めた修正コードが返ってきます。

AIAI鬼管理

AI鬼管理へのお問い合わせ

この記事を読んで気になった方へ。
AI鬼管理の専門スタッフが、御社に最適な
業務自動化プランを無料でご提案します。

会社名を入力してください
業種を選択してください
お名前を入力してください
正しいメールアドレスを入力してください

1つ以上選択してください
1つ以上選択してください
月額コストを選択してください

約1時間のオンライン面談(Google Meet)です

空き枠を取得中...
面談日時を選択してください

予約確定後、Google Calendarの招待メールをお届けします。
しつこい営業は一切ございません。

監修 最終更新日: 2026年7月19日
菅澤孝平
菅澤 孝平 株式会社GENAI 代表取締役
  • AI業務自動化サービス「AI鬼管理」を運営 — Claude Code を活用し、経営者の業務を「AIエージェントに任せる仕組み」へ転換するパーソナルトレーニングを 伴走構築 で提供。日報・採用・問い合わせ対応・経費精算・議事録・データ集計・営業リスト等の定型業務を、AIに代行させる体制を経営者と一緒に作り込む
  • Claude Code 実装ノウハウを 経営者・法人クライアント に直接指導。生成AIを「便利ツール」ではなく 「業務を任せる存在」 として運用する手法を体系化
  • 「やらせ切る管理」メソッドの開発者。シンゲキ株式会社(2021年設立・鬼管理専門塾運営)にて累計3,000名以上の学習者を志望校合格に導いた管理メソッドを、AI × 経営者支援 に転用
  • 著書『3カ月で志望大学に合格できる鬼管理』(幻冬舎)、『親の過干渉こそ、最強の大学受験対策である。』(講談社)
  • メディア出演: REAL VALUE / カンニング竹山のイチバン研究所 / ええじゃないかBiz 他
  • 明治大学政治経済学部卒
現在は AI鬼管理(Claude Code活用の伴走型パーソナルトレーニング)を主事業とし、経営者と二人三脚で「AIに業務を任せる仕組み」を実装。「実行を強制する環境」を AI で構築する手法を、自社の実運用知見をもとに発信している。