
C言語の基本|アドレスとポインタ
アドレスを覚え、場所をたどって値を操作する。ポインタの仕組みが分かれば、C言語のメモリ操作がぐっと身近になります。
C言語では、変数に入っている値だけでなく、その値がメモリ上のどこに保存されているかも扱えます。
変数が置かれている場所を表す情報がアドレスです。そして、そのアドレスを保存するための変数がポインタです。
この関係は、次の3つに分けると分かりやすくなります。
| 用語 | 意味 | 身近なたとえ |
|---|---|---|
| 値 | 変数に格納されているデータ | 箱の中身 |
| アドレス | 変数が置かれているメモリ上の場所 | 箱がある住所 |
| ポインタ | アドレスを保存する変数 | 住所を書いたメモ |
たとえば、int型変数scoreに80が格納されているとします。
int score = 80;scoreには値80が入っています。同時に、scoreはメモリ上のどこかに置かれているため、その場所を表すアドレスも持っています。
このscoreのアドレスを別の変数へ保存したものがポインタです。
int *ptr = &score;このコードでは、&scoreでscoreのアドレスを取得し、そのアドレスをポインタ変数ptrへ保存しています。
さらに、*ptrと書くと、ptrに保存されているアドレスをたどり、その場所にある値へアクセスできます。
*ptr = 90;ptrはscoreのアドレスを保存しているため、*ptrを90にすると、scoreの値も90に変わります。
アドレスとポインタを理解すると、scanfで変数に&を付ける理由や、関数から呼び出し元の変数を変更する仕組みも見えてきます。
ポインタとは
ポインタとは、メモリ上のアドレスを保存する変数です。
普通のint型変数は整数値を保存します。
int number = 25;一方、int型のデータを指すポインタは、int型変数のアドレスを保存します。
int *ptr = &number;number、&number、ptr、*ptrの違いを整理すると、次のようになります。
| 書き方 | 表すもの | 内容の例 |
|---|---|---|
| number | 変数numberの値 | 25 |
| &number | numberのアドレス | 0x1000 |
| ptr | ptrに保存されたアドレス | 0x1000 |
| *ptr | ptrが指す場所にある値 | 25 |
&numberとptrには、数値として同じアドレスが入っています。
ptrそのものはアドレスを表し、*ptrはそのアドレスに保存されている値を表します。ここを分けて考えることが、ポインタを理解する大切なポイントです。
普通の変数とポインタ変数の違い
普通の変数とポインタ変数では、保存する情報が異なります。
| 変数の種類 | 保存するもの |
|---|---|
| int型変数 | 整数値 |
| double型変数 | 小数値 |
| char型変数 | 1文字 |
| ポインタ変数 | メモリ上のアドレス |
次の変数には整数値25が格納されます。
int number = 25;次のポインタ変数には、numberのアドレスが格納されます。
int *ptr = &number;ptrの中に25が直接格納されるわけではありません。ptrに入るのは、25を保存しているnumberの場所です。
図1:ポインタが変数のアドレスを保存する仕組み

この図から分かること
変数numberには値25が入っています。一方、ポインタptrにはnumberのアドレスが入っています。
ptrが保存しているのは25という値ではありません。25が保存されている場所です。
ptrからnumberへ向かう矢印をイメージすると、ポインタが特定の変数を指すという関係を理解しやすくなります。
ポインタを住所のメモとして考える
ポインタは、住所を書いたメモにたとえると分かりやすくなります。
たとえば、ある建物の住所が0x1000で、その建物の中に25という荷物があるとします。
| たとえ | C言語での対応 |
|---|---|
| 建物 | 変数number |
| 建物の中の荷物 | 値25 |
| 建物の住所 | &number |
| 住所を書いたメモ | ポインタptr |
| 住所をたどって荷物を見る | *ptr |
住所を書いたメモは、荷物そのものではありません。しかし、その住所をたどれば荷物が置かれている場所へ到達できます。
同じように、ポインタは値そのものではなく、値が置かれている場所を保存します。そのアドレスをたどることで、元の変数の値を参照したり変更したりできます。
ポインタをリモコンとして考える
ポインタは、離れた場所にある機器を操作するリモコンにもたとえられます。
| リモコンのたとえ | C言語での対応 |
|---|---|
| 操作される機器 | 変数 |
| 機器がある場所 | アドレス |
| リモコン | ポインタ |
| リモコンから操作する | *を使って値を参照・変更する |
ポインタ自身が操作対象の値を持つのではありません。操作する対象がどこにあるかを知っているため、その場所へアクセスできます。
ポインタ変数を宣言する
int型のデータを指すポインタ変数は、次の形で宣言します。
int *ptr;各部分の意味は次のとおりです。
| 部分 | 意味 |
|---|---|
| int | ptrが指す先のデータ型 |
| * | ptrをポインタ変数として宣言する |
| ptr | ポインタ変数の名前 |
この宣言は、ptrがint型のデータを指すためのポインタであることを表します。
変数のアドレスを同時に設定するなら、次のように書きます。
int number = 25;
int *ptr = &number;&numberでnumberのアドレスを取得し、それをptrへ代入しています。
ポインタの型が必要な理由
ポインタには、指している先の型があります。
int *int_ptr;
char *char_ptr;
double *double_ptr;| 宣言 | 指す対象 |
|---|---|
| int *int_ptr | int型のデータ |
| char *char_ptr | char型のデータ |
| double *double_ptr | double型のデータ |
ポインタが指す先の型が分かれば、C言語はその場所にあるデータをどのような型として読み書きすればよいか判断できます。
int型を指すポインタなら、その場所のデータをint型として扱います。char型を指すポインタなら、char型として扱います。
&で変数のアドレスを取得する
&はアドレス演算子です。変数名の前に付けると、その変数のアドレスを取得できます。
&numbernumberがメモリ上の0x1000に置かれていると仮定すると、&numberは0x1000というアドレスを表します。
ptr = &number;この代入によって、ptrはnumberを指すようになります。
処理の流れは次のとおりです。
- numberのためのメモリ領域を用意する
- numberに値25を保存する
- &numberでnumberのアドレスを取得する
- 取得したアドレスをptrへ保存する
*で指している先の値へアクセスする
ポインタ変数の前に*を付けると、そのポインタが指している場所にある値へアクセスできます。
*ptrptrがnumberのアドレスを保存しているなら、*ptrはnumberの値を表します。
printf("%d\n", *ptr);このコードでは、numberに入っている値が表示されます。
値を書き換えることもできます。
*ptr = 40;ptrはnumberを指しているため、numberの値が40に変わります。
| コード | 起こること |
|---|---|
| ptr = &number; | ptrにnumberのアドレスを保存する |
| *ptr | numberの値を表す |
| *ptr = 40; | numberの値を40に変更する |
宣言の*と式* のを区別する
*は、使われる場所によって読み方が変わります。
ポインタ変数を宣言するときは、ポインタであることを表します。
int *ptr;宣言後の式で使うときは、ptrが指している先の値を表します。
*ptr| 使われる場所 | コード | 意味 |
|---|---|---|
| 宣言 | int *ptr; | ptrはint型を指すポインタ |
| 式 | *ptr | ptrが指す先のint型の値 |
| 代入先 | *ptr = 40; | ptrが指す先の値を40にする |
同じ*でも、宣言にあるのか、値を扱う式にあるのかを確認すると読みやすくなります。
&と*の関係
&と*は、アドレスと値を行き来するために使います。
int number = 25;
int *ptr = &number;この状態では、次の関係があります。
| 式 | 表すもの |
|---|---|
| number | numberの値25 |
| &number | numberのアドレス |
| ptr | ptrに保存されたnumberのアドレス |
| *ptr | ptrが指しているnumberの値25 |
考え方を順番に並べると、次のようになります。
- &numberでnumberのアドレスを取得する
- 取得したアドレスをptrへ保存する
- *ptrでptrが指す場所の値へアクセスする
アドレスを表示する方法
printfでポインタが保存しているアドレスを表示するときは、%pを使います。
printf("%p\n", (void *)ptr);%pへ渡すポインタは、voidポインタへ変換します。
| 部分 | 役割 |
|---|---|
| %p | アドレスを表示する |
| ptr | ポインタに保存されたアドレス |
| (void *)ptr | ptrをvoidポインタへ変換する |
アドレスの具体的な表示形式は処理系によって異なります。また、同じプログラムでも実行するたびに異なるアドレスが表示される場合があります。
ポインタを使ってファン回転数を変更する
ポインタ経由でファン回転数を書き換えるプログラム
ファイル名:11_3_1.c
#include <stdio.h>
int main(void)
{
int fan_speed = 1200; /* ファンの回転数 */
int *ptr = &fan_speed; /* fan_speedのアドレスを保存 */
printf("変更前の回転数: %d rpm\n", fan_speed);
printf("ptrが保存しているアドレス: %p\n", (void *)ptr);
printf("ptrが指している先の値: %d rpm\n", *ptr);
*ptr = 1500; /* fan_speedを間接的に変更 */
printf("変更後の回転数: %d rpm\n", fan_speed);
printf("変更後にptrから見た値: %d rpm\n", *ptr);
return 0;
}実行結果の例
アドレスは実行環境によって変わります。次は表示例です。
変更前の回転数: 1200 rpm
ptrが保存しているアドレス: 0x000000a1b2c3d4e0
ptrが指している先の値: 1200 rpm
変更後の回転数: 1500 rpm
変更後にptrから見た値: 1500 rpm通常の変数を用意する
最初に、ファンの回転数を保存するint型変数fan_speedを用意しています。
int fan_speed = 1200;この時点でfan_speedには1200が格納されています。
fan_speedはメモリ上のどこかに配置され、その場所にはアドレスがあります。
ポインタを宣言してアドレスを保存する
次に、int型を指すポインタptrを宣言しています。
int *ptr = &fan_speed;この文は、次の2つを同時に行っています。
- int型を指すポインタptrを用意する
- fan_speedのアドレスをptrへ保存する
| 式 | 内容 |
|---|---|
| &fan_speed | fan_speedのアドレス |
| ptr | fan_speedのアドレスを保存するポインタ |
| *ptr | fan_speedに格納されている値 |
この時点で、ptrはfan_speedを指しています。
ポインタに保存されたアドレスを表示する
次のprintfでは、ptrに保存されているアドレスを表示しています。
printf("ptrが保存しているアドレス: %p\n", (void *)ptr);ptrにはfan_speedのアドレスが入っています。そのため、ptrを%pで表示すると、&fan_speedと同じ位置を表すアドレスが表示されます。
アドレスの値そのものは、実行環境や実行時によって異なります。
ポインタが指している先の値を表示する
次のprintfでは、*ptrを表示しています。
printf("ptrが指している先の値: %d rpm\n", *ptr);ptrは fan_speedを指しているため、*ptrはfan_speedの値1200を表します。
| 表示する式 | 表示内容 |
|---|---|
| fan_speed | 1200 |
| ptr | fan_speedのアドレス |
| *ptr | 1200 |
ポインタ経由で値を書き換える
次の文では、ptrが指している先の値を1500へ変更しています。
*ptr = 1500;ptrが指している先はfan_speedです。そのため、実際に変更されるのはfan_speedの値です。
| 項目 | 変更前 | 変更後 |
|---|---|---|
| fan_speed | 1200 | 1500 |
| ptrが保存するアドレス | fan_speedのアドレス | 同じアドレス |
| *ptr | 1200 | 1500 |
値を書き換えても、ptrに保存されているアドレスは変わりません。変化するのは、ptrが指している場所の中身です。
図2:ポインタ経由で指している先の値を変更する

この図から分かること
*ptr = 1500を実行すると、ptr自身の中身が1500になるわけではありません。
ptrには引き続きfan_speedのアドレスが保存されています。変更されるのは、そのアドレスをたどった先にあるfan_speedの値です。
ポインタ経由の代入では、ポインタが指す対象の中身を変更すると考えましょう。
ptrと*ptrを混同しない
ポインタの学習では、ptrと*ptrを同じものとして読まないことが大切です。
| 書き方 | 表すもの |
|---|---|
| ptr | アドレス |
| *ptr | そのアドレスにある値 |
たとえば、ptrが0x1000を保存し、0x1000に1200が格納されているなら、次の関係になります。
| 式 | 内容 |
|---|---|
| ptr | 0x1000 |
| *ptr | 1200 |
ptrへ別のアドレスを代入すると、指す対象が変わります。
ptr = &other_number;一方、*ptrへ値を代入すると、現在指している対象の値が変わります。
*ptr = 1500;&と*の動きを図で整理する
&は、変数の値からアドレスを取り出すために使います。
&fan_speed*は、ポインタに保存されたアドレスをたどり、その場所にある値へアクセスするために使います。
*ptrこの2つを順番に読むと、次の流れになります。
fan_speed
↓ &でアドレスを取得
&fan_speed
↓ ptrへ保存
ptr
↓ *で指している先へアクセス
*ptr図3:&と*でアドレスと値を行き来する

この図から分かること
&dataは、変数dataのアドレスを表します。そのアドレスをポインタpへ保存すると、pはdataを指すようになります。
*pは、pに保存されたアドレスをたどった先の値です。
&は変数からアドレスを取得し、*はアドレスから指している先の値へアクセスする働きを持っています。
ポインタが必要になる場面
簡単なプログラムなら、普通の変数だけで処理できる場合もあります。それでもポインタが重要なのは、別の場所にあるデータを直接扱えるからです。
代表的な利用場面は次のとおりです。
| 利用場面 | ポインタが役立つ理由 |
|---|---|
| scanfによる入力 | 入力値を書き込む変数の場所を渡せる |
| 関数から変数を変更する | 呼び出し元の変数へ直接アクセスできる |
| 配列の処理 | 先頭要素の場所を基準に要素をたどれる |
| 文字列の処理 | 先頭文字から \0 まで順番に処理できる |
| 動的メモリ | 確保したメモリの場所を保存できる |
ポインタは単なる記号の学習ではありません。メモリ上のデータを柔軟に扱うための仕組みです。
値を渡す場合とアドレスを渡す場合
関数へ普通の値を渡すと、関数には値のコピーが渡されます。
| 渡すもの | 関数内で扱うもの | 元の変数 |
|---|---|---|
| 値 | 値のコピー | 直接は変更されない |
| アドレス | 元の変数の保存場所 | ポインタ経由で変更できる |
たとえば、main関数にあるnumberを別の関数から変更したい場合は、numberの値ではなく、&numberによってアドレスを渡します。
受け取る側の関数は、そのアドレスをポインタで受け取り、*を使って元の変数へアクセスします。
関数へアドレスを渡す基本イメージ
void update(int *ptr)
{
*ptr = 20;
}呼び出す側では、変数のアドレスを渡します。
int number = 10;
update(&number);処理は次のようにつながります。
- numberに10を格納する
- &numberでnumberのアドレスを取得する
- updateへnumberのアドレスを渡す
- updateのptrがnumberを指す
- *ptr = 20によってnumberを20へ変更する
この仕組みによって、別の関数から呼び出し元の変数を変更できます。
scanfとポインタの関係
scanfは、ポインタの考え方が使われている身近な例です。
int number;
scanf("%d", &number);scanfは入力された値を、呼び出し元のnumberへ書き込む必要があります。そのため、numberの値ではなく、numberのアドレスを渡します。
| 書き方 | 意味 |
|---|---|
| number | numberに入っている値 |
| &number | numberが置かれている場所 |
| scanf("%d", &number) | 入力値の保存場所としてnumberのアドレスを渡す |
もしnumberの値だけを渡しても、scanfは入力値をどこへ保存すればよいか判断できません。
&numberによって保存場所を受け取ることで、その場所へ入力値を書き込めます。
scanfでchar配列に&を付けない理由
整数変数への入力では&numberを使います。
scanf("%d", &number);一方、char配列へ文字列を入力するときは、一般に配列名へ&を付けません。
char text[100];
scanf("%99s", text);配列名textは、多くの式の中で先頭要素へのポインタに変換されます。そのため、文字列の保存先としてtextをそのまま渡せます。
| 入力対象 | scanfへ渡すもの |
|---|---|
| int型変数number | &number |
| char配列text | text |
この違いも、配列名とポインタの関係を学ぶと理解しやすくなります。
ポインタを使う前に有効なアドレスを設定する
ポインタを宣言しただけでは、正しい変数を指しているとは限りません。
int *ptr;この状態で*ptrを使ってはいけません。ptrには、有効なアドレスが設定されていないからです。
次のように、指す対象のアドレスを設定してから使います。
int number = 25;
int *ptr = &number;その後で、*ptrを利用できます。
*ptr = 40;安全に読むための順番は次のとおりです。
- 指される変数を用意する
- &で変数のアドレスを取得する
- ポインタへアドレスを保存する
- *で指している先へアクセスする
型の合うアドレスを保存する
int型を指すポインタには、int型変数のアドレスを保存します。
int number = 25;
int *ptr = &number;double型を指すポインタには、double型変数のアドレスを保存します。
double rate = 1.5;
double *rate_ptr = &rate;指す先の型を合わせることで、ポインタが指しているデータの大きさや扱い方を正しく判断できます。
よくあるつまずき
| つまずきやすい点 | 正しい考え方 |
|---|---|
| ptrに値が直接入っていると思う | ptrにはアドレスが入る |
| ptrと*ptrを同じものだと思う | ptrはアドレス、*ptrは指す先の値 |
| &と*を同じ働きだと思う | &はアドレス取得、*は指す先へのアクセス |
| *ptrへの代入でptrが変わると思う | 変わるのはptrが指す先の値 |
| 未設定のポインタを使う | 有効なアドレスを設定してから使う |
| %dでポインタを表示する | アドレスは%pで表示する |
| アドレスの数値を覚えようとする | 実行環境や実行時で変わるものとして見る |
コードを読むときの確認手順
ポインタを使ったコードでは、次の順番で確認すると理解しやすくなります。
- ポインタが何型のデータを指すのか確認する
- どの変数のアドレスを保存しているか確認する
- &で取得しているアドレスを確認する
- ptrと*ptrのどちらを使っているか確認する
- 値を変更しているのか、指す場所を変更しているのか確認する
- *を使う前に有効なアドレスが設定されているか確認する
- アドレスの表示に%pが使われているか確認する
ポインタを読むときは、何の値を持っているかではなく、何の場所を覚えているかから考えるのがコツです。
値、アドレス、ポインタ、指している先の値を分けて考えられるようになると、&と*の役割が自然につながります。この土台ができれば、関数へのポインタ渡し、配列とポインタの関係、文字列操作、動的メモリ確保も理解しやすくなります。
