ポインタ

ポインタってのは、型情報つきのアドレスのことです。


話は変わりますが。

○変数

変数とはそもそも、何でしょうか。

int a;

としますと、32bitの整数型の変数aが宣言されます。この宣言で、コンパイラは
変数aのために32bit(=4byte)のメモリを用意します。
(わたくしのPCはメモリが64Mなので、その環境で話をします。)
以降、aというシンボルには64MbyteのPC搭載メモリのどこかの4byteが
aのための領域として割り当てられ、変数aに関わる処理を行うたびに
そのメモリ領域が参照されるというわけです。


○アドレス

次に。
メモリには「アドレス」とゆうものが有ります。
搭載64Mbyteのメモリには、1byteずつ、64メガ個のアドレスがそれぞれ
割り当てられておりまして、今風に16進数で表せば、
0x00000000 〜 0x03FFFFFF
のアドレスが付いているわけです。
どこか指定のメモリ1byteにアクセスするには、そのアドレスの番地を知っている必要がある、わけです。
より機械寄りに考えれば、メモリアクセスの際のCPUに対しての注文は
「0x00412488のメモリに255入れといてくれる?」
「0x00447711〜0x00447714のメモリの中身出しやがれコラ」
という感じになるわけです。
注)態度がでかくてもていねいでも結果は一緒です。

例えば
変数aは4byteの情報なので、
(0x01234567,0x01234568,0x01234569,0x0123456A)番地
のメモリに、変数aのための領域が確保されているかもしれないわけです。


○&

(ビット演算子と同じ記号ですが、単項演算子としての&は全く別の機能を持ちます)
さて、C言語では変数シンボルから、その変数用に確保されている
メモリのアドレスを知る演算子が用意されておりまして。それが、「&」であります。
&演算子(アドレス演算子と呼ばれる)は、右側の変数のアドレスを返す演算子です。
(正確には、アドレスではなく「ポインタ」を返すのですが、それを説明せなあかんワケで)
#include <stdio.h>
int main(){
	int a=10;
	printf("%08X",&a);
	
	return 0;
}
(出力例)
0065FDF4
%08Xはprintfの書式で、「上位余り桁は0で埋める8桁表示、16進(大文字)」を指定するものです。

この出力から、全メモリのうち、
0x0065FDF4( 〜 0x0065FDF7)に相当する領域が、
変数aのために使われているらしいということなのです。


#include <stdio.h>
int main(){
	int a[5];
	printf("%08X\n",&a[0]);
	printf("%08X\n",&a[1]);
	printf("%08X\n",&a[2]);
	printf("%08X\n",&a[3]);
	printf("%08X\n",&a[4]);
	
	return 0;
}
これの出力は
0065FDE4
0065FDE8
0065FDEC
0065FDF0
0065FDF4
でした。配列は連続的なメモリに確保されるので、取得されるアドレスは
int型のサイズ(4byte)ずつ増えているのが判ると思います。


○ポインタ

ポインタの話に還ります。
int *p;

このように宣言するとint型へのポインタ変数「p」が宣言されます。

まず、限定的な説明をします。
ポインタ変数が何を示すのかというと、「アドレス」です。
	int型の変数が「整数値」を表す			のだとすると、
	int型へのポインタ変数は「アドレス」を表す		のです。
(これも正確には、ポインタ変数の中にはもちろんポインタが入るわけなのですが…)

次のサンプルをご覧なされ。
#include <stdio.h>
int main(){
	int a  = 10;
	int *p = &a;

	printf(" a = %08X\n",a);
	printf("&a = %08X\n",&a);
	printf(" p = %08X\n",p);
	printf("&p = %08X\n",&p);
	
	return 0;
}
これを実行すると、
 a = 0000000A
&a = 0065FDF4
 p = 0065FDF4
&p = 0065FDF0
と、出力されます。変数aの中身は10進数の「10」が16進数に変換されて「A」になっています。
int *p = &a;

「aのアドレス」がポインタ変数pに代入宣言されているため、
&a = 0065FDF4

 p = 0065FDF4

&aとpの値は同じになり、
&p = 0065FDF0

またpも変数(ポインタ変数)ですから、p用のメモリ領域を示すアドレスが表されています。

つまり、普通の変数が整数用の入れものだとすると、
ポインタ変数はアドレス用の入れものなわけです。
(再三つっこみますが、本当はポインタ用の入れものです)

pの値は「pが指しているアドレス」などと呼ばれます。
&pの値は「pが格納されているアドレス」と呼ぶことにします。
「pのアドレス」というと、意味があいまいになるからです。


○*

ポインタ変数には「*」という演算子が使えます。これは、
「pの指しているアドレスにあるメモリの内容を持つ変数があたかもそこにあるかのごとく
メモリにアクセスすることを可能にする演算子」

という名前の演算子です。

うそです。
ただ、この演算子の名前って、一般化されてないように思います。

(注意)この演算子は乗算演算子と別もんであることは言うまでもないかとは思いますが、
ポインタ変数の宣言に使った「*」とも特に関係があるわけではありません。
宣言に使ったやつは「ポインタ変数の宣言に使うアスタリスク」という名前の宣言子です。

うそです。 名前はネ。

使用例を見てくだされ。
#include <stdio.h>
int main(){
	int a  = 10;
	int *p = &a;

	printf(" a = %08X\n",a);
	printf("&a = %08X\n",&a);
	printf(" p = %08X\n",p);
	printf("*p = %08X\n",*p);
	
	return 0;
}
 a = 0000000A
&a = 0065FDF4
 p = 0065FDF4
*p = 0000000A
*pの値が…。な、なんと、aの値と同じになっているではありませんかあァ。
こ、これではまるで…!
あたかも
printf("*p = %08X\n",*p);

に「*p」ではなく「a」と書いてあるかのようですッ!


もうひとつな。
#include <stdio.h>
int main(){
	int a  = 10;
	int *p = &a;

	*p = 20;
	printf(" a = %08X\n",a);
	printf("&a = %08X\n",&a);
	printf(" p = %08X\n",p);
	printf("*p = %08X\n",*p);
	
	return 0;
}
 a = 00000014
&a = 0065FDF4
 p = 0065FDF4
*p = 00000014
aの値が16進で14になっています。10進で表すと20です。
これは、
*p = 20;

で、代入処理が行われているからです。

…。えッ!? *pでなぜaの値が変わって…、これではまるで…!
あたかも
*p = 20;

に「*p」ではなく「a」と書いてあるかのようですッッ!!


まぁ、そういうことなんですが、どうでしょう。わかりますか?
aへのポインタがpに入っていて、「*」を付けることによりaと同等の存在を召還できるのです。


○中間まとめ

ぼちぼちポインタの正体をつかみかけて来てくれると私としても嬉しいのですが、
ポインタは、
全メモリのうち、どこか特定の位置を指すカーソルのようなもの。
です。ピンときますか?


○一歩進んで二歩さがる

ポインタは、一歩進んだり二歩さがったりすることができます。

ポインタ変数に対して加算または減算を行うと、ポインタが
指しているアドレスが進んだりさがったりします。


#include <stdio.h>
int main(){
	int a  = 10;
	int *p = &a;

	printf(" p = %08X\n",p);
	p = p+1;
	printf(" p = %08X\n",p);
	p = p-2;
	printf(" p = %08X\n",p);

	return 0;
}
 p = 0065FDF4
 p = 0065FDF8
 p = 0065FDF0
このとおりです。
ポインタの位置が、4byte(intのサイズ)ずつ前後しています。

例えばこのように加減算を行ったポインタに対して、
*p = 20;

などとしますと、aとはまったく関係ないメモリ領域に代入を行うことになり、
どこかの情報が破壊されます。仮にそれが重要な情報であれば、大ピンチです。
不正な処理と言われて落とされることもあります。そういうプログラムを
友人のPCに入れて実行してみれば、笑いのネタになるかもしれません。
ネタになるかもしれませんが、やらない方がいいのかもしれません。


配列ならば、
連続的なメモリ領域が取られるわけですから、
#include <stdio.h>
int main(){
	int a[5]  = {16,32,48,64,80};
	int *p = &a[0];

	printf("*p = %08X\n",*p);
	p += 1;
	printf("*p = %08X\n",*p);
	p += 2;
	printf("*p = %08X\n",*p);

	return 0;
}
*p = 00000010
*p = 00000020
*p = 00000040
一歩進んで要素2、さらに二歩進んで要素4の中身が表示されるということです。


もうひとつ、重要なことを。
今までの例では、pの「一歩」は4byteでした。
しかし、すべてのポインタが4byteずつ動くのではありません。
int *p;

と宣言されたポインタが「int型へのポインタ」であるため、
intの型サイズが32bit=4byteであるため、4byteずつ動いていたわけです。
char型へのポインタに対して1を加算するとアドレスは1byte
short型へのポインタに対して1を加算するとアドレスは2byte
移動するので、予めご了承下さい。

れい
#include <stdio.h>
int main(){
	int a     = 10;
	int *pi   = &a;
	char *pc  = (char*)&a;
	short *ps = (short*)&a;

	pi++;		printf("pi = %08X\n",pi);
	pi++;		printf("pi = %08X\n",pi);
	pi++;		printf("pi = %08X\n",pi);

	pc++;		printf("pc = %08X\n",pc);
	pc++;		printf("pc = %08X\n",pc);
	pc++;		printf("pc = %08X\n",pc);

	ps++;		printf("ps = %08X\n",ps);
	ps++;		printf("ps = %08X\n",ps);
	ps++;		printf("ps = %08X\n",ps);

	return 0;
}
pi = 0065FDF8
pi = 0065FDFC
pi = 0065FE00
pc = 0065FDF5
pc = 0065FDF6
pc = 0065FDF7
ps = 0065FDF6
ps = 0065FDF8
ps = 0065FDFA
(char*)ってなんですかって?人に質問する前にgoogleで検索をすれ。
殴らないで、ヒィ、おまじないですッ


○[ ]のホント(中級ネタかも)

int a[5]  = {16,32,48,64,80};
int *p = &a[0];
この時に。
*(p+1)

この式は、どんな値になるでしょうか。問題です。
どうですか?

a[0]のアドレスを指しているint型へのポインタpから一歩進んだところに
あたかもそこに変数があるかのごとくなので、要はa[1]と同等になるわけで、
値としては32(0x20)になるわけです。解ったかな、ふふん。

さて、心を落ち着けてじっくりと読んでください。
*(p+1)は、p[1]と書いてもいいのです。
繰り返します。
*(p+1)は、p[1]と書いてもいいのです。
ちなみに、
*(p+2)は、p[2]と書いてもいいのです。
さらに言えば、
*(p+0)は、p[0]と書いてもいいのです。

ポインタに関して、*(p+整数値)をp[整数値]と書く事が許されていて、
つまり、 [ ] は、そういう演算子なのです。名前不明
#include <stdio.h>
int main(){
	int a[5]  = {16,32,48,64,80};
	int *p = &a[0];

	printf("*(p+1) = %08X\n",*(p+1));
	printf("p[1]   = %08X\n",p[1]);

	return 0;
}
*(p+1) = 00000020
p[1]   = 00000020
ここまで読んで。
配列ってポインタと関係があるのでは、思った方がおるやも知れません。
配列もある種のポインタなのでは?、と思った方もおるやも知れません。

配列のシンボル「a」を単体で書いたとき、それは式中において
配列の先頭要素へのポインタの意味になります。
―sizeof,&の非演算子となったとき、例外的な動作をしますが、説明を省きます―
と考えると、a[3]のような記述が出来るのは「aがポインタだから」ということになります。

ポインタ変数のシンボル「p」を単体で書いたとき、それは式中において、
pの指すものへのポインタの意味になります。
当たり前ですか。

注意すべきなのは、本当は、ポインタ変数とポインタは別のものだということです。
ただ、便宜上ポインタ変数のことをポインタということもあり、「ポインタ」がどちらの
意味になるかは文脈によって変わります。

ポインタ変数はあくまでも変数で、ポインタのいれものです。
ポインタそのものは、いわば型情報を持つアドレス、です。
配列変数もやはり変数ですが、要素に対するアクセスはポインタ的になります。とはいえ、
配列変数はポインタ変数ではないので、ポインタのいれものにはなりません。
だから、配列変数にポインタを代入しようとかしても無理です。
ポインタ変数の配列、ってのはあります。ケドね

なんだか鬼のような日本語ですね。

(何を言っているのかというと、
 配列変数もポインタ変数もa[1],p[2]のように使えるからと言って、
 a = p;のようなことができるように思ってはいけない、同等に見てはいけない、
 ということです。)


○関数の引数としてのポインタ

この章長いネェ。

#include <stdio.h>
void poinntawouketorimasu(int *p2){
	*p2 = 20;
}

int main(){
	int a  = 10;
	int *p = &a;

	printf("a = %08X\n",a);
	poinntawouketorimasu(&a);
	printf("a = %08X\n",a);

	return 0;
}
a = 0000000A
a = 00000014
以上。


なんてな。

C言語は「値渡しの言語」です。値渡しというのは、参照渡しでない事です。
では、参照渡しとは何かというと、値渡しでない…げひッ
参照渡しと言うんは、ゴホゴホ
sub(int a){
	a=20;
}
main(){
	int a=10;
	sub(a);
	…
という記述で、main関数のaがいつのまにか20になっているような言語仕様のことです。
「値渡しの言語」なるC言語は、こういう事は出来ません。なぜならッ、
あ、ところで、C++んなると参照渡しができてしまうので、C限定ですヨ。

値渡しというのは。あ、これ、「あたいわたし」と読んでくださいね。
関数subに渡されるのは、変数aの情報ではなく、aの値の情報のみ
だということです。つまり、関数subの側からは、aの中身の0x0000000A(十進で10)
という情報しか教えてもらえないということです。
だから、sub側でダミー変数aを生成して、そこにあたかもmain関数のaをそのまま
受け取ったようなカオをしつつ10を代入しておき
、プログラマーにsub関数で使える変数aを
提供してくれるわけです。
変数aは自動変数で、sub関数が終了次第殺られますから、sub関数のaになにか代入したところで
別に何が起こるわけでもない、というわけで、す。

ところで、main()の変数aの中身に(呼ばれた関数側から)手を加えたい場合は?
そうだ!、アドレス渡せばいいんじゃんっ☆
あぁ、ひとり遊び。

アドレスを渡してしまえば、変数aがメモリのどこにあるかが解ってしまいます。
から、変数aの中身を書きかえることも可能になるわけで、それがこの節の冒頭の
サンプルになるわけでした。
アドレスも本来的には32bitの「値」ですから、値渡しの例外ではないわけです。


○文字列とポインタ(けっこう中級ネタ)

#include <stdio.h>
int main(){
	char *sp = "moemoe";
	char sa[] = "herohero";

	printf(sp);
	printf("\n");
	printf(sa);

	return 0;
}
moemoe
herohero
Cでは、文字列のいれものとしての変数型はない、と前の章で書きました。

ソースコード中に埋め込まれている"mojiretu"みたいなものを、文字列リテラルといいます。
文字列リテラルは、先頭文字へのcharポインタを値として持ちます。
文字列リテラルを式として評価すると、先頭文字へのcharポインタを返すということです。

ただし、配列の初期化時は例外で、配列への文字列の一括代入の意味になります。
char sa[] = "herohero";

詳しくは、もう少し↓で説明します。

これらの意味するところは。
宣言以降、smに対しては、
sp = "punipuni";

このような記述が可能ですが、
配列、saに対して
sa = "raburabu";

なんてことはできませんよって事です。
saは配列なので、目的と思われる処理を行うには、
	sa[0] = 'r';
	sa[1] = 'a';
	sa[2] = 'b';
	sa[3] = 'u';
	sa[4] = 'r';
	sa[5] = 'a';
	sa[6] = 'b';
	sa[7] = 'u';
	sa[8] = '\0';
こうする必要があります。最後の'\0'は↓の方で説明します。

では、
sp = "punipuni";

コレは何をしているのでしょうか?
文字列リテラルは、文字列の定数です。"punipuni"をソースコード中に
埋め込みますと、コンパイル後の実行ファイルにも"punipuni"がとある場所に収納されます。
そのプログラムが実行されると、アクセス可能なメモリに"punipuni"が移されます。
で、ソースコード中で
sp = "punipuni";

に当たる処理が行われるとき、"punipuni"という式は、メモリ上の"punipuni"の
先頭文字(p)へのcharポインタとして評価され、spにそれが代入されます。
spはそれ以降、「メモリ上の"punipuni"の先頭文字へのアドレス」を指すことになるわけです。

ところで、char配列の初期化時には。
sa[] = "herohero";

このような書き方が通ります。
配列の初期化時に要素数が指定されなかった場合は、必要な要素数、この場合は9(文字数+1(\0))
が計算され、配列に自動で割り当てられます。
この場合の"herohero"は通常の文字列リテラルとは異なり、=も代入演算子とは言えません。
むしろ、こういう一文で「配列の宣言構文」とでも言うべきものになります。

'\0'、とは。
HSPにもありますが、シングルクォートで囲まれた文字は、その文字の、
何だ、…何て言うんだっけ、その文字のコードを示す数値になります。
一方、文字列に対して、\(数値)などと表記すると、文字列中に本来の数値を
埋め込むことが出来ます。
…なんやうまく説明がいかへんのですが、ぶっちゃけた話、
'\0' イコール 0
です。だから、sa[8] = 0;と書いても同じです。
何でまたまどろっこしい表記をしているのかというと、
文字列の終端ですよということを明確に主張しようとしているという、
単なる個人的な配慮であったりします。

話が前後しますが、文字列の終端は数値の0で表します。
ちなみに、正確には終端の0まで含めて「文字列」です。
char *sp = "moemoe";

char sa[] = "herohero";

これには、コンパイラによってどちらも終端に0が付け加えられます。
さて、
#include <stdio.h>
int main(){
	char s[] = "ugu";
	s[3] = 'u';
	printf(s);

	return 0;
}
このように、終端文字として付いているはずの0を潰すようなコーディングをすると、
どんな文字列が出力されるでしょうか。やってみましょう。わくわく
uguu8・e
うーん、期待ハズレ。話のネタになりませんなぁ。
printfは、0が出るまでひたすら文字列を調べ上げます。今回は残念ながら
すぐそばに0があったため4byteほどのオーバーランで終わってしまいましたが、
状況によっては大量の奇怪な文字列が出力されるかもしれません。
もしかしたら、蒼い画面に遭遇できるかも?
ぜひ、友人のPC はうっ

先ほど、配列に対する文字列の代入に、
	sa[0] = 'r';
	sa[1] = 'a';
	…(つづく)
という手法を使いましたが、これは、(例えば)Cの標準ライブラリのsprintf
を用いても、同様のことが出来ます。
sprintf(sa,"raburabu");

これは、標準出力の代わりに、文字列のバッファに対して出力をするprintfです。
他にも、文字列処理用の関数がいくつかありますが、これらの関数は
配列がどれだけ確保されているかを考えずに、0が出るまで徹底的に全力疾走します。

文字列の処理を行うときは、終端記号やバッファのサイズをよくよく考慮しないと、
メモリの内容を壊して、よくわからないバグを生むことになります。


○NULLポインタ

普通のCの処理系には、というか激しく異常でない限りは、
「NULL」という定数が定義されています。この定数は、そのポインタが
有効なメモリ領域を指していないことを示すシンボルです。
具体的には、
#include <stdio.h>
int main(){
	char *p = NULL;
	printf(p);

	return 0;
}
このようにすると、エラーが出ます。たぶん。いや、笑いとってるんじゃなくてな。
「誤りのサンプルコードなんて出さないで下さい。混乱するじゃないですか。」
と今つぶやいたあなた。誰もいないか。けっ

それは意味が違うのです。

NULLを入れていないコード、次のサンプルを実行した場合、
#include <stdio.h>
int main(){
	char *p;
	printf(p);

	return 0;
}
これは一歩間違うと、エラーも出ずに動いてしまうこともあります。
(一歩間違った例)
ζテj
関数内で宣言された自動変数の値は不定ですから、pは宣言した段階では
謎のメモリを指しているのです。printfはそれをまじめに受け取り、
どうにか処理しようとしてしまいます。

一方、NULLのサンプルでは、pは具体的に「無効の領域」を指しているのが明確で
あるため、明確なエラーが出るかもしくは、目的の処理を中止するなどの対応を
呼ばれた関数側でとることが出来るのです。


○構造体へのポインタ(そこそこ中級ネタ)

構造体へのポインタがあれば、各メンバに->という演算子でアクセスできます。
#include <stdio.h>
typedef struct{
	int x;
	int y;
}Point;
void dispoint(Point* pp){
	printf("(%d,%d)",pp->x,pp->y);
}
int main(){
	Point p;
	p.x = 10;
	p.y = 20;

	dispoint(&p);

	return 0;
}
(10,20)
詳細は割愛しマッ ふぎッ


○関数へのポインタ(なかなか中級ネタ)

関数へのポインタを使うと、関数の柔軟な呼び出しが出来るようになります。

googleで「関数へのポインタ」で検索してください。 ふにぃ
自分も使い慣れているわけじゃないので、きちんと説明する自信ないです。
#include <stdio.h>
void combo3(void(*pfunc)()){
	pfunc();
	pfunc();
	pfunc();
}
void func1(){
	printf("ha");
}
void func2(){
	printf("fu");
}
int main(){
	combo3(func1);
	combo3(func2);

	return 0;
}
hahahafufufu
関数名が引数に入ってて、新鮮っしょ?



○やっと終わり

この章の内容、全体的にたぶん初心者には荷が重いというか、
「本当に初心者だったのですが、これを読んで全部意味がわかりました。」
などという輩がいたら、
この章を書いた人が物凄く偉いか、この章の著者が天才的かのどっちかです。
か、
その自称初心者ちょっと頭いいかも。
のどっちかです。
ふゥ、あやうく、調子こいてんじゃねぇこのタコ、と言われるところだったヨ。
いや、何が言いたいのかっつうと、結構難しいから、フインキわかればいいんじゃないかしら?