【基礎から学ぶプログラミング言語】 C言語/ポインタとは何なのか?数値の変遷を実際に確認しながら解説!

IT
スポンサーリンク
スポンサーリンク
スポンサーリンク

私たちは日常生活で何気なくパソコンやスマートフォンというコンピュータを使用しています。
これらのコンピュータが普通に動作しているのは、そのようにプログラミング言語が記述されているからです。
本記事は、そんなプログラミング言語について実際に学びながら要点をまとめていったメモという位置付けになります。
私は専攻が電気でプログラムに関しては全くの初心者ですので、同様に初心者の方には理解しやすくなっているかと思います。

今回は、「C言語/ポインタとは何なのか」についての説明です。

1.初めに

前回はアドレスとは何なのかを説明しましたので、今回からポインタについて説明していきます。
C言語を理解する上での壁とよく言われているようですが、順序を追っていけば別にそんな難しい内容では無いです。
現に、C言語を自主勉だけで学んでいる素人がこうして理解しているわけですし。

今回は、そもそもポインタとは何なのかという基礎的なところの解説をしていきます。

2.ポインタとは何なのか?

これまで定義してきた変数は、決まった値がありましたよね?

例えば、【int a = 10;】と記述すれば整数aが10と初期化されます。
当たり前ですね。
このように、普通に定義・初期化してきた変数は実体があるアドレスの決まった数値だったんですよね。

それに対してポインタとは、色んな変数に“なりすまし”ができる実体の無い変数のことです。

要は、a・b・cという変数を定義・初期化してあったら、やりようによってはa・b・cという変数の値を取得してなりすますことができます。
変な例えをすると、Aさんのドッペルゲンガーが実際にAさんになりすまして行動し、Aさん所有のクレジットカードを勝手に使用していたとします。
このドッペルゲンガーみたいな行動をポインタで実行できます。
まあ、ポインタはこんな悪さをするというわけではありませんけども…。

ちなみに、単にポインタとも呼びますし、ポインタ変数と呼ばれていることもあります。

3.ポインタを実際に使用してみる

言葉で説明するよりも実際にどんなものなのか見た方がイメージがわきますので、早速ポインタの使用例を見ていきましょう。

#include<stdio.h>

void main() {
int a = 10;
int b = 20;
int *p = NULL;
p = &a;
printf(“a = %d。b = %d。*p = %d。\n”, a, b, *p);
}

図1

6行目に【*p】という表記がありますね?
このように【*(アスタリスク)】を付けることが、ポインタ型の変数を定義するためのルールです。
要するに、このint型の変数pがポインタになります。
※「*」が付いていればポインタ扱いになるので、【int *p = NULL;】でも【int* p = NULL;】でもpはポインタということになります。

ポインタの動きは一旦置いておいて、ここでもう一つ気になる点があるかと思います。
そう、【NULL(ヌル)】というヤツです。
この【NULL】は過去の記事でもtime関数の使い方の説明時などにしれっと登場しています。

【NULL】とは、一般的には何もない空の状態を表しています。

ポインタも変数の一種ですので、初期化が必須になります。
ただ、初期化をしたいけど初期値に特にこだわりが無いという場合に【NULL】と入力するルールになっています。
初期値が決まっているならその値を入力してください。

上記のプログラムを実行すると、以下のようになります。

図2

変数a及び変数bは定義した値をprintfで表示しているだけですね。

では、*pはどうなっているのかというと、変数aと同じ値になりすましています。

こうなっている理由は、7行目にあります。
【p = &a;】と記述されていますね?
前回、“&(アンパサンド)”を付けるとアドレスが割り当てられると説明しました。
つまり、ここで行っている処理は「ポインタ変数であるpに変数aのアドレスを入れてください」という内容になります。
こうすることで、ポインタは他の数値になりすますのです。
ポインタはアドレスを入れるための変数ということですね。

ちなみに、printfでは【*p】と記述しているように、しっかりと“*”まで記述してあげないとpの値は読み取れないので、そこは注意しましょう。
アドレスを入力する際は逆に“*”は不要になるので、微妙にややこしいんですよね。

なりすましの本当の意味

ここまでの説明だけでは、『アドレスを読み取ってる点は変わってるけど、結果的にただ数値を代入しているだけでは?』と思うのではないでしょうか?
実際、この時点ではそうです。

では、ポインタとしての機能の真骨頂を見ていきましょう。

先程のプログラムに追加で記述をしていきます。

#include<stdio.h>

void main() {
int a = 10;
int b = 20;
int *p = NULL;
p = &a;
printf(“a = %d。b = %d。*p = %d。\n”, a, b, *p);
*p = 30;
printf(“a = %d。b = %d。*p = %d。\n”, a, b, *p);
}

図3

新しい処理として追加されたのは9行目だけです。
*pが30になるように値を指定していますね。
その後に、10行目でこの状態で再び変数a・変数b・ポインタ変数*pをprintfで出力しているだけです。

このプログラムを実行すると、どうなると思いますか?
ポインタについてよく知らない状態だと、「a=10,b=20,*p=30」になると考えるかと思います。

実際にプログラムを実行すると、以下のようになります。

図4

*pが30になっているのは合っているのですが、何故かaの値も30に変わっていますね
これがポインタのなりすましによって引き起こされる現象です。

先程述べたように、*pだとpのアドレスが持っている数値を読み出します。
なので、*pを30に変えると、*pが取得しているアドレスの数値ごと書き換えられます。
その結果、pになりすまされているaごと数値が変化するわけです。

最初の方に挙げたドッペルゲンガーの例えみたいなことをしているでしょう?

再度なりすましたらどうなる?

では、上記の状態から、今度はbのアドレスを代入して動きを見てみましょう。

#include<stdio.h>

void main() {
int a = 10;
int b = 20;
int *p = NULL;
p = &a;
printf(“a = %d。b = %d。*p = %d。\n”, a, b, *p);
*p = 30;
printf(“a = %d。b = %d。*p = %d。\n”, a, b, *p);
p = &b;
printf(“a = %d。b = %d。*p = %d。\n”, a, b, *p);
}

図5

11行目でbのアドレスをpに代入し、その結果を12行目で表示しています。
このプログラムを実行すると、以下のようになります。

図6

pにbのアドレスを代入したので、*pの数値がbと同じ20に変化していますね。

ここで、aは30のまま数値が変動していません。
先程までpはaになりすましていたのに、何故ここでaの値は変化しないのでしょうか?
理由は単純、*pの数値を変化させたわけではなく、pのアドレスを変化させたからです。

9行目の【*p = 30;】では、ポインタである*pの数値を30に変化させました。
この場合、*pの数値はaのアドレスに格納された数値自体になっているので、そのアドレスに保存されている数値が変化しました。

11行目の【p = &b;】では、pのアドレスをbに変化させました。
この場合、あくまで変化するのはpが参照しようとしているアドレス情報であり、aのアドレス情報ではないんですよね
だからpのアドレスがbと同じになり、*pがbと同じ数値になっているのです。

CDに例えるとわかりやすいかと思います。

aというデータが書き込まれたCDをセットしてCDドライバで読み取っているデータがpだと思ってください。
ここでpのデータを書き換えてしまったのが【*p = 30;】の処理です。
このデータを書き換えてしまうと、当然ながらセットされているCDのデータ自体が書き換えられてしまいます。
ということは、データaが変化してしまうわけです。

【p = &b;】の処理は、CDドライバに読み込ませるCDをaではなくbに交換しています。
だから、aのデータが書き込まれたCDは普通に取り出され、データは変化しません。
そして、代わりにセットされたCDから読み出されたbのデータがpとして認識されるわけです。

その為、追加で【*p = 50;】という処理をさせると、今度はbのデータと*pが50に切り替わります。

#include<stdio.h>

void main() {
int a = 10;
int b = 20;
int *p = NULL;
p = &a;
printf(“a = %d。b = %d。*p = %d。\n”, a, b, *p);
*p = 30;
printf(“a = %d。b = %d。*p = %d。\n”, a, b, *p);
p = &b;
printf(“a = %d。b = %d。*p = %d。\n”, a, b, *p);
*p = 50;
printf(“a = %d。b = %d。*p = %d。\n”, a, b, *p);
}

図7
図8 実行結果

仕組みさえ理解してしまえば、そんな難しくないでしょう?

4.なりすまさないとどうなる?

ポインタ変数はなりすますものだとわかってもらえたかと思います。
では、何もなりすまさなかった場合はどうなると思いますか?
ということで、そんな場合についても確認してみましょう。

#include<stdio.h>

void main() {
int *p = NULL;
*p = 10;
}

図9

本来の使い方なら5行目で他の変数などになりすまさなければいけないところを、アドレスを指定せずに数値を突っ込んでみました。
このプログラムを実行すると、私の動作環境の場合は実行できてしまいました。
ただ、環境によってはプログラムが異常終了されます。

本来の使い方を守らないと碌なことにはならないと覚えておきましょう。

以上、C言語/ポインタとは何なのかについての説明でした。