6日でできる 新C言語入門|ポインタの基本と使い方

ポインタは、変数の場所を覚えて値へたどり着くための仕組み。
アドレス、&演算子、*演算子を理解して、C言語らしいメモリ操作の基本を身につけよう。

C言語を学習していると、必ず登場する重要なテーマがポインタです。

ポインタは、変数の値そのものではなく、変数がメモリ上のどこにあるのかを示すアドレスを保存するための変数です。普通の変数が数値や文字などの値を保存するのに対して、ポインタ変数はメモリ上の場所を保存します。

たとえば、int型の変数scoreがあるとします。scoreには80のような値が入っていますが、その値はメモリ上のどこかに保存されています。この場所を表すのがアドレスです。そして、そのアドレスを保存できるのがポインタです。

ポインタを使うと、変数の場所をたどって値を読み取ったり、ポインタが指している変数の値を書き換えたりできます。これは、関数で変数の値を直接変更したい場合や、配列や文字列を効率よく扱いたい場合にとても重要になります。

最初は、&演算子や*演算子が出てきて難しく感じるかもしれません。しかし、考え方を分けて見ると整理しやすくなります。

&変数名は、その変数のアドレスを取得します。
*ポインタ名は、そのポインタが指している場所の値を扱います。

この記事では、ポインタとは何か、ポインタ変数の宣言方法、NULLで初期化する理由、&演算子と*演算子の使い方、ポインタ経由で値を書き換える流れを、サンプルプログラムを使ってやさしく解説していきます。

ポインタはアドレスを保存する変数

通常の変数は、値そのものを保存します。

たとえば、次のような変数があるとします。

int score = 80;

このscoreには80という値が入っています。

一方、ポインタ変数は値そのものではなく、変数のアドレスを保存します。

int* pScore = NULL;

このpScoreは、int型の変数のアドレスを保存できるポインタ変数です。

種類保存するもの
通常の変数int score;int型の値
ポインタ変数int* pScore;int型変数のアドレス
アドレス取得&scorescoreの場所
間接参照*pScorepScoreが指す場所の値

ポインタ変数は、どの型の変数のアドレスを保存するのかを型で表します。

int型の変数のアドレスを保存するならintを使います。
double型の変数のアドレスを保存するならdouble
を使います。

ポインタ変数の宣言

ポインタ変数は、次のように宣言します。

int* p;

これは、pがint型の変数を指すポインタであることを表します。

double型の変数を指すポインタなら、次のように書きます。

double* q;

ポインタ変数の型は、指し示す変数の型と合わせることが大切です。

指したい変数ポインタ変数
int型の変数int*
double型の変数double*
float型の変数float*
char型の変数char*

int型の変数のアドレスをdouble*型のポインタに入れるような書き方は、型が合っていないため避けます。ポインタはメモリ上の場所を扱うため、型の一致がとても重要です。

図:通常の変数とポインタ変数の違い

この図から分かること

この図から分かるのは、通常の変数とポインタ変数では保存しているものが違うということです。

scoreは80という値を保存しています。一方、pScoreはscoreのアドレスを保存しています。ポインタは値を直接持つのではなく、値が保存されている場所を指し示すための変数です。

&演算子で変数のアドレスを取得する

ポインタに変数のアドレスを代入するときは、&演算子を使います。

pScore = &score;

この書き方は、scoreのアドレスをpScoreに代入するという意味です。

ここで、scoreと&scoreはまったく意味が違います。

書き方意味
scorescoreに入っている値
&scorescoreが保存されているアドレス

ポインタを使うときは、この違いをしっかり分けて考えることが大切です。

scoreは値です。
&scoreは場所です。

*演算子でポインタが指す先の値を扱う

ポインタがアドレスを持っている状態で、*演算子を使うと、そのポインタが指している先の値を扱えます。

*pScore

これは、pScoreが指している場所にある値を表します。

もしpScoreにscoreのアドレスが入っているなら、*pScoreはscoreの値と同じように扱えます。

pScore = &score;
*pScore = 95;

この場合、pScoreはscoreを指しています。そのため、*pScore = 95;と書くと、scoreの値が95に変わります。

書き方意味
pScore保存しているアドレス
*pScorepScoreが指す先の値
*pScore = 95;指している変数の値を95に変更する

*演算子は、ポインタが指す先をたどるための演算子です。ポインタの学習では、この考え方がとても重要です。

NULLで初期化する理由

ポインタ変数を宣言した直後は、どこのアドレスを指しているか分からない状態になることがあります。

int* pScore;

このような状態のポインタをそのまま使うと、意図しないメモリを参照してしまう危険があります。

そこで、安全のためにNULLで初期化します。

int* pScore = NULL;

NULLは、どこも指していないことを表す特別な値です。

状態意味
int* pScore;どこを指しているか不明な状態になり得る
int* pScore = NULL;どこも指していないことを明示する
pScore = &score;scoreを指す状態になる

ポインタを使う前に、どこを指しているのかをはっきりさせることが大切です。

ポインタで値を間接的に操作するプログラム

次のプログラムでは、ポインタを使って変数の値を間接的に操作します。

プロジェクト/ファイル名: Lesson50_1/main.c

#include <stdio.h>

void showValues(int n1, int n2, int n3);

int main(void) {
    int scoreA = 60;
    int scoreB = 90;
    int* pScore = NULL; // ポインタ変数をNULLで初期化する

    // pScoreにscoreAのアドレスを代入する
    pScore = &scoreA;
    showValues(scoreA, scoreB, *pScore);

    // pScoreが指しているscoreAの値を書き換える
    *pScore = 75;
    showValues(scoreA, scoreB, *pScore);

    // pScoreにscoreBのアドレスを代入する
    pScore = &scoreB;

    // pScoreが指しているscoreBの値を書き換える
    *pScore = 100;
    showValues(scoreA, scoreB, *pScore);

    return 0;
}

// 2つの変数とポインタ経由の値を表示する関数
void showValues(int n1, int n2, int n3) {
    printf("scoreA = %d, scoreB = %d, *pScore = %d\n", n1, n2, n3);
}

実行結果

scoreA = 60, scoreB = 90, *pScore = 60
scoreA = 75, scoreB = 90, *pScore = 75
scoreA = 75, scoreB = 100, *pScore = 100

このプログラムでは、pScoreというポインタ変数を使って、scoreAとscoreBの値を順番に操作しています。

最初に、pScoreをNULLで初期化しています。

int* pScore = NULL;

次に、pScoreにscoreAのアドレスを代入しています。

pScore = &scoreA;

この時点で、pScoreはscoreAを指しています。そのため、*pScoreはscoreAの値を表します。

*pScore = 75;

この処理によって、scoreAの値が75に変わります。

その後、pScoreにscoreBのアドレスを代入しています。

pScore = &scoreB;

今度は、pScoreがscoreBを指す状態になります。

*pScore = 100;

この処理によって、scoreBの値が100に変わります。

ポインタが指す先を変えると操作対象も変わる

ポインタの大切な特徴は、指す先を変えられることです。

今回のプログラムでは、pScoreが最初にscoreAを指し、そのあとscoreBを指すように変わっています。

処理pScoreが指す先*pScoreの意味
pScore = &scoreA;scoreAscoreAの値
*pScore = 75;scoreAscoreAを75に変更
pScore = &scoreB;scoreBscoreBの値
*pScore = 100;scoreBscoreBを100に変更

同じ*pScoreでも、pScoreがどの変数を指しているかによって、操作対象が変わります。

この点が、ポインタの便利なところであり、同時に注意が必要なところでもあります。

図:ポインタが指す先を変えると操作する変数も変わる

この図から分かること

この図から分かるのは、ポインタ変数が保存しているアドレスを変えると、指す先の変数も変わるということです。

pScoreにscoreAのアドレスを入れれば、*pScoreはscoreAの値を表します。pScoreにscoreBのアドレスを入れれば、*pScoreはscoreBの値を表します。同じポインタ変数でも、保存しているアドレスによって操作対象が変わります。

ポインタと型の関係

ポインタでは、型の一致がとても重要です。

int型の変数のアドレスは、int型のポインタに保存します。double型の変数のアドレスは、double型のポインタに保存します。

変数正しいポインタ避ける書き方
int score;int* pScore = &score;double* pScore = &score;
double price;double* pPrice = &price;int* pPrice = &price;
float rate;float* pRate = &rate;char* pRate = &rate;

型が合っていないポインタにアドレスを入れると、正しく値を読み取れなかったり、意図しない動作につながったりします。

ポインタはメモリ上の場所を扱うため、どの型のデータとして読むのかが重要です。int型の場所をint型として読むから正しく扱えます。違う型として読むと、メモリの解釈がずれてしまいます。

*には宣言と参照の2つの場面がある

ポインタの学習で混乱しやすいのが、*の使い方です。

*は、ポインタ変数を宣言するときにも使いますし、ポインタが指す先の値を扱うときにも使います。

書き方場面意味
int* pScore;宣言int型変数を指すポインタを宣言
*pScore使用pScoreが指す先の値を扱う
*pScore = 75;使用pScoreが指す変数の値を75にする

同じ*でも、宣言の中にある場合と、式の中で使う場合では意味が変わります。

int* pScore;は、pScoreがポインタ変数であることを表します。
*pScoreは、pScoreが指している先の値を表します。

ここを分けて理解すると、ポインタのコードが読みやすくなります。

&演算子と*演算子の関係

ポインタでは、&演算子と*演算子がよく一緒に出てきます。

&演算子は、変数のアドレスを取得します。

pScore = &scoreA;

*演算子は、ポインタが指している先の値を扱います。

*pScore = 75;

整理すると、次のようになります。

演算子役割
&変数のアドレスを取得する
*ポインタが指す先の値を扱う

&は場所を取り出すためのものです。
*は場所をたどって値へアクセスするためのものです。

この2つの関係が分かると、ポインタの基本がかなり理解しやすくなります。

図:&演算子と*演算子の役割

この図から分かること

この図から分かるのは、&演算子と*演算子が反対方向の働きを持っているということです。

&scoreAでscoreAのアドレスを取得し、そのアドレスをpScoreに保存します。そのあと*pScoreを使うと、pScoreが指しているscoreAの値を扱えます。&はアドレスを取り出す、*はアドレスをたどって値へアクセスする、と考えると分かりやすくなります。

ポインタを使うときの注意点

ポインタは便利ですが、メモリ上の場所を扱うため、注意して使う必要があります。

初期化してから使う

ポインタ変数は、宣言しただけで使わないようにしましょう。

int* pScore = NULL;

NULLで初期化しておくと、どこも指していない状態を明示できます。

アドレスを代入してから*で参照する

ポインタがどこを指しているか分からない状態で*pScoreを使うのは危険です。

pScore = &scoreA;

このように、必ず有効な変数のアドレスを代入してから使います。

型を合わせる

int型の変数を指すならintを使います。double型の変数を指すならdoubleを使います。

型が違うポインタに無理にアドレスを入れると、正しく動かない原因になります。

指す先が変わることを意識する

ポインタは、保存しているアドレスを変えることで、指す先を変更できます。

そのため、今そのポインタがどの変数を指しているのかを意識して読むことが大切です。

ポインタの基本操作を表で整理する

ポインタでよく使う操作を整理しておきましょう。

操作記述例意味
ポインタの宣言int* pScore;int型変数を指すポインタを宣言
NULLで初期化int* pScore = NULL;どこも指していない状態にする
アドレスを代入pScore = &scoreA;scoreAのアドレスを保存する
指す先の値を読む*pScorepScoreが指す変数の値を読む
指す先の値を変更*pScore = 75;pScoreが指す変数の値を変更する

この表の内容が理解できれば、ポインタの基本的な読み書きはかなり整理できます。

ポインタを理解するとC言語の世界が広がる

ポインタは、変数のアドレスを保存するための変数です。

通常の変数は値を保存しますが、ポインタ変数はメモリ上の場所を保存します。&演算子を使うと変数のアドレスを取得でき、*演算子を使うとポインタが指している場所の値を扱えます。

ポインタを使うと、変数の値を間接的に読み取ったり、書き換えたりできます。今回のサンプルでは、pScoreがscoreAを指しているときはscoreAを変更し、pScoreがscoreBを指しているときはscoreBを変更しました。

また、ポインタでは型の一致も大切です。int型の変数のアドレスはint型のポインタに保存します。double型の変数のアドレスはdouble型のポインタに保存します。

最初は難しく感じやすいポインタですが、値とアドレスを分けて考えると理解しやすくなります。変数名は値、&変数名はアドレス、*ポインタ名は指す先の値という関係を押さえておくと、次の配列や文字列、関数のアドレス渡しの学習にもつながります。