データと関数をつなぐ |C言語のポインタ入門:ロケーションで覚える「場所を指す」考え方

当サイトでは、コンテンツの一部に広告を掲載しています。
データと関数をつなぐ |C言語のポインタ入門:ロケーションで覚える「場所を指す」考え方

はじめての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 も同じ値を表示します。
px の場所を指しているためです。

行ってみたら棚がない:不正な場所を指す危険

ロケーション札を見て棚に行ったのに、その棚が存在しなかったら危険です。

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) は、同じ要素を表します。
この関係を理解すると、配列、文字列、関数引数の理解がつながりやすくなります。

危険を避けるコツ

ポインタ入門|ロケーションで覚える「場所を指す」考え方(C言語)危険を避けるコツ

初期化していないポインタを使わない

ポインタは、使う前に必ず有効な場所を指すようにします。
まだ指す先が決まっていない場合は、NULL を入れておくと状態を確認しやすくなります。

int *p = NULL;

NULL なら中身を取りに行かない

NULL は、有効なオブジェクトを指していないことを表す特別な値です。
NULL の可能性があるポインタは、いきなり * で触らず、先に確認します。

if (p != NULL) {
    printf("%d\n", *p);
}

配列の範囲外に出ない

配列には有効な範囲があります。
3個の要素を持つ配列なら、有効な添字は 012 です。

範囲外の場所を読んだり書いたりすると、未定義動作になります。
配列を関数に渡すときは、要素数も一緒に渡す習慣をつけます。

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 したあとのメモリを使わない

ポインタは怖いものというより、場所を正しく扱うための仕組みです。
場所、有効範囲、寿命を意識できるようになると、配列、文字列、関数引数、動的メモリ確保の理解がつながりやすくなります。

次回予告

次回は、ポインタの「場所を指す」考え方を、配列とつなげて見ていきます。
配列とポインタが別々のものに見えていても、同じ並びを別の見方で扱っていることが分かってきます。

あわせて読みたい
データと関数をつなぐ | C言語の配列とポインタが、同じ景色に見える瞬間 はじめてのC言語 | 第31回 配列とポインタは、どこでつながるのか C言語では、配列や文字列を扱うとき、「場所(ロケーション)」という考え方が、何度も顔を出します...

さらに学びたいあなたへ

用途ごとに選ぶ Linux のおすすめ本

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

のいのアバター のい UNIX Cafe マスター

Macintosh Color Classicから始まった旅は、長いWindows時代を経て、Windows10のサポート終了をきっかけにUNIXの世界へ戻ってきました。UNIX Cafeでは、UNIX・Linux・そしてMacな世界を、むずかしい言葉を使わず、物語のように書いています。プログラミングは、アイデアをコンピューターに伝えるための言葉です。簡単な単語と文法を覚えれば、誰でもコマンドを使えます。ぜひ一度、やさしいプログラミングの世界をのぞいてみてください。

Created by UNIX Cafe

目次