C言語のポインタ、それは鬼門か?
まあ、分かってしまえば簡単なのだが、分からない人には分からない(当たり前か)。
それがC言語のポインタだ。
そもそもなぜポインタが必要なのか?
その辺りから考えてみればポインタの必然性が理解出来ると思う。
必然性が理解出来たら、ポインタを使ってCプログラムを書く事も出来るようになるだろう。
では、本題に入ろう。
C言語にポインタが登場する理由
ポインタの無い言語の場合
例えば、どういう言語でも
int a; int array[10];
などの変数宣言があるだろう。ここではC言語で書いてみたが、他の言語でも同じだ。
変数を一個宣言するのか、あるいは配列として10個分の変数領域を確保するか、そういう機能だ。
どんな言語にも備わっている機能であり、プログラミングには不可欠の機能だ。
で、これらを使ってプログラムを書くわけだが、
a = 2;
とすればその変数aの中に整数の2が保持される。
ここで、
a = a + 1;
とすれば、
2 = 2 + 1
となり、数学的には間違った数式だが、プログラミングの世界では、
右辺の意味は、現在のaが持っている値2に1を加える。つまり右辺の値は3になる。
それを左辺の変数aに代入すると言う意味だ。
なので、値の代入に=を使うのではなくて、気分的には、
a ← a + 1
こんな感じか(C言語にはこういう記述方法は無いが)。
なので最終的には変数aの中身は3になるのだ。
まあ、そんな事は知っているわいと言う人も多いだろう。
変数aは誰がどこで管理しているのか?
さて、上記のような計算をする場合、変数aの値はコンピュータのメモリ上のどこかに記憶されていて、あなたがその値を
a = 3;
という代入文で3にすれば、メモリ上のaの中身が3になる。
C以外のプログラミング言語の場合には、変数aの値がメモリ上のどこに保存されているのかなどという情報は気にしなくても良い。と言うよりはそういう情報は得られない場合が多い。
兎に角、好きなだけ変数を宣言しておけば、誰かが上手い具合にそれらの変数を保持する領域をパソコンのメモリ上に確保して値を管理してくれている。
で、プログラムの実行が終了した時点でそれらのメモリ領域は解放されて、他のプログラムが別の用途に利用できるようになる。
こういうふうに変数の管理をしてくれているのは誰なのかを考えなくてもプログラムは書けるし動く。
C言語でプログラムを書く場合には、それが誰なのかをか少しは気にしておく必要がある。
メモリを使って上手い具合に変数を管理してくれているのは、それは、そのプログラムをコンパイルしたコンパイラさんと、その実行プログラムが動いているWindowsなどのOS(オペレーティングシステム)さんの共同作業だ。
コンパイラではなくてインタープリタ言語の場合にも、まあ、似たようなもんだ。
まとめると、変数をメモリ上で上手く管理してくれているのは、あなたが使っているプログラミング言語(コンパイラやインタープリタ)とそれが動いているOSさん達なのだ。
C以外の言語では、そういう事を意識せずにプログラムが書ける。
一方、C言語の場合にもポインタを一切使わなければそういう事を意識せずにプログラムを書く事は可能である。
しかしながら現実的には、ポインタは頻繁に登場する。
ポインタとは何なのか?
ポインタとはメモリのアドレスを記憶する変数の事だ。
アドレスは番地とも言われる。
なのでまあ、
ポインタとはアドレスと思っていても良いかな。
32bitアプリならこの後説明するようにアドレス空間は32bit=4byteなので、その4バイトの値を保持するポインタ変数は4バイトの大きさが必要となる。
そしてそのポインタ変数もメモリ上のどこかのアドレスに保管されているのだ。
パソコンの中には、メモリが沢山入ってる。
その中に各種のデータが蓄えられて、WindowsやOS Xその他のオペレーティングシステムが動いているのだ。
例えば4GBメモリを買った場合、メモリには
4GB = 4,000,000,000 バイト = 40億バイト
という物凄い数の記憶領域がある。
4GBでも安くなったもんだ。
1バイトというのは8ビットの事だ。
1ビットは記憶の最小の単位であり、0か1かどちらかの値を保持出来る。
2進数だ。
要するにメモリはキャパシタと言う部品で構成されているので、
キャパシタに電気を蓄えている状態
キャパシタが空の状態
の二値状態をとれる。それをコンピュータの中では0と1との二値状態に対応させているのだ。
とは言っても、プログラミングをする場合には、ビット単位で情報を記憶する事はあまりなくて、どちらかと言うとそれが8個集まった1バイトを単位として情報を記憶するほうが多いかな。中にはビットフィールドとかビット演算を使いまくると言うビットの達人の人もいるかも知れないが、ワテは苦手だ。
下図は、8ビットの記憶域に 0000 0110 という2進数が入っている様子を示している。
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 |
00000110
と書いても良いが、分かり易くするために
0000 0110
のように4桁ずつに区切る事が多い。
この場合、10進数に変換すると、
位置 | 2進値 | 10進値 |
7 | 0 | 128 |
6 | 0 | 64 |
5 | 0 | 32 |
4 | 0 | 16 |
3 | 0 | 8 |
2 | 1 | 4 |
1 | 1 | 2 |
0 | 0 | 0 |
10進の4と2の位置のビットが立っている(=1)ので、10進数の6(=4+2)となる訳だ。
メモリ空間
4GBメモリの場合には 0x00000000~0xFFFFFFFF 番地までの部屋がある。
なお、16進数の場合には先頭に0xを付けることによって明示的に16進数で有る事を表現する場合が多い。
16進数 | 10進数 | データ | |||||||
---|---|---|---|---|---|---|---|---|---|
0000 0000 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
0000 0001 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
・・・ | ・・・ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
・・・ | ・・・ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
・・・ | ・・・ | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
FFFF FFFE | 4294967294 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
FFFF FFFF | 4294967295 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
4GBメモリ空間
各部屋にはデータを8ビットで保持出来る。
整数なら -128 ~ 127 (8bitの最上位bitを符号用に使う)
unsigned整数なら 0 ~ 255
までを保持出来る。
部屋番号は0からFFFFFFFFまでの4GB=232個あり連番になっている。
約40億部屋もあるのだ。
4GBでも十分巨大だが、現在では32GBとか64GBのメモリを搭載できるパソコンもある。
OSはこれらの巨大なメモリ空間を使って、OS自身のプログラムや、各ユーザーが実行する各種プログラムの本体や、そのプログラムで使う変数を格納して管理しているのだ。
働き者だ。
C言語にポインタが登場する理由
さて、パソコンを動かしているCPU(中央処理装置)にはインテルやAMDのプロセッサが使われている。
それらのCPUは、電源がONされるとメモリの特定の番地(例えばゼロ番地)にあるデータを取って来て、その意味を解釈してその命令を実行する。そして次の番地のデータを取って来て、意味を解釈してその命令を実行する、、、という手順をひたすら繰り返している。
最近のプロセッサは3GHzとか4GHzのクロックで動いているので、毎秒当たり、30億とか40億個の命令が実行出来る。物凄い世界だ。
CPUが読み出して実行しているそのメモリ上にあるデータが何かと言うと、それがオペレーティングシステムのソフトウェア(OS)と言う事になる。
OSが起動すると、他に必要となる各種のプログラムも起動する。ネットワークを管理するプログラムとか、グラフィック関連を制御しているプログラムとか、沢山ある。それらはWindowsならタスクマネージャで確認出来る。
この場合、OSさんは、1個のCPUを使ってそれらの処理を行っているので、CPUのレベルでは同時に沢山の仕事は出来ない(1CPUに4コアとか入っている場合には並列に処理できる場合もあるが)。なので、OSさんは沢山のプログラムが上手い具合に動くように、ごく短い時間間隔でプログラムAさん、プログラムBさん、プログラムCさんのようにCPUを割り当てて、あたかも三つのプログラムが同時に動いているように見せているのだ。
CPUだけでなく、メモリの読み書きもこのように時間分割で行っている。なのでプログラムAさんがメモリを読み書きする場合には、他の人は読み書き出来ないので休んでいる。で、Aさんがメモリのどこそこのアドレスのデータを読み出したいと言うと、OSさんがCPUを使ってメモリのデータを読み取ってくれて、Aさんはそのデータを使って何らかの処理を行う。
と言う感じで、兎に角OSさんは、CPUを切り替えたり、多数のプロセス間でメモリを上手い具合に使い分けたりと、ややこしい作業を一手に引き受けてやってくれているのだ。
もともとC言語はそういうオペレーティングシステムを開発する用途で設計された言語なので、メモリ上の何番地のデータを読み取ったり、あるいは書き込んだりという操作が当然必要になるのだ。
その為にC言語では、変数aを宣言した場合に、そのaが何番地のアドレスに格納されているかと言う情報もユーザーが自由にアクセスできる仕様の言語設計になっている。
この辺りが他の言語とは大きく異なる点だ。ポインタの無い言語の場合でも、変数を宣言するとその変数はメモリ上のどこかの番地に保存されるので、そのアドレスは存在する。しかしユーザーにはそのポインタ情報を取得する手段が公開されていないので何番地にその変数が格納されているかなどは知る手段がない。
知らなくてもその言語が上手く変数を管理してくれているので何ら問題はないのだ。その場合でも、その言語は内部的にはポインタを使ってそれらの変数を管理している事になる。
しかしながら、ポインタを扱う事が出来ない言語では、アドレスを意識する処理、例えばオペレーティングシステムの開発とか、あるいは、ハードウェア制御などの用途にはあまり向いていない。
なので、例えば、電子機器をプログラミングする言語にもC言語が良く利用されるのはそういう理由もある(と思う)。
こういう奴だ。
C言語はもともとUNIXオペレーティングシステムの開発の用途に設計された言語であるが、その辺りの歴史的背景を知りたい人は、C言語を開発したベル研究所の
Dennis M. Ritchie (Bell Labs/Lucent Technologies)
さんの論文を読んでみるのも良いだろう。
The Development of the C Language (PDF)
The Development of the C Language (HTML)
勿論英文なので、ワテにはよく理解出来ない。
そういう人には、こんな本がお勧めかもしれない。
本日の時点でベストセラー1位だ。
UNIXマニアな人にはたまらない本なのかも知れないが、ワテは読んでいないので中身は未確認だ。
一体全体、どんな内容なのか気になる。
続く
コメント