
はじめてのC言語 | 第30回
ポインタは「ロケーション札」を持って動く
カフェのバックヤードを想像してみてください。
表のカフェスペースは、注文して、受け取って、作業する場所です。
一方でバックヤードでは、棚が並び、必要なものを必要な場所から取り出します。
C言語のポインタは、このバックヤードで使う「ロケーション札」に近い考え方です。
- 商品そのものではなく、商品が置かれている場所を持つ
- 値そのものではなく、値が置かれているアドレスを持つ
この「場所を扱う」という考え方が、ポインタの基本です。
ポインタが持っているのは「値」ではなく「場所」
C言語では、通常の変数には値が入ります。
int x = 10;この場合、変数 x には整数値 10 が入っています。
一方、ポインタ変数には、値そのものではなく、値が置かれている場所、つまりアドレスを入れます。
int *p = &x;この p が、バックヤードでいうロケーション札です。x の中身ではなく、x が置かれている場所を指しています。
記号は2つ:& と *
& は「場所を取り出す」
&x は、変数 x のアドレスを表します。
ロケーション札でいうと、「この商品はどの棚にあるか」を確認する操作です。
* は「その場所の中身を扱う」
*p は、ポインタ p が指している先の値を扱います。
ロケーション札でいうと、「その棚まで行って中身を取り出す」操作です。
#include <stdio.h>
int main(void) {
int x = 10;
int *p = &x;
printf("%d\n", x);
printf("%d\n", *p);
return 0;
}このプログラムでは、x も *p も同じ値を表示します。p が x の場所を指しているためです。
行ってみたら棚がない:不正な場所を指す危険
ロケーション札を見て棚に行ったのに、その棚が存在しなかったら危険です。
C言語でも、ポインタが有効でない場所を指している状態で *p を使うと危険です。
たとえば、初期化していないポインタを使うと、どこを指しているか分かりません。
int *p;
*p = 10;このコードは未定義動作です。
未定義動作とは、C言語の規格が結果を決めていない動作のことです。クラッシュすることもあれば、たまたま動いたように見えることもあります。
棚はあるが、中身を使ってよいとは限らない
ポインタでは、「場所が何かを指しているように見える」だけでは十分ではありません。
その場所に有効なオブジェクトがあり、その型として読んでよい状態である必要があります。
- 初期化していない値を読む
freeしたメモリをもう一度使う- 本来の型と違う型として無理に読む
これらは、見た目には「何かの場所を指している」ように見えても、安全に使えるとは限らない例です。
ポインタでは、場所だけでなく、その場所にあるデータの寿命や型も重要です。
古いロケーション札を持ち続ける:もう有効でない場所を指す
ポインタが指していた場所が、あとから使えなくなることがあります。
たとえば、malloc で確保したメモリを free したあと、そのポインタをもう一度使う場合です。
これは、片付けた棚の古いロケーション札を持ち続けている状態に近いです。
int *p = malloc(sizeof(int));
if (p == NULL) {
return 1;
}
*p = 10;
free(p);
printf("%d\n", *p);この最後の *p は使ってはいけません。free したあとのメモリは、もう自分のプログラムが使ってよい領域ではないからです。
配列や文字列につながる「場所」の考え方
C言語では、配列や文字列を扱うときにも、場所の考え方が出てきます。
配列名は、多くの場合、先頭要素のアドレスとして扱われます。
そのため、配列の要素を順番に処理するとき、ポインタの考え方とつながります。
int numbers[3] = {10, 20, 30};
printf("%d\n", numbers[1]);
printf("%d\n", *(numbers + 1));numbers[1] と *(numbers + 1) は、同じ要素を表します。
この関係を理解すると、配列、文字列、関数引数の理解がつながりやすくなります。
危険を避けるコツ

初期化していないポインタを使わない
ポインタは、使う前に必ず有効な場所を指すようにします。
まだ指す先が決まっていない場合は、NULL を入れておくと状態を確認しやすくなります。
int *p = NULL;NULL なら中身を取りに行かない
NULL は、有効なオブジェクトを指していないことを表す特別な値です。NULL の可能性があるポインタは、いきなり * で触らず、先に確認します。
if (p != NULL) {
printf("%d\n", *p);
}配列の範囲外に出ない
配列には有効な範囲があります。
3個の要素を持つ配列なら、有効な添字は 0、1、2 です。
範囲外の場所を読んだり書いたりすると、未定義動作になります。
配列を関数に渡すときは、要素数も一緒に渡す習慣をつけます。
free したあとは使わない
malloc で確保したメモリは、使い終わったら free します。
ただし、free したあとのポインタを使ってはいけません。
同じポインタを誤って再利用しないために、必要に応じて NULL を代入しておくと確認しやすくなります。
free(p);
p = NULL;ただし、p = NULL; はメモリを解放する操作ではありません。
解放は free が行い、NULL 代入は誤使用を減らすための補助です。
迷ったら道具を使って点検する
ポインタやメモリの問題は、見ただけでは分かりにくいことがあります。
そのため、コンパイラ警告や実行時チェックを使うと発見しやすくなります。
-Wall -Wextraで警告を増やす- AddressSanitizer で範囲外アクセスや use-after-free を見つける
- UndefinedBehaviorSanitizer で未定義動作の一部を見つける
- Linux 環境では Valgrind が役立つ場合もある
macOS で試すなら、まず AddressSanitizer を使うのが扱いやすいです。
clang -Wall -Wextra -fsanitize=address sample.c -o sampleまとめ:ポインタは「場所」を扱うための道具
ポインタは、値そのものではなく、値が置かれている場所を扱うための道具です。
&は変数のアドレスを取り出す*はポインタが指す先の値を扱う- 初期化していないポインタは使わない
NULLの可能性があるなら確認する- 配列の範囲外に出ない
freeしたあとのメモリを使わない
ポインタは怖いものというより、場所を正しく扱うための仕組みです。
場所、有効範囲、寿命を意識できるようになると、配列、文字列、関数引数、動的メモリ確保の理解がつながりやすくなります。
次回予告
次回は、ポインタの「場所を指す」考え方を、配列とつなげて見ていきます。
配列とポインタが別々のものに見えていても、同じ並びを別の見方で扱っていることが分かってきます。


