C言語のきほん|ポインタの基本を学ぶ

見えないメモリのしくみが、ポインタを学ぶとぐっと身近になる

C言語を学んでいると、いつか必ず出会うのがポインタです。
そしてこのポインタは、C言語の中でも「ここで少し難しく感じた」という人がとても多いテーマでもあります。

でも、最初にお伝えしておきたいのは、ポインタは特別な魔法のようなものではない、ということです。
ポイントはとてもシンプルで、ポインタとはデータそのものを覚えるのではなく、データが置かれている場所を覚えるための仕組みです。

たとえば、変数には値が入ります。
int 型の変数 score に 80 を入れれば、その変数には 80 という値が保存されます。
一方でポインタは、その 80 が入っている変数そのものではなく、その変数がメモリ上のどこに置かれているかという場所の情報を扱います。

この「場所の情報」が、アドレスです。

C言語では、変数の値を直接使う場面だけでなく、変数の場所を指定して処理したい場面がたくさんあります。
たとえば、関数の中から呼び出し元の変数を書き換えたいとき、配列を効率よく扱いたいとき、文字列を順番にたどりたいとき、あるいは必要な分だけメモリを確保して使いたいときなど、ポインタの考え方がとても重要になります。

そのため、ポインタを理解すると、C言語の理解そのものが一段深くなります。
逆にいえば、ポインタがあいまいなままだと、配列や文字列、関数、メモリ操作といった重要な学習内容が少しずつ分かりにくくなってしまいます。

ただし安心してください。
ポインタが難しく見える大きな理由は、目に見えないメモリの中の動きを頭の中でイメージしなければならないからです。
つまり、考え方を順番に整理しながら学べば、決して理解できないものではありません。

ここではまず、ポインタをいきなり複雑な形で覚えるのではなく、最初の土台になるアドレスとは何か、変数の値とアドレスはどう違うのか、そして & を使ってアドレスを取り出す方法はどうなっているのか、という基本から丁寧に見ていきます。

「値を見る」のが普通の変数、
「場所を見る」のがポインタの入り口です。

この違いをしっかりつかめると、ポインタへの苦手意識はかなり小さくなります。
まずは、メモリの中に変数がどう置かれているのかを、やさしく整理しながら確認していきましょう。

アドレスとは何か

ポインタを理解するためには、最初にアドレスという考え方をしっかり押さえることが大切です。

コンピュータのメモリは、よく「小さな箱がたくさん並んでいる場所」にたとえられます。
それぞれの箱には番号のようなものが付いていて、その番号によって、どこにどのデータがあるかを区別できるようになっています。
この番号がアドレスです。

身近なたとえでいうと、変数の値は家の中にある荷物、アドレスはその家の住所のようなものです。
荷物そのものを見たいなら中身を確認しますし、荷物の置かれている場所を知りたいなら住所を調べます。
C言語では、この住所にあたる情報を使ってデータを操作できます。

たとえば、次のような変数を考えてみましょう。

int score = 80;

このとき、score という変数には 80 という値が入っています。
ただし、実際にはメモリ上のどこかに score のための領域が確保され、その場所に 80 が保存されています。
つまり、変数には必ず値と場所の両方があるわけです。

ここで大事なのは、普段わたしたちが変数名を書いたときに意識しているのは値の方だということです。
score と書けば、通常はその中に入っている 80 を使います。
しかしポインタを学ぶときは、その score がどこに置かれているのかにも注目します。

メモリとアドレスのイメージ

アドレスは目に見えないので、図でイメージすると理解しやすくなります。
変数はメモリ上に領域を持ち、その領域に値が入っています。
その領域の先頭の場所を示すのがアドレスです。

表でイメージする変数とアドレス

項目内容
変数名score
int
格納される値80
メモリ上の場所あるアドレスに保存される
その場所を表すものアドレス

さらにイメージを深めると、次のように考えられます。

見方何を表しているか
score変数に入っている値
&score変数 score が置かれている場所

この違いは、ポインタ学習の最初の最重要ポイントです。
変数名だけなら値、&変数名ならアドレス、という対応をここでしっかり覚えておきましょう。

配列のアドレスも考えてみる

変数がひとつだけの場合でもアドレスはありますが、配列になるとアドレスの考え方がさらに分かりやすくなります。

たとえば、int 型の配列 data[5] を宣言したとします。
int 型が 4 バイトの環境では、各要素は 4 バイトずつ並んで配置されることが多いです。
そのため、隣り合う要素のアドレスも 4 バイトずつ増えていくイメージになります。

配列のアドレスのイメージ

要素例のアドレス
data[0]1000
data[1]1004
data[2]1008
data[3]1012
data[4]1016

実際のアドレスはこのような単純な数字ではなく、通常は 16 進数で表示されます。
また、実行するたびに同じ値になるとは限りません。
ここで重要なのは、配列の要素が連続して並ぶこと、そして型の大きさに応じてアドレスの間隔が決まることです。

この感覚は、あとでポインタと配列の関係を学ぶときにとても役立ちます。

図で理解する

この図は、変数 score がメモリ上のどこかに配置され、その場所に 80 という値が保存されていることを表現しています。
つまり、変数には中身としての値があり、同時にメモリ上の場所としてのアドレスもあります。

アドレスはふつう 16進数で表示される

実際にプログラムでアドレスを表示すると、10進数ではなく 16進数の形で出てくることが一般的です。
たとえば 000000A1B2C3D4E0 のような形式で表示されることがあります。

これは、コンピュータの内部で扱うメモリの位置を表すのに 16進数が都合よいからです。
桁数が短くなり、ビットとの対応も考えやすいため、アドレス表記にはよく使われます。

ただし、ここでアドレスの数字そのものを暗記する必要はありません。
大切なのは、アドレスは実行環境によって変わること、同じプログラムでも毎回同じ値になるとは限らないこと、そしてアドレスは値そのものではなく場所を表していることです。

この点を誤解しないようにしておくと、実行結果を見たときに混乱しにくくなります。

変数のアドレスを調べる方法

C言語では、変数のアドレスを調べるために & を使います。
この & はアドレス演算子と呼ばれ、変数名の前に付けることで、その変数が置かれている場所を取り出せます。

たとえば、変数 number があるとき、

  • number は変数の値
  • &number は変数のアドレス

を表します。

この違いは短いですが、とても重要です。
ポインタはまさにこのアドレスを扱うための仕組みなので、まずは & を使ってアドレスを取得できることを確認しておきましょう。

シンプルなプログラム例

ここでは気温を表す変数 temperature を使って、値とアドレスを表示してみます。

ファイル名:11_1_1.c

#include <stdio.h>

int main(void)
{
    int temperature = 25;

    printf("現在の気温は %d 度です。\n", temperature);
    printf("temperature の保存場所は %p です。\n", (void *)&temperature);

    return 0;
}

このプログラムのポイント

内容
int temperature = 25;int 型の変数 temperature を用意し、25 を入れる
printf("現在の気温は %d 度です。\n", temperature);変数の値を表示する
printf("temperature の保存場所は %p です。\n", (void *)&temperature);変数のアドレスを表示する

ここで 1 行目の printf では、temperature の中に入っている値を表示しています。
つまり、変数名をそのまま使っているので、取り出されるのは値です。

一方で 2 行目の printf では、&temperature を使っています。
これは temperature という変数がメモリ上のどこに置かれているか、つまり保存場所を表示しています。

なお、%p でアドレスを表示するときは、(void *)&temperature のように void ポインタへ変換して渡す書き方がよく使われます。
学習の最初では &temperature と考えてかまいませんが、実際のコードではこの形も見かけるので、少しずつ慣れていくとよいでしょう。

実行結果のイメージ

実行すると、たとえば次のような表示になります。

現在の気温は 25 度です。
temperature の保存場所は 000000B7C1AFFA2C です。

ここで表示されるアドレスは、みなさんの環境では別の値になります。
同じソースコードでも、OS やコンパイラ、実行したタイミングによって表示結果が変わることがあります。
ですので、アドレスの数字がサンプルと違っていても、まったく問題ありません。

見るべきなのは数字そのものではなく、

  • 値は 25 と表示される
  • アドレスは何らかの 16進数らしい値で表示される

という違いです。

値とアドレスを混同しないことが大切

ポインタの学習で最初につまずきやすいのは、変数の値と変数のアドレスを頭の中で混同してしまうことです。
ここをきちんと整理しておくと、その後の理解がかなり楽になります。

値とアドレスの違い

書き方意味
temperature変数に入っている値
&temperature変数が置かれているアドレス

たとえば、temperature が 25 だったとしても、&temperature が 25 になるわけではありません。
前者は中身、後者は場所です。
ここを住所と荷物の違いとしてイメージすると分かりやすいです。

  • temperature は荷物の中身
  • &temperature は荷物が置いてある住所

この感覚が持てるようになると、ポインタ変数は「住所を入れるための変数なんだな」と自然につながっていきます。

図で理解する

この図は、変数名だけを書いたときは中に入っている値を見ること、&変数名と書いたときはその変数が置かれている場所を見ることを表しています。
ポインタの学習では、この違いを頭の中で切り分けられるかどうかがとても大切なです。

なぜアドレスを扱える必要があるのか

ここまで読むと、「値が使えれば十分では」と感じるかもしれません。
たしかに、簡単なプログラムでは値だけを扱っても動作します。
しかし、C言語ではアドレスを使えることによって、より柔軟で効率のよいプログラムが書けるようになります。

たとえば、次のような場面です。

場面ポインタが役立つ理由
関数で変数を書き換えたい変数のアドレスを渡せば、元のデータを直接変更できる
配列を扱いたい先頭要素の位置を基準に順番にアクセスできる
文字列を扱いたい文字の並びを先頭からたどれる
動的メモリを使いたい必要な分のメモリを確保して、その場所を保持できる

つまり、ポインタは単なる難しい文法ではなく、C言語が持つ柔軟さや高速さを支える重要な仕組みなのです。

最初は アドレスを取り出す ことだけ押さえれば大丈夫

ポインタという言葉を聞くと、

  • * を使う書き方
  • 間接参照
  • 配列との関係
  • 関数への引き渡し
  • 動的メモリ確保

など、いろいろな内容が一気に思い浮かぶかもしれません。
でも最初の段階では、そこまで全部を同時に理解しようとしなくて大丈夫です。

まずは次の 2 つをしっかり押さえれば十分です。

基本意味
変数名値を表す
&変数名アドレスを表す

この 2 つが自然に区別できるようになると、次に学ぶポインタ変数や * を使った操作もぐっと理解しやすくなります。

ポインタは、いきなり全部を覚えると難しく見えます。
でも、変数には値と場所の両方がある、という基本からひとつずつ積み上げていけば、ちゃんと理解できる内容です。
まずはアドレスを見る練習を通して、メモリの中でデータがどう存在しているのかを少しずつつかんでいきましょう。