C言語の『ポインタ』について、自分なりの説明というものに挑戦してみました。プログラム初心者によるものなので、内容の保証は出来ません。"ポインタ"(Pointer)の名の通り、"指し示すもの"であり、何を指すかといえばハードメモリ上に置かれている変数のアドレスになります。
はじめに
新年も迎えて、Unityを触っているとやはりまだまだ勉強しなければならないことが、いっぱいあるなと改めて思った次第です。プログラミング自体にはだいぶ慣れてきました。簡単なものであれば、自分で考えてスクラッチからコードを書く、ということも幾分か出来るようになりました。
それで去年の秋以降、本ブログではプログラミング自体についていろいろ書いてみましたが、そろそろいいかな、と思ってきました。というのもそもそもUnityを使いたいけど、とりあえずプログラミングはプログラミングで勉強した方がいいだろう。というのがここ数ヶ月の課題でした。それがひと段落したので後はUnity上でやりながら勉強する、という段階に移ろう!ということです。
ということでプログラミング自体も楽しいのですが、ここらで1つ区切りをつけるという意味で、C言語における『ポインタ』というものについての個人的見解をまとめたいと思います。なんで今回こそUnityには全く関係ないです。これ以降はUnityに集中します。
まだまだ初心者ですので、C言語における最難関とされるポインタについて、はてなのような、かなり人目に触れるこのような場所で書くのはどうかとは思います。
しかし論理的な説明として、なるべくわかりやすいような形にまとめられるかも?
と思ったのでちょっと書いてみたいと思います。
ポインタとは?結局何?
ポインタ、ないしポインタ変数は結局どういうものかといえば、
ある変数の代役として使うことのできる変数です。
ここで疑問なのが、まずそもそもポインタは何のためにあるのか?そしてなんで代役が必要なのか?ということです。その変数を直接使えばいいじゃん?って思いますよね。
そこをどう説明できるか、考えてみよう!というのがここでの試みです。
ポインタはC言語の学ぶ上で初心者にとって、大きな障壁として語られます。ここで挫折してしまうという人がかなり多いようです。C#の事前学習のためだから、と最初は無視しましたが、確かに意味不明でした。
ポインタというものを理解するためには、コンピュータのハードとして構造、そしてC言語というものの特性(あるいはアセンブリ)、プログラミングにおける変数のスコープや関数の独立性などをロジカルに理解していくことが必要なのかなと思います。
ということで、順に説明していきます。
ハードとしてのコンピュータ
ざっくりと捉えます。ハードとして見たコンピューターというものは、
・メモリ 長期記憶用のHDD,SSD、と短期記憶用のRAMメモリ
・CPU 計算、演算装置
・入力出力装置 ディスプレイ、キーボード、などのインターフェイス
が組み合わさったもの、と解することが出来ます。
メモリはここでは“短期メモリ”の方を指し、全てに番号が割り振られた連続した作業台、と考えることとします。
そして実際のプログラミングが起動されると、ハードディスク上に保管されていたそのプログラムコード、OS上ではダブルクリックされるexeファイルなわけですが、それがメモリ上にロード、つまりコピーされ展開されます。
ポインタをデスクトップ上のショートカットに例える方もいます。ショートカットをダブルクリックするということは、本体のexeファイルをダブルクリックしていることと同じです。デスクトップ上のショートカットに実体はなく、ハードディスク上のどこに.exeがあるか?という位置情報のみを持っています。このショートカットによって、windowsであれば、Program Filesのどこかにあるであろうexeをダブルクリックせずとも直ぐにプログラムを起動することが出来ます。
計算機であるCPUと、作業台であるメモリがやり取りすることで、コンピュータとしての処理、計算というものが行われることになるわけです。
その際、そもそものプログラミングの中身は、データであったり処理の手順であったり
要は変数であったり関数の集合体なわけです。(ザックリ言うと)
ポインタについてなので変数に絞ります。
変数というのは数字だったり、文字だったりのデータ、値を格納するためのものです。
そして、そもそも変数の宣言というのはメモリ上において、そのタイプのデータのための場所を名前をつけて確保するということです。
(言い換えれば、メモリ上のある一定のスペースに名前を付けたものが変数)
メモリの上の住所、転勤多し。
メモリのその全てに番号が振り分けられているので、メモリ上に作られる変数は、その番号をそれぞれ持っていることになります。
その番号というのが、コンピュータにおける『アドレス』というものになります。
ここで重要なのは、そのアドレスというものは、実際にプログラムが起動されるまでは
どんな値があるのかというのはわからないことです。そのプログラム自体、あるいはそのプログラムの中における関数が呼び出される毎に変わります。
変数が作り出される際にメモリのどこに作るかはコンピュータ(正確にはコンパイラ)に任されているからです。という事は仮にもしそのアドレスが何かの際に必要になる場合には、その都度きちんとそれを保管するようなものが必要になる、と考えるのが自然です。つまり、アドレスを格納することができる変数こそがポインタ(ポインタ変数)です。ということで、とりあえず次に移ります。
関数の独立性。
C言語、あるいはその影響を受けたプログラミング言語は『関数の独立性』というものが確保されています。それはその関数の中で宣言された変数は、その関数の中でしか効力を持たないということです。
これは関数の中でしか使わない変数は、その関数の中で宣言するのが、リーダブルなコードとしていいんですが、もし関数の中で宣言された変数の適用範囲が、その外にまで行ってしまうと、誤って他の変数と混同してしまう恐れがあるのでそれを避けるためです。同様にその関数の外で宣言された変数は、その関数内部に効力をもたらしません。
Cは『値渡し』だけ
そしてC言語における大きな特徴が、関数に値渡ししかできないということです。値渡し、というのは変数という器のメーターに表示された数値を関数という機械に手打ちで入力する、というイメージで説明できます。変数そのものは関数に入ってません。変数の中に入っていたデータを読み取って、写しているだけです。
つまり変数それ自体を関数の中に入れることはできないというわけです。
では逆に関数に変数それ自体を入れなければいけないという状況はどういうものがあるのか。(別にreturnでその変数に返せばいいじゃないか?という疑問もありますね。)
変数に直接何かを入力したい、
1つの変数を複数の関数間で使いまわしたい。
とにかく変数を直接弄りたい!まぁいろいろあるみたいです。
scanf("%d", &num);
scanf関数は変数の中にキーボードから入力された値を代入する、という機能を持った関数です。でも変数の頭に付いてる&の記号はなんだろう?と思いますよね?
はい、あともう少しです!
変数を直接操作するための裏技
じゃあ、どうすればいいのか?とここで先ほどのアドレスが出てきます。
paizaのブラウザエディタにて。変数numのアドレスを表示させています。三回とも違いますね。 実行ごとにアドレスは変わるということです。
メモリにおけるアドレスは16進数で書かれた数値です。見慣れない形での表記になりますが、数は数に変わりありません。数値であるならば関数に入力することができるわけです。これを使えばいいんじゃね?となりますよね?ポインタはアドレスを代入することが出来るんでした。ということは・・・?
ポインタは代理人
つまりは、ポインタを使うということはこういうことです。
&はアドレス演算子といって頭に付けるとその変数のアドレスを表すことが出来ます。
(上記の写真によるコードはそれを利用してnumのアドレスを表示させました。)
それを使い、その変数のメモリ上のアドレスを関数に引数として入力し、
一方でその関数の中で宣言されたポインタ変数がそれを受け取る。
ポインタ変数は通常変数モードに切り替えることで、
格納されているアドレスにある変数の代わりを務めることが出来る。(間接参照できる。)そのポインタに関数の中で行われたことは、その外にある変数にも連動して反映される。よって関数に変数が入力する、ということを代替的に実行することができる。
これがC言語におけるポインタ、というものへの
現時点での自分なりの論理的な説明です。
そもそも値渡ししか出来ない、というのがC#からすれば不便そのものなんですが、
しかしポインタがないとされる言語も内部的にはポインタ機能はあるようで、
C言語学習は将来的には役立ってくれるだろうと思います。