はじめに
C言語は、私がエンジニアになるにあたって一番最初に勉強したさせられたプログラミング言語です。「プログラミングって楽しいかもしれない...!」と思い始めた矢先に登場するこのポインタと呼ばれる単元は、当時のプログラミング初心者であった私を挫折させました。正直に言ってしまえばくっそ難しいです。
それでも乗り越えたい...!という初心者エンジニアさんにこの記事をお送りしたいと思います。
ポインタとは?
ポインタ(pointer)と聞くと、プレゼンテーション等でよく使われるこれを思い浮かべませんか?
手に持ってスライド中の大切なところを指し示すために使われるイメージの強いポインタですが、この「指し示す」というのが非常に重要です。C言語においてもレーザーポインタのように指し示すことができます。
ポインタが指し示すもの
プレゼン中でスライドの今話している該当箇所をレーザーポインタで指し示すのと同じように、ポインタはとある場所の1点を指し示すことができます。
C言語におけるポインタはなんの場所の1点を指し示すかというと、**アドレス(address)**を指し示します。そしてなんのアドレスかというと、**オブジェクト(object)**のアドレスです。
さらに噛み砕いていきます。C言語では変数と呼ばれるものを宣言することができました。例えば、次のようなプログラムにおいて
#include<stdio.h>
int main(void)
{
int x; /* xはint型の変数 */
x = 5; /* 変数xに5を代入 */
printf("x:%d", x); /* xの値を表示 */
return 0;
}
x
というのが変数です。変数とは、数値や文字を格納するための「箱」みたいなものでありオブジェクトです。
ところでこのオブジェクトは、当然作ったからには5を代入することができたり、その変数の値を表示できたりと色々なことができますが、このオブジェクト自体そもそもどこに存在するのでしょう?
正解はメモリです。メモリとは、広大な空間を持つ記憶域であり、その中の1点にオブジェクトが存在しています。広大な空間の中の1点に存在するわけですから、このオブジェクトを端から端まで探していくのは極めて大変な作業です。そこでアドレスという、いわば住所がその1点についています。一度ここで図を使って整理しましょう。
この図では広大なメモリの中の1点のアドレス「0x7ffeed0dca38」にオブジェクトのxが存在し、その中に数値の5が代入されていることになります。実際にプログラムを作って確認してみましょう。
#include<stdio.h>
int main(void)
{
int x; /* xはint型の変数 */
x = 5; /* 変数xに5を代入 */
printf("x:%p", &x); /* xのアドレスを表示 */
return 0;
}
このプログラムをコンパイルし実行すると、おそらく人それぞれで異なる値が表示されると思います。私の場合はx:0x7ffeed0dca38
になりました。
簡単にプログラムの解説をすると、printf
による表示の際の「&」はアドレス演算子と呼ばれるものでx
のメモリ上のアドレス値を表します。そして、これを表示するためには%p
を使って指定したオブジェクトのアドレスを出力します。
さて、ここまで色々と書いてきましたが、肝心のポインタの話に戻りましょう。最初の方でポインタはオブジェクトのアドレスを指し示しているということを書きました。上の図において、オブジェクトx
のアドレス0x7ffeed0dca38
を指し示しているのは、緑の矢印(→)ですね。ポインタはオブジェクトのアドレスを格納していることになります。イメージはこんな感じです。
少し情報量が多いですね。整理しましょう。オブジェクトx
がありますね。これのアドレス0x7ffeed0dca38
をポインタ変数と呼ばれる箱に格納します。このポインタ変数を図ではp_x
とおいています。
このp_x
は当然ながらオブジェクトx
のアドレス0x7ffeed0dca38
を持っているため、この情報を表示することができます。これに加えて、このポインタ変数が指し示している1点のオブジェクトx
の格納値も表示することができるのです。xの格納値5を出力するためには、***p_x
**のように米印(アスタリスク)を置きます。プログラムで確認してみましょう。
#include<stdio.h>
int main(void)
{
int x; /* xはint型の変数 */
x = 5; /* 変数xに5を代入 */
printf(" x:%d\n", x); /* xの値を表示 */
printf(" &x:%p\n", &x); /* xのアドレスを表示 */
int *p_x; /* p_xはポインタ変数 */
p_x = &x; /* ポインタ変数p_xにxのアドレスを代入 */
printf(" p_x:%p\n", p_x); /* p_xの値を表示 */
printf("*p_x:%d\n", *p_x); /* *p_xの値を表示 */
return 0;
}
/*----実行結果------
x:5
&x:0x7ffeed0dca38
p_x:0x7ffeed0dca38
*p_x:5
---------------- */
ポインタが難しいと呼ばれる所以はこのプログラムに詰まっています。上2つの実行結果は先ほどのプログラムと全く同じです。ポインタ変数は普通の変数同様に宣言ができますが***
マークがつきます。つくったp_x
には先ほどの図のようにオブジェクトx
のアドレスを代入します。このとき、*p_x
ではなくp_x
に代入しています。下のprintf
にて出力される結果をみてみましょう。p_x
にはオブジェクトxのアドレス**、*p_x
にはオブジェクトxの格納値が出力されます。
&
と*
の区別がしづらいので整理しましょう。
演算子 | 名前 | 説明 |
---|---|---|
& | アドレス演算子 | オブジェクトのアドレスを取り出す |
* | 関接演算子 | それが指し示すものの中身 |
p_x
はオブジェクトx
を指し示すポインタ変数であり、アドレスが格納されています。*p_x
という記述にするとその指し示したオブジェクトの中身、すなわち格納された5を得ることができます。
実は...
先ほどのプログラムにおけるポインタ変数p_x
の宣言ですが、実は下記の記述でも全く問題がありません。寧ろ理解がしやすいと思います。
int* p_x;
p_x = &x;
int *p_x
という書き方だと、int型なの?と困惑された方が多いかと思います。確かにp_x
という変数はint型のオブジェクトのアドレスを指し示すデータなので、本来であればint* p_x
と書くべきです。しかし、一行で複数の変数を宣言しようとした時に、その書き方だとかえって理解し難くなってしまうので、int *p_x
が一般になりました。
int* p_x, p_y;
/* int*型のp_x, int型のp_yを用意してしまう */
最後に
今回は導入編なので、ここまでにしておきたいと思います。ポインタはまだまだ奥が深く難しいです。なるべく早めに記事にしたいと思います。完成次第、下記にリンクを貼っておきます。