【2026年7月最新】C言語ポインタ完全ガイド|基本・配列・関数・構造体の使い方とClaude Code活用
この記事の内容
C言語の学習で多くの人が最初にぶつかる壁がポインタです。「何を指しているのかわからない」「*と&が混乱する」「セグメンテーションフォールトが出てプログラムが落ちる」という悩みは、C言語学習者のあるあるです。
ポインタが難しく感じる最大の理由は、「変数の値」ではなく「変数のメモリアドレス」を扱うという概念の転換が必要だからです。この記事では、メモリアドレスの視覚的な理解から始めて、配列・関数・構造体でのポインタ活用パターン・よくあるエラーの対策・Claude Codeでのデバッグ支援まで解説します。
01 MEMORY 変数とメモリアドレス|ポインタが指すもの メモリの仕組みとポインタ変数の意味を図解で理解する
プログラムが実行されると、変数はメモリ(RAM)上の特定の場所に格納されます。各メモリの場所にはアドレス(番地)と呼ばれる一意の番号があります。ポインタ変数は、この「アドレス(番号)」を値として格納する特別な変数です。
📚 用語解説
メモリアドレス:メモリ(RAM)の各バイトに割り当てられた一意の番号。通常16進数で表示される(例: 0x7ffee4b)。プログラムが変数を宣言すると、OSがメモリ上の空きアドレスを割り当てて変数を格納する。int型は4バイト、char型は1バイト、double型は8バイトなど、型によって占有するバイト数が決まる。ポインタはこのアドレスを値として保持する変数で、「そのアドレスにある値」を読み書きできる。
#includeint 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; }
このメモリ図のポイントを押さえましょう。xはアドレス0x7ffe...acに値42を格納しています。pは別のアドレス0x7ffe...b0にxのアドレス(0x7ffe...ac)を格納しています。*pは「pが指すアドレスに行って値を読む」という意味です。
02 OPERATORS &演算子(アドレス取得)と*演算子(間接参照)の使い方 宣言と使用で意味が違う*演算子の二重の役割を理解する
* 演算子はC言語で2つの意味を持ちます。①ポインタ宣言: int *p; のように型名の後に書くと「pはint型ポインタ変数」という宣言になります。②間接参照: 式の中で *p と書くと「pが指すアドレスの値」を意味します。どちらの*かは文脈(宣言か式か)で判断します。
#includeint 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に代入 |
| 間接参照(読み) | *p | pが指すアドレスの値を読む |
| 間接参照(書き) | *p = 99; | pが指すアドレスに99を書く |
| ポインタの移動 | p++; | pを次の要素のアドレスに進める(int型なら4バイト進む) |
03 ARRAY ポインタと配列の等価性 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)に変換する。
#includeint 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[0]の
アドレスを保持
i番目要素のアドレス
= 先頭 + i×型サイズ
arr[i]と完全に同じ
コンパイラが変換
04 FUNCTION 関数へのポインタ渡し(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 STRUCT 構造体ポインタとアロー演算子 (*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; }
06 DOUBLE_PTR ポインタのポインタ(ダブルポインタ) ポインタ自体のアドレスを保持する**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 SEGFAULT よくあるセグメンテーションフォールトと対策 初期化忘れ・NULLポインタ・範囲外アクセスの3大原因を防ぐ
①未初期化ポインタ(ゴミアドレス)への間接参照: 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のまま使わない
if (p != NULL)
してから参照
配列は0〜n-1
オーバーラン禁止
fp = NULL
use-after-free防止
08 AI_ASSIST Claude Codeでポインタ問題をデバッグする エラーメッセージとコードを貼るだけで原因と修正箇所を特定
C言語のポインタ周りのバグは「エラーメッセージが出ない(Segfault)」「コンパイルは通るが動作がおかしい」という形で表れることが多く、原因特定が難しいです。Claude Codeにエラーメッセージとコードを貼り付けることで、ポインタの問題を素早く診断できます。
| 症状・質問 | Claude Codeへの指示例 |
|---|---|
| Segmentation fault | 「以下のCコードがSegmentation faultで落ちます。原因と修正箇所を教えてください。[コードを貼り付け]」 |
| ポインタの書き方 | 「Cでswap関数をポインタ渡しで実装したい。引数の受け取り方と呼び出し元での使い方を教えて。」 |
| メモリリーク確認 | 「このCコードでmalloc/freeが正しく対応しているか確認して。メモリリークがあれば指摘して。[コードを貼り付け]」 |
| 構造体ポインタ | 「Cでtypedef structした構造体のポインタを関数に渡して内部でメンバを更新するコードを書いて。」 |
09 CONCLUSION まとめ:C言語ポインタ理解の確認チェックリスト 基本から応用まで理解のポイントを整理
C言語・組み込み開発をAIで効率化したい方へ
ポインタのデバッグに限らず、C言語・組み込み開発でのバグ診断・コードレビュー・テスト生成はClaude Codeとの組み合わせで大幅に効率化できます。
AI鬼管理では、ソフトウェア開発のAI活用設計・実装支援を行っています。
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の出力)も貼ると原因特定が速くなります。「ポインタを関数に渡して値を変えたいがうまくいかない」という場合は現在のコードと期待する動作を説明するだけで、値渡しとポインタ渡しの違いを含めた修正コードが返ってきます。
Claude Codeで業務自動化を90日で叩き込む
経営者向けの伴走型パーソナルトレーニング
Claude Code を業務に落とし込む
専門研修コース一覧
受講者本人の業務を題材に、「使いこなせる」状態になるまで伴走する研修プログラム。1対1特化型・ハンズオン・法人講座の3コースを展開中。業務特化・実装まで踏み込むタイプのClaude Code研修です。
研修コース一覧を見る →AI鬼管理へのお問い合わせ
この記事を読んで気になった方へ。
AI鬼管理の専門スタッフが、御社に最適な
業務自動化プランを無料でご提案します。


