ポインタとはメモリのアドレスを指し示す針のようなものです。本で言えばしおりにあたります。ポインタを使えばポインタ名を指定するだけでそのポインタが指し示すメモリの場所がすぐに分かります。本で言えば、しおりの刺さっている所を開けばそのページがすぐに分かるということです。そのしおりのように、プログラミングで使用するポインターは必要な時にだけ使えばいいのです。どういうときにポインタが必要になるのかは後で述べます。しかし、C言語でのプログラミングの際にポインタの使用は不可欠なものです。ここが分かればどんなプログラムでも組めるようになるでしょう。
ポインタを使う場合、次のように宣言します。
型 *ポインタ変数名
例を挙げると
char *pstr;
int *p_of_address;
float *pdata;
という風に宣言します。(変数名の頭にポインタの頭文字 p を付けると私達にとって分かりやすくなります。)
変数名は例えれば「しおりの絵柄等」しおりそのものの目印(特徴)です。
では、こういう風に宣言されたポインタ変数には一体何が入るのでしょうか?ポインタ変数には、メモリ上のアドレスの値が一つだけ入ります。二つ以上は入れられません。
しかし、宣言したばかりではどこを指しているのか全く分かりません。とんでもないところを指している可能性もあります。(OSの中核部とか)また、どこを指しているか分からないポインタをそのまま使うことはありません。最初に、ポインタが指し示す位置を設定してやる必要があります。「位置を設定」ということは、指し示すアドレスを代入するということです。
例えば、次のような変数があったとします。
char string[10];
int address;
float data;
この変数達のメモリ上でのアドレスをポインタに代入(設定)するには
pstr = &string[0];
p_of_address = &address;
pdata = &data;
と、見てもらったら分かるとおり、&を変数の頭に付けて修飾すればアドレスがポインタに代入されます。
この&をアドレス演算子と言います。
普通の変数の頭に&を付けると、その変数がメモリ上に存在するアドレスを意味するようになります。
よって、
data = 5
のとき、
&data は 5ではなく、6208 とか、アドレスを意味する値となります。(どういう値を取るかは全く未知の世界です。)このアドレスの値そのものはプログラマーが意識する必要は全くありません(特殊なプログラムを作る時は必要がありますが)。このアドレスの値がどうだからといって関係はないのです。とにかく、上の例では
pdata が 変数 data のアドレスを指し示している
ということが重要なのです。上の例で
pstr = &string[0];
これは文字列を扱う時の場合です。文字列を扱う時にポインタを利用する機会が多いのが事実です。
また、上の例では
pstr = string;
とすることもでき、この方が一般的です。配列変数の変数名だけを記述したものは、配列の先頭のアドレスを意味します。このときにかぎり、&は不要です。
string[2] に対してポインタを設定したい場合はどうすればいいのでしょうか?
pstr = &string[2];
こうすれば、ポインタはstring[2] の存在するアドレスを指し示すようになります。
ポインタに対する演算は、一般に、ポインタが指し示しているメモリのアドレス(位置)を前後に移動するために使います。
加算すると、ポインタはメモリの後方へ進み、減算すると前方に戻ります。
例えば、
pstr = string;
pstr++;
とすると pstr は string[1] を指し示します。
pstr = string;
pstr++;
pstr--;
とすると pstr は string[0] を指し示します。つまり、元に戻ってきます。
pstr = string;
pstr+= 5;
pstr-= 1;
とすると pstr は string[4] を指し示します。また次の例ではどうでしょうか?
pstr = string;
pstr++; //string[1]
pstr--; //string[0]
pstr--; //string[-1]?????
こうすると、大変なことになってしまいます。string[-1]というものは存在しませんので、最悪コンピュータが止まってしまう可能性もあります。つまり、安全を保障された
string[0]〜string[9]の範囲を超えてしまうと何が起こるか分からないということです。よって、ポインタの操作は十分注意を払う必要があります。
(よくWindowsで一般保護違反とかいうメッセージが出て強制終了されますが、そこで「詳細」を押した時に「○●のページ違反です」とかいうメッセージが表示された時はこういう限られた範囲を逸脱したポインタ操作が原因です。)
ポインタの演算には加減算しかありません。(メモリのアドレスに対する乗算、除算は無意味なものです。)
加減算とは、先ほども述べたようにポインタの移動です。一度移動したポインタはもう一度位置を初期化しない限り戻ってくることはありません。進めば進みっぱなしです。
さて、ポインタを宣言する時に型を指定しますが、この進む距離がこの型に関係してきます。
C言語で扱える型は数種類あり、メモリを確保する範囲が違います。
int 型 Windows系では 4バイト
double型 Windows系では 8 バイト
char型は 1バイト
このように、型によって必要となるメモリのサイズが違います。
char | ||||||||||||||||
int | ||||||||||||||||
double |
メモリのイメージは図で表わすと上のような感じです。
さて、
ポインタ pstr の加減算による移動距離はどのようにして決めるのでしょうか?この中にポインタの型を宣言時に決める答えがあります。
次のような場合を想定します。
int table[4];
もしint 型の配列要素の参照 にchar型のポインタを使ってみるとどうなるでしょうか?
次のようなプログラムでポインタを動かしてみます。
char *p;
(1) p = table;
(2) p++;
(3) p+= 3;
次の図を見てください。
(1)
char | ||||||||||||||||
int | table[0] | table[1] | table[2] | table[3] | ||||||||||||
double | ||||||||||||||||
p | ↑ |
(2)
char | ||||||||||||||||
int | table[0] | table[1] | table[2] | table[3] | ||||||||||||
double | ||||||||||||||||
p | ↑ |
あれれ??? p++ が table[1] じゃありませんね・・・。 変なところを指しています。(このページ一番下・解説 「ポインタ遊び」を参照)
(3)
char | ||||||||||||||||
int | table[0] | table[1] | table[2] | table[3] | ||||||||||||
double | ||||||||||||||||
p | ↑ |
結果的に p + 4 の位置が table[1] を指し示ています。
分かりましたでしょうか?ポインタの型を宣言したからと言って、ポインタの中身が int であるとか、そう言う意味ではないのです。中身はあくまでメモリ上のポインタが指し示すアドレスです。この型がポインタを移動する際の移動距離(●○バイトという感じで)を決定するのです。では、ポインタの(アドレスを格納する)サイズはどれだけあるのか?といいますと、処理系に大きく左右されますが、Windowsでは16ビットまたは32ビットとなっています。
****************************
char *pstr; int *p_of_address; float *pdata; **************************** char string[10]; int address; float data; **************************** pstr = &string[0]; p_of_address = &address; pdata = &data; **************************** |
ポインタがアドレスを指し示すものだという事は分かってもらえたと思います。
では、そのポインタが指し示す先にあるデータは何であるのか?ということを参照する必要性がプログラミングをしていく上であります。
ポインタの真髄は変数名(上の例では string,data など)を利用しなくてもポインタ名を使えばそこのアドレスにあるデータにアクセスできるということにあります。
pstr これはアドレスを意味します。
*pstr これはアドレスに存在するデータを意味します
ポインタ名を*で修飾することでポインタが指し示すアドレスにあるデータを参照できます。この*を間接演算子と呼びます。
乗算の*とは意味が違います。
次のようなプログラムがあります。
int i;
int numtable[5] ;
int *p_ntable;
for(i = 0;i < 5;i++)
numtable[i] = i;
p_ntable = &numtable[2];
numtable[5] には 0〜4の数字が順番に格納されます。
numtable[0] | 0 | |
numtable[1] | 1 | |
numtable[2] | 2 | ← |
numtable[3] | 3 | |
numtable[4] | 4 |
p_ntable には numtable[2] のデータが存在するメモリ上のアドレスが格納されます。
このアドレスをもとにnumtable[2] の数値”2” を取り出すには
*p_ntable
とします。printf文では
printf("%d",*p_ntable);
とすることで、2 が表示されます。
* と & を混同して訳が分からなくなる人が沢山いますが(僕もそうでした・・・)、
char **ppstr;
これを*が二つでポインタのポインタといいますが(後述)こういうのを使わない場合、つまり
char *pstr;
int *pdata;
など、単純なポインタを使用する場合、宣言した時点で
変数の場合
data;
&data;
ポインタの場合
pdata;
*pdata;
しか表現方法がありません。変数に*をつけたり、ポインタに&をつけるのは意味がありません。
今までの説明を次の表でまとめます。
文字配列 string に "abcdefghi "という順でデータが格納されているとします。
配列要素 | 配列の中身 | 配列の要素へのポインタの設定 | 左表をポインタによる記述に置き換え | ポインタの指し示すアドレスにある内容 |
string[0] | a | pstr = string;
(pstr = &string[0];) |
pstr = string;
(pstr = &string[0];) |
*pstr -> 'a' |
string[1] | b | pstr = &string[1]; | pstr + 1 | *pstr -> 'b' |
string[2] | c | pstr = &string[2]; | pstr + 2 | *pstr -> 'c' |
string[3] | d | pstr = &string[3]; | pstr + 3 | *pstr -> 'd' |
string[4] | e | pstr = &string[4]; | pstr + 4 | *pstr -> 'e' |
string[5] | f | pstr = &string[5]; | pstr + 5 | *pstr -> 'f' |
string[6] | g | pstr = &string[6]; | pstr + 6 | *pstr -> 'g' |
string[7] | h | pstr = &string[7]; | pstr + 7 | *pstr -> 'h' |
string[8] | i | pstr = &string[8]; | pstr + 8 | *pstr -> 'i' |
string[9] | \0 | pstr = &string[9]; | pstr + 9 | *pstr -> '\0' |
極普通のプログラムを組んでいる分にはポインタを使わなくても大丈夫だと思えます。しかし、次の章で扱う関数というものを利用するとき、ポインタは切っても切り離せない存在になります。関数利用時以外で良く使うのは配列を扱う場合です。
※この項目以降は特に必要を感じた場合だけ読んでください。学校の授業でやる程度のプログラムならこれ以降の項目は特に必要ありません。関数の章へ行って下さい。
ポインタではなく配列で扱うと効率が悪いことが良くあります。「効率が悪い」とはここではメモリの使用効率を主に指します。
例えば、コンピュータネットワークで電子メールを受信するプログラムを考えます。
配列で電子メールの文章を格納する入れ物を宣言するとします。
char MailTextData[100000];
電子メールはどんな長さのメールが届くか分からないのでこうやって万が一のことも考え余裕を持っておく必要があります。
これでも、100000バイト以上のサイズのメールが届いた時はあふれてしまいます。しかも、宣言でサイズを100000バイトに指定してしまうと、例えば大きさ10バイト
(10文字)程度のメールでもこの変数に格納することになり、確保した分のうちのメモリ使用効率は0.01%となり明らかに無駄です。
こういう時、ポインタが有効に使えます。
まず、
char *MailTextData;
と、ポインタとして宣言だけしておきます。この時点ではポインタの指し示す中身が何であるかは不定です。
次に、メールの長さが100000バイトであれば
MailTextData = (char *)malloc(100000);//C言語流
MailTextData = new char[100000];//C++言語流
10バイトであれば
MailTextData = (char *)malloc(10);//C言語流
MailTextData = new char[10];//C++言語流
と、ポインタの位置から必要量だけメモリを確保できます。
これをメモリの動的確保といいます。
確保が終われば、あとはMailTextDataを普通の配列のように扱うことが出来ます。
つまり、メモリを必要時に確保するときのメモリ上の位置決めとしてポインタが利用できるということです。
(例 : メモリの動的確保のプログラム)
main( )
printf("メモリを動的に確保します。文字列で実験します。サイズを入力してください\n"); scanf("%d",&size); //サイズの取得 p_string = (char *)malloc(size + 1);//char型なのでサイズと(半角の)文字数は等しくなります。+1はNULL文字を考慮して。 gets(p_string);//文字列をキーボードより入力します。確保したサイズ以上の文字列が入力された場合はどうなるかは分かりません。 printf("確保したサイズ:%d\n入力された文字列:%s\n", size , p_string ); //結果を表示します。 free(p_string);//動的に確保したメモリは必ず開放しなければなりません。ここで開放すれば同変数名でまた新たに確保できます。 printf( "メモリ領域が解放されました。\n" );
|
しつこいようですが、ポインタはアドレスを格納するものです。このアドレスは何かの変数のアドレスである必要はありません。
ポインタへのアドレスを格納したり、関数へのポインタを設定することも出来ます。なぜかというと、ポインタが単純にアドレスを指すだけの道具だからです。
ポインタそのものがアドレス以外の値を持っているわけではありません。
さて、次のようなプログラムを考えてみます。
int data = 8;
int *p_data;
int **pp_data;
int ***ppp_data;
int ****pppp_data;
一つ目がポインタで、二つ目はポインタのポインタ、3つ目はポインタのポインタのポインタ、4つ目はポインタのポインタのポインタのポインタになります。
「え!!こんなややこしいの使うことがあるの???」と思うでしょう。まぁ、普段はあまりお目にかかりません。どういうときに使うかというと、ポインタ配列、多次元配列を扱う時などに役立つことがあります。詳しくやると深みにはまるのであまり解説しませんが、上の例ではどういう関係になるのかだけお話します。
p_data = &data;
これはいつもと同じです。
pp_data = &p_data;
これで、pp_data には ポインタ p_data のアドレスが設定されます。
同じく
ppp_data = &pp_data;
とすることで、pp_data のアドレスが設定されます。
では、ppp_data を使って data の値を参照するにはどうしたらいいのでしょうか?
printf("%d",***ppp_data);
こうすると、値8が表示されます。間接演算子を3つ付けます。
また、次の例ではどうなるでしょうか?
printf("%d",**ppp_data);
間接演算子が2つです。この場合、ポインタp_data の アドレスが表示されます。私のマシンで先ほど試したところ 6684136 という値になりました。
printf("%d",*ppp_data);
とすると、6684108 と表示されました。これはポインタ pp_data のアドレスです。このように、多次元に関連させた場合はつながりを把握することが大切です。
この指定を誤ると大変な結果になってしまうかもしれません。
また、もう一つ注意があります。
pp_data++;
こうしてしまうと、pp_data は p_data を指さなくなってしまいます。p_dataがポインタの配列
p_data[150] という具合に、配列になっている場合は pp_data++ とすると
p_data[1] を指すようになります。しかし、指し示す先が単純なポインタであるならば、この操作は謝りです。
ここで、ポインタの配列というのが出てきました。これは、ポインタを配列として扱うことが出来るこというこです。
例えば、
int a;
int b[2];
int c;
とあり、
int *p[4];
と、ポインタを配列として宣言したとします。
初期の状態ではポインタpはどこを指しているのか分かりませんので、アドレスを与えてやります。
a = 1;
b[0] = 2;
b[1] = 3;
c = 4;
p[0] = &a;
p[1] = &c;
p[2] = &b[0];
p[3] = &b[1];
このように、初期化してみました。指し示すメモリの内容を見るには
*p[0]
*p[1]
・・・
とすればOKです。しかし、ポインタの演算を利用してもっと簡単に変数a b c にアクセスする方法は無いでしょうか?
p++;
p+= 4;
・・・
これはエラーになってしまいます。なぜなら、p は配列名ですから規則にのっとって&p[0] となり、ポインタが指し示すところではなくポインタ自体が存在する場所を移動してしまうことになるからです。では、ポインタ演算を扱うにはどうしたら言いのか?ここでポインタのポインタが活用できます。
int **pp;
pp = &p;
とします。pp にはポインタ配列 p の最初の要素が設定されます。
すなわち、
printf("%d",**pp);
とすると *pp = p[0] = &a ですから **pp とすることで **pp = *p[0] = a となり、変数a の値1が表示されます。
pp++;
とすると、今度は *pp = p[1] = &c となります。故に **pp = *p[1] = c となり、変数cの値4が表示されます。同様に
pp += 2;
pp--;
2つすすんで1つ戻ると **pp = *p[2] = b[0] となり、配列b の0番目の要素
3が表示されます。
おぉっ、すごいですね!一つのポインタを前後に動かすだけで一度に3つの変数(配列含む)にアクセスできましたね。これが何かに応用できそうな気がしてきませんか??
このように、ポインタ配列を使うことで一つのポインタ名で複数の変数を一度に操作できるという大きな利点を持っています。また、その操作をしやすくするためにはポインタへのポインタが便利であるという事も分かっていただけると思います。
ポインタの移動距離の項目でお話したことを利用し、int型 のデータがどのようにメモリ上に配置されているのかを調べてみましょう。
unsigned int data;
char *pointer;
int型は4バイトで unsigned で修飾してるので正の数のみ扱えるとします。
ポインタはchar型を指定し、移動距離を1バイトとします。
つまり、int型を左から1バイトごとに見ていき、どういうデータが格納されているのかを確かめようというわけです。
unsigned int data | 4バイト | |||
ポインタ pointer | 1バイト目 | 2バイト目 | 3バイト目 | 4バイト目 |
unsigned int は4バイトですから、単純計算して
1バイト=0〜255
4バイト=256の4乗=0〜4294967295
の間の範囲の値が代入できることになります。
では、
int data;
char *pointer;
data = 調べる数値;
pointer = &data;
printf("%d:",*pointer);
pointer++;
printf("%d:",*pointer);
pointer++;
printf("%d:",*pointer);
pointer++;
printf("%d\n",*pointer);
このプログラムで「調べる数値」に値を代入し、4バイトのメモリ上でどういう配置がされるのか検証してみます。
まず、3を代入すると次のような結果になります。
3:0:0:0
unsigned int data | 3 | 0 | 0 | 0 |
図で表わすとこうなります。あれ、右に詰めるようではなさそうですね。人間の感覚では右から詰めていきそうなものですが。
では、256を代入したらどうなるでしょうか?
0:1:0:0
unsigned int data | 0 | 1 | 0 | 0 |
図で表わすとこうなります。0×2560+1×2561=256 です。
次に、0×2560+0×2561+0×2562+1×2563=16777216を試してみましょう。
予想どおり
0:0:0:1
となります。
unsigned int data | 0 | 0 | 0 | 1 |