例え話をしないC言語のポインタの説明 のマネ
に触発されたエントリです。
読んでてなんかしっくりこないなあ。と思ったので、私なりに書いてみようと思います。
メモリとは
ポインタを理解するためには、上のエントリの通りまずはメモリを知る必要があります。
メモリとは一時的な記憶領域で、ハードディスクやSSDとは違い電源を切ると消えてしまう性質があります。
その代わり、ハードディスクやSSDよりも高速に読み書きが出来る。という特徴があります。
パソコンやスマートフォンでは、ハードディスクやSSDのような記憶領域から必要なデータを読み込んで配置してプログラムを実行するのに使用します。
コンピュータは 0/1 で処理される。とよく言われます。
一般的なメモリでは通電しているか、していないかで 0/1 を表現します。
単位として バイト/ビット という単語を聞いたことがあると思いますが
上の図で言うと、0/1 ひとつひとつを表現するために必要なデータサイズを 1ビット と呼びます。
そして、8ビットあつまると、1バイトと表現します。
メモリは4GBや8GBといった単位でコンピュータに内蔵されていますが、
4GBの場合は 4,294,967,296 バイト = 34,359,738,368 ビットのデータを保持出来る。ということになります。
メモリにデータを配置する場合や読み出す場合は メモリアドレス=メモリ上の住所 を指定してアクセスすることになります。
メモリは一次元的に続くデータ領域で、1バイト目を0番地。2バイト目を1番地 というように呼びます。
コンピュータが実行するプログラムデータや、プログラムが動作するために必要な一直なデータなどは全てこのメモリのどこかに配置されて実行されることになります。
ポインタとは
ポインタとは、ずばり メモリアドレス のことです。
例えば、プログラムで 1バイトのデータを保持する data という領域を確保したとしましょう。
これがメモリのどこに確保されるのかは実装方法により、スタック領域やヒープ領域といった場所から確保されますが、今回の趣旨からは外れるので詳しくは語りません。
とりあえずメモリのどこかに1バイトの空間が確保されます。
そして、data というデータを確保したメモリアドレスが data のポインタということになります。
たとえば、data は メモリアドレス1番地に配置されて 3 という数値を格納したとすると以下の図のようになります。
この場合、C 言語的に言えば data(dataの中身) は 3 であり、&data(データのポインタ) は 1になります。
なぜポインタを使うのか
たとえば、data を受け取って data を 1増やした値を返すプログラムがあったとしましょう。
uint8 addOne(uint8 data) {
return data + 1;
}
プログラム的に書くとこんな感じです。
この程度のプログラムの場合、近代的なコンパイラを使用すると最適化されてこのようなことは起こらないと思いますが、原始的に考えると
uint8 data = 3;
data = addOne(data);
というプログラムを書いた場合、3を設定した data と addOne の中の data は別のメモリアドレスが割り当てられ、addOne の中の data には 3を設定した data の内容がコピーされて実行されます。
今回 data のサイズは1バイトという非常に小さいデータだったので、それほど気にする必要は無いでしょうが、もっと大きなデータだった場合、データをコピーするのにかかる時間が勿体ないです。
最終的に 3を代入した data が 4 になってほしいわけですので、ここでポインタを使用します。
void addOne(uint8* data) {
*data += 1;
}uint8 data = 3;
addOne(&data);
こうすると、addOne は data のメモリアドレスを受け取って、
*data(dataポインタが示すメモリの中身)に1を加算する。ということになり、
メモリの内容をコピーせずに同じ処理を実行出来ました。
このように基本的にはポインタは例外の無いC言語で戻り値はエラーコードに使用し、関数の副作用は引数で受け取ったポインタの実体に対して行うために使われることが多かったです。