C/C++ ポインタ入門

ポインタとは

ポインタとはアドレスの入れ物です
ただそれだけです

おしまい

ポインタとアドレス

わかっていれば本当にそれでおしまいなのですが、ではそのアドレスというのは何かというと、アドレスというのはメモリ上の場所を表す値で、ただの整数です
(実際には違いますが)単純に言うと 64KB のメモリのある環境ではアドレスは 0x0000 〜 0xffff になりますし、2GB のメモリのある環境ではアドレスは 0x00000000 〜 0x7fffffff になりますし、4GB のメモリのある環境ではアドレスは 0x00000000 〜 0xffffffff になります
また 8GB のメモリのある環境ではアドレスは 0x000000000 〜 0x7ffffffff になりますし、1TB のメモリのある環境ではアドレスは 0x00000000000 〜 0x3ffffffffff になります
16ビットの符号なし整数で表現できるのは 0 〜 64KB まで、32ビットの符号なし整数で表現できるのは 0 〜 4GB まで、64ビットの符号なし整数で表現できるのは 0 〜 16エクサバイトまでになりますね
このアドレスのビット幅のことを指して 16ビット環境、32ビット環境、64ビット環境などと呼んでいます
ポインタ型というのはこのアドレスを入れるためのものですから、16ビット環境、32ビット環境、64ビット環境などというのは正にポインタの大きさを表していると言ってもいいでしょう
(本当は違うけど)

ポインタとアドレス:その2

アドレスというのはメモリ上の場所を表す値であると言いましたが、これは具体的には、メモリの先頭から何バイト離れているかというバイト数のことです
ですから、アドレスとしての値が 1 ならメモリの先頭から 1バイト目の場所を指していますし、アドレスとしての値が 100 ならメモリの先頭から 100バイト目の場所を指しています
このようにアドレスというのはただの整数なので、ポインタも実はただの整数です

int	a = 0;		// a に入っている値は 0
int*	b = 0;		// b に入っている値は 0
char*	c = 0;		// c に入っている値は 0
int**	d = 0;		// d に入っている値は 0
int	(*e)[10] = 0;	// e に入っている値は 0
int	(*f)() = 0;	// f に入っている値は 0
void*	g = 0;		// g に入っている値は 0

普通の整数型に入っている数値もポインタに入っている数値も数値として見る限りはどちらも全く同じものです
普通の整数型に入っている数値とポインタに入っている数値とで何が違うのかというと、普通の整数型に入っている数値はそれが何を意味しているかはプログラマしか知らないけれども、ポインタに入っている数値がアドレスであるということはコンパイラも知っているというところです
コンパイラはポインタにアドレスが入っていることを知っているので、それを常にアドレスとして適切であるように扱います
つまりポインタとはコンパイラが特別扱いをしてくれる整数型のことです

ポインタとアドレス:その3

コンパイラが特別扱いをしてくれるとはいえ、ポインタはただの整数型とわかれば怖くはないですね

ここで具体的に考えるために今システムのメモリ全体が1つのファイルであるとしてみましょう
するとメモリのアドレスはシークオフセットということになりますね
ポインタはシークオフセットを入れている変数です
ただのシークオフセットなら益々何も怖いことはないですね

変数宣言をするということは、そのメモリというファイル上のどこかの場所のシークオフセットに名前を付けて覚えておくということです
そして変数に値を入れるということはその変数用のシークオフセットの場所に値を書き込むということです
変数から値を取り出すということはその変数用のシークオフセットの場所から値を読み出すということです

このような想定で次のコードを見てみましょう

int	a = 10;
int*	p = &a;
int	b = *p;
*p = 20;

  1. シークオフセットを得て a の場所として覚える
  2. a の場所に 10 を書く
  3. シークオフセットを得て p の場所として覚える
  4. p の場所に a のシークオフセットを書く
  5. シークオフセットを得て b の場所として覚える
  6. p の場所から読みだした値を新たなシークオフセットとしてその場所にある値を読み出して b の場所に書く
  7. p の場所から読みだした値を新たなシークオフセットとしてその場所に 20 を書く


ここで次のような関数があると考えてみると

// address_t : アドレスの型
// value_t : 任意の値の型

// 現在位置を得る
address_t	memory_tell();

// 現在位置を設定する
void	memory_seek( address_t	p_to );

// 現在位置から指定されたバイト数のデータを読み込み、その分現在位置を進める
value_t	memory_read( size_t	bytes_to_read );

// 現在位置に指定されたバイト数のデータを書き込み、その分現在位置を進める
void	memory_write( value_t	value_to_write, size_t	bytes_to_write );

// 指定位置から指定されたバイト数のデータを読み込む。現在位置には触らない
value_t	memory_read( address_t	p_from, size_t	bytes_to_read );

// 指定位置に指定されたバイト数のデータを書き込む。現在位置には触らない
void	memory_write( address_t	p_to, value_t	value_to_write, size_t	bytes_to_write );


上記のコードは次のように書けます

// int	a = 10;
address_t	addressof_a = memory_tell();						// a を宣言
memory_write( 10, sizeof( int ));						// a の場所に 10 を書く
// int*	p = &a;
address_t	addressof_p = memory_tell();						// p を宣言
memory_write( addressof_a, sizeof( address_t ));				// p の場所に &a を書く
// int	b = *p;
address_t	addressof_b = memory_tell();						// b を宣言
address_t	valueof_p = ( address_t )memory_read( addressof_p, sizeof( address_t ));	// p の場所から読み出した値をシークオフセットとして
int	valueof_b = ( int )memory_read( valueof_p, sizeof( int ));		// そこから値を読み出す
memory_write( valueof_b, sizeof( int ));					// b の場所にその値を書く
// *p = 20;
address_t	valueof_p = ( address_t )memory_read( addressof_p, sizeof( address_t ));	// p の場所から読み出した値をシークオフセットとして
memory_write( valueof_p, 20, sizeof( int ));					// その場所に 20 を書く

また別のコードでは次のようになります

int	c[2] = { 0, 1 };
int*	p = c;
*p = 1;
++p;
*p = 2;
int	d = p - c;

  1. シークオフセットを得て c の場所として覚える
  2. c の場所に 0 と 1 を書く
  3. シークオフセットを得て p の場所として覚える
  4. p の場所に c のシークオフセットを書く
  5. p の場所から読みだした値を新たなシークオフセットとしてその場所に 1 を書く
  6. p の場所から読みだした値に p の指す先にあるものの大きさを1つ足して p の場所に書き戻す
  7. p の場所から読みだした値を新たなシークオフセットとしてその場所に 2 を書く

// int	c[2] = { 0, 1 };
address_t	addressof_c = memory_tell();						// c を宣言
memory_write( 0, sizeof( int ));						// c の場所に 0 を書く
memory_write( 1, sizeof( int ));						// c の場所に 1 を書く
// int*	p = c;
address_t	addressof_p = memory_tell();						// p を宣言
memory_write( addressof_c, sizeof( address_t ));				// p の場所に c のアドレスを書く
// *p = 1;
address_t	valueof_p = ( address_t )memory_read( addressof_p, sizeof( address_t ));	// p の場所から読み出した値をシークオフセットとして
memory_write( 1, sizeof( int ));						// その場所に 1 を書く
// ++p;
address_t	valueof_p = ( address_t )memory_read( addressof_p, sizeof( address_t ));	// p の場所から読み出した値を
valueof_p += sizeof( int );							// 一要素分先に進めて
memory_write( addressof_p, valueof_p, sizeof( address_t ));			// p の場所に書き戻す
// *p = 2;
address_t	valueof_p = ( address_t )memory_read( addressof_p, sizeof( address_t ));	// p の場所から読み出した値を
memory_write( valueof_p, 2, sizeof( int ));					// p の場所に 2 を書く
// int	d = p - c;
address_t	addressof_d = memory_tell();						// d を宣言
address_t	valueof_p = ( address_t )memory_read( addressof_p, sizeof( address_t ));	// p の場所から読み出した値から
int	valueof_d = ( int )( valueof_p - addressof_c ) / sizeof( int );		// c のアドレスを引いて要素のサイズで割って p と c の差分を要素数で求める
memory_write( valueof_d, sizeof( int ));					// d の場所に求めた要素数を書く

ここで ++p のところに注目してください
addressof_p のところから読みだした値 valueof_p を単純に +1 するのではなく、+sizeof( int ) としていますね
これは重要なところで、ポインタの加減算は常にポインタの型のサイズの単位で増減します

これはファイルで考えれば当たり前のことでしょう
ファイルに sizeof( int ) のデータを書き込んだらシークオフセットをその分だけ動かしますね
もしシークオフセットを動かすのが今書き込んだデータのサイズよりも小さかったら、次にそのシークオフセットの指すところに同じ大きさのデータを書き込んだら、直前に書き込んだデータが壊れてしまいますね
ですからシークオフセットは常に読み書きするデータのサイズの単位で増減します
ここで言うシークオフセットとはアドレスのことです
また読み書きするデータのサイズとはポインタの型のサイズのことです
つまり、ポインタの中身であるアドレスは常にポインタの型のサイズの単位で増減するということです

これは言い換えると、ポインタに指示する加減算のオペランドはバイト数ではなく要素数であるということです
ポインタに +1 をするとポインタの値であるアドレスは一要素分増えます
ポインタに -1 をするとポインタの値であるアドレスは一要素分減ります
アドレスを一要素分増やすのにポインタに +1 をします
アドレスを一要素分減らすにはポインタに -1 をします

またここで d = p - c のところに注目してください
d には p の値であるアドレスと c のアドレスとの差をそのまま入れているのではなく、その差を要素数に直してから入れていますね
これは二つのポインタの差分はバイト数ではなく要素数であるということを表しています
加減算に指定するのも要素数、差分が返すのも要素数です

ポインタは常にその型のサイズ単位で動くということを覚えましょう

ポインタの型

普通の整数に int や long などの型があるように、ポインタにも型があります
普通の整数の型とポインタの型との違うところは、普通の整数の型がその整数自身の型を決めるものであるのに対して、ポインタの型は指している場所にあるものの型を決めるものであるということです

int	a;		// a は int
int*	b;		// b の指しているところにあるものは int
char*	c;		// c の指しているところにあるものは char
int**	d;		// d の指しているところにあるものはポインタ。そのポインタが指しているところにあるものは int
int	(*e)[10];		// e の指しているところにあるものは要素数10個の int の配列
int	(*f)();		// f の指しているところにあるものは関数
void*	g;		// g の指しているところにあるものは何だかわからない

ポインタ自身には常にアドレスが入っているので、そのアドレスの指す先にあるものの型が何であろうとも、ポインタ自身には関係ありません
ファイルに入っているものが何であろうとシークオフセットを入れておく変数の型が変わったりはしませんからね

今、上記の a, b, c について考えてみると、a は a 自身が int であると宣言しています。a の大きさは sizeof( int ) になります
一方 b はポインタなので b 自身は int* であると宣言しています。b の大きさは sizeof( int* ) になります
また b が int* なので b に入っているアドレスが指す先にあるものは int であり、その大きさは sizeof( int ) です
c はポインタなので c 自身は char* であると宣言しています。c の大きさは sizeof( char* ) になります
また c が char* なので c に入っているアドレスが指す先にあるものは char であり、その大きさは sizeof( char ) です

例えば具体的に char が 1バイト、int が 4 バイト、ポインタが 8 バイトの環境で考えてみると、
a は int なので a 自身の大きさは 4 バイトです
一方 b はポインタなので b 自身の大きさは 8 バイトで、b に入っているアドレスが指す先にあるものは 4 バイトです
c はポインタなので c 自身の大きさは 8 バイトで、c に入っているアドレスが指す先にあるものは 1 バイトです

ここで a, b, c をそれぞれインクリメントしてみましょう。するとどうなるでしょうか

++a;
++b;
++c;

a はただの整数なので値が 1 増えます。元の値が 0 なら新しい値は 1 になります
一方 b はポインタなのでその値であるアドレスは一要素分増えます。b は int* なので sizeof( int ) 増えます。今 sizeof( int ) が 4 であるとすると、元の値が 0 なら新しい値は 4 になります
c もポインタなのでその値であるアドレスは一要素分増えます。c は char* なので sizeof( char ) 増えます。今 sizeof( char ) が 1 であるとすると、元の値が 0 なら新しい値は 1 になります

ここで、d, e もインクリメントしてみましょう

++d;
++e;

d はポインタなのでその値であるアドレスは一要素分増えます。d は int** なので sizeof( int* ) 増えます。今 sizeof( int* ) が 8 であるとすると、元の値が 0 なら新しい値は 8 になります
e もポインタなのでその値であるアドレスは一要素分増えます。e は int (*)[10] なので sizeof( int ) × 10個分増えます。今 sizeof( int ) が 4 であるとすると、元の値が 0 なら新しい値は 40 になります

ここで、f, g もインクリメントしてみましょう

++f;
++g;

しかしこれはどちらもエラーになります
f はポインタなのでその値であるアドレスを一要素分増やしたいところですが、f は関数を指すポインタなので、その要素は関数です。関数のサイズを知る方法は無いので、f はインクリメントできません
g もポインタなのでその値であるアドレスを一要素分増やしたいところですが、g は void* です。何を指しているのか決まっていないので要素のサイズを決定できません。ですから g もインクリメントできません

ポインタと配列

次のような配列があったとき

int	c[4] = { 0, 1, 2, 3 };

その各要素にアクセスするには次のようにしますね

int	c0 = c[0];
int	c1 = c[1];
int	c2 = c[2];
int	c3 = c[3];

これが何をしているかを見てみましょう

// int	c[4] = { 0, 1, 2, 3 };
address_t	addressof_c = memory_tell();						// c を宣言
memory_write( 0, sizeof( int ));						// c の場所に 0 を書く
memory_write( 1, sizeof( int ));						// c の場所に 1 を書く
memory_write( 2, sizeof( int ));						// c の場所に 2 を書く
memory_write( 3, sizeof( int ));						// c の場所に 3 を書く
// int	c0 = c[0];
address_t	addressof_c0 = memory_tell();					// c0 を宣言
value_t	valueof_c0 = memory_read( addressof_c + 0 * sizeof( int ), sizeof( int ));	// c[0] の値を得て
memory_write( addressof_c0, valueof_c0, sizeof( int ));				// c0 の場所にその値を書く
// int	c1 = c[1];
address_t	addressof_c1 = memory_tell();					// c1 を宣言
value_t	valueof_c1 = memory_read( addressof_c + 1 * sizeof( int ), sizeof( int ));	// c[1] の値を得て
memory_write( addressof_c1, valueof_c1, sizeof( int ));				// c1 の場所にその値を書く
// int	c2 = c[2];
address_t	addressof_c2 = memory_tell();					// c2 を宣言
value_t	valueof_c2 = memory_read( addressof_c + 2 * sizeof( int ), sizeof( int ));	// c[2] の値を得て
memory_write( addressof_c2, valueof_c2, sizeof( int ));				// c2 の場所にその値を書く
// int	c3 = c[3];
address_t	addressof_c3 = memory_tell();					// c3 を宣言
value_t	valueof_c3 = memory_read( addressof_c + 3 * sizeof( int ), sizeof( int ));	// c[3] の値を得て
memory_write( addressof_c3, valueof_c3, sizeof( int ));				// c3 の場所にその値を書く

配列の先頭アドレスから添え字に指定された要素の分だけ離れたところにアクセスしているわけです
配列と言っても何か特別なものがあるわけではなくただ要素が順番に並んでいるだけなので、そこにアクセスするときには先頭のアドレスと先頭からどのくらい離れているかというオフセットだけがわかればいいわけです

アドレスとオフセットだけあればいいなら別にポインタでも実現できそうですからやってみましょう

int*	p = c;
int	p0 = *( p + 0 );
int	p1 = *( p + 1 );
int	p2 = *( p + 2 );
int	p3 = *( p + 3 );

// int*	p = c;
address_t	addressof_p = memory_tell();						// p を宣言
memory_write( addressof_c, sizeof( address_t ));				// p の場所に c のアドレスを書く
// int	p0 = p[0];
address_t	addressof_p0 = memory_tell();					// p0 を宣言
address_t	valueof_p = ( address_t )memory_read( addressof_p, sizeof( address_t ));	// p の場所から読み出した値をシークオフセットとして
value_t	valueof_p0 = memory_read( valueof_p + 0 * sizeof( int ), sizeof( int ));	// そこから値を得て
memory_write( addressof_p0, valueof_p0, sizeof( int ));				// p0 の場所にその値を書く
// int	p1 = p[1];
address_t	addressof_p1 = memory_tell();					// p1 を宣言
address_t	valueof_p = ( address_t )memory_read( addressof_p, sizeof( address_t ));	// p の場所から読み出した値をシークオフセットとして
value_t	valueof_p1 = memory_read( valueof_p + 1 * sizeof( int ), sizeof( int ));	// そこから値を得て
memory_write( addressof_p1, valueof_p1, sizeof( int ));				// p1 の場所にその値を書く
// int	p2 = p[2];
address_t	addressof_p2 = memory_tell();					// p2 を宣言
address_t	valueof_p = ( address_t )memory_read( addressof_p, sizeof( address_t ));	// p の場所から読み出した値をシークオフセットとして
value_t	valueof_p2 = memory_read( valueof_p + 2 * sizeof( int ), sizeof( int ));	// そこから値を得て
memory_write( addressof_p2, valueof_p2, sizeof( int ));				// p2 の場所にその値を書く
// int	p3 = p[3];
address_t	addressof_p3 = memory_tell();					// p3 を宣言
address_t	valueof_p = ( address_t )memory_read( addressof_p, sizeof( address_t ));	// p の場所から読み出した値をシークオフセットとして
value_t	valueof_p3 = memory_read( valueof_p + 3 * sizeof( int ), sizeof( int ));	// そこから値を得て
memory_write( addressof_p3, valueof_p3, sizeof( int ));				// p3 の場所にその値を書く

配列でもポインタでも、基準となるどこかのアドレスから指定された要素分だけ離れたところにアクセスしているというところは、まったく同じですね
実際 p[n] という表記は *( p + n ) という表記と全く同じ意味です
どちらもポインタと要素数を足し算した結果が指している場所にある値を表しています

ところで、足し算の左と右が入れ替わっても結果は同じですね
ですから *( p + n ) と *( n + p ) が同じように p[n] と n[p] も同じです
次の表記はどれも同じ意味です

p[3]
3[p]
*( p + 3 )
*( 3 + p )
( p + 3 )[0]
0[p + 3]

これは次のような表記ができるところからもわかります

1+1 == (int)&((char*)1)[1]
1+1 == (int)&1[(char*)1]
1+1 == (int)&*( 1+1 )

このようなことからも lhs[rhs] という表記が単純に [] の内と外を *( lhs + rhs ) の形に置き換えているだけだということがわかりますね

ポインタと配列:その2

次のような配列があったとき

int	cc[3][4] = {
	{ 0, 1, 2, 3 },
	{ 4, 5, 6, 7 },
	{ 8, 9, 10, 11 },
};

その各要素にアクセスするには次のようにしますね

int	cc01 = cc[0][1];
int	cc12 = cc[1][2];
int	cc23 = cc[2][3];

これが何をしているかを見てみましょう

//int	cc[3][4] = {
//	{ 0, 1, 2, 3 },
//	{ 4, 5, 6, 7 },
//	{ 8, 9, 10, 11 },
//};
address_t	addressof_cc = memory_tell();				// cc を宣言
memory_write( 0, sizeof( int ));					// cc の場所に 0 を書く
memory_write( 1, sizeof( int ));					// cc の場所に 1 を書く
memory_write( 2, sizeof( int ));					// cc の場所に 2 を書く
memory_write( 3, sizeof( int ));					// cc の場所に 3 を書く
memory_write( 4, sizeof( int ));					// cc の場所に 4 を書く
memory_write( 5, sizeof( int ));					// cc の場所に 5 を書く
memory_write( 6, sizeof( int ));					// cc の場所に 6 を書く
memory_write( 7, sizeof( int ));					// cc の場所に 7 を書く
memory_write( 8, sizeof( int ));					// cc の場所に 8 を書く
memory_write( 9, sizeof( int ));					// cc の場所に 9 を書く
memory_write( 10, sizeof( int ));					// cc の場所に 10 を書く
memory_write( 11, sizeof( int ));					// cc の場所に 11 を書く
// int	cc01 = cc[0][1];
address_t	addressof_cc01 = memory_tell();				// cc01 を宣言
value_t	valueof_cc01 = memory_read( addressof_cc + ( 0*4 + 1 ) * sizeof( int ), sizeof( int ));
										// cc[0][1] の値を得て
memory_write( addressof_cc01, valueof_cc01, sizeof( int ));		// cc01 の場所にその値を書く
// int	cc12 = cc[1][2];
address_t	addressof_cc12 = memory_tell();				// cc12 を宣言
value_t	valueof_cc12 = memory_read( addressof_cc + ( 1*4 + 2 ) * sizeof( int ), sizeof( int ));
										// cc[1][2] の値を得て
memory_write( addressof_cc23, valueof_cc23, sizeof( int ));		// cc12 の場所にその値を書く
// int	cc23 = cc[2][3];
address_t	addressof_cc23 = memory_tell();				// cc23 を宣言
value_t	valueof_cc23 = memory_read( addressof_cc + ( 2*4 + 3 ) * sizeof( int ), sizeof( int ));
										// cc[2][3] の値を得て
memory_write( addressof_cc23, valueof_cc23, sizeof( int ));		// cc23 の場所にその値を書く

これをポインタで書くとどうなるでしょうか
まず p[n] は *( p + n ) と同じなので cc[0] は *( cc + 0 ) と書けますね
この *( cc + 0 ) を受け取るポインタの型は何でしょう

cc の要素の型ですから int でしょうか
しかし int は cc[0] の型ではなく cc[0][0] の型ですね

では int* でしょうか
しかし int* は &cc[0] の型ではなく &cc[0][0] の型ですね

まず cc が何なのかを考えましょう
cc は int が3×4個ある配列です
言い方を変えると int が4つの配列が3個ある配列です
つまり「int が4つの配列」が3個の配列ということですから、この配列の要素は「int が4つの配列」ですね
つまり cc の要素である cc[0] は「int が4つの配列」であるということです
その cc[0] を指すポインタは int が4つの配列を指すポインタということですから、それは int (*)[4] になります

int	(*p)[4] = cc;
int	p01 = *( *( p + 0 ) + 1 );
int	p12 = *( *( p + 1 ) + 2 );
int	p23 = *( *( p + 2 ) + 3 );

// int	p01 = *( *( p + 0 ) + 1 );
address_t	addressof_p01 = memory_tell();					// p01 を宣言
address_t	valueof_p = ( address_t )memory_read( addressof_p, sizeof( address_t ));	// p の場所から読み出した値をシークオフセットとして
value_t	valueof_p01 = memory_read( valueof_p + ( 0*4 + 1 ) * sizeof( int ), sizeof( int ));
									// そこから値を得て
memory_write( addressof_p01, valueof_p01, sizeof( int ));				// p01 の場所にその値を書く
// int	p12 = *( *( p + 1 ) + 2 );
address_t	addressof_p12 = memory_tell();					// p12 を宣言
address_t	valueof_p = ( address_t )memory_read( addressof_p, sizeof( address_t ));	// p の場所から読み出した値をシークオフセットとして
value_t	valueof_p12 = memory_read( valueof_p + ( 1*4 + 2 ) * sizeof( int ), sizeof( int ));
									// そこから値を得て
memory_write( addressof_p12, valueof_p12, sizeof( int ));				// p12 の場所にその値を書く
// int	p23 = *( *( p + 2 ) + 3 );
address_t	addressof_p23 = memory_tell();					// p23 を宣言
address_t	valueof_p = ( address_t )memory_read( addressof_p, sizeof( address_t ));	// p の場所から読み出した値をシークオフセットとして
value_t	valueof_p23 = memory_read( valueof_p + ( 2*4 + 3 ) * sizeof( int ), sizeof( int ));
									// そこから値を得て
memory_write( addressof_p23, valueof_p23, sizeof( int ));				// p23 の場所にその値を書く

p[n] と *( p + n ) が同じだったように *( *( p + n ) + m ) と p[n][m] も同じ意味です
ただしこの p は int* ではなく int (*)[M] であるというところが違います

ところで上記の例を見ておわかりの通り cc は int [3][4] ですが中身は int[12] と同じです
ですから下記のようにすることもできなくはありません

int*	p = &cc[0][0];
int	p01 = p[0*4+1];
int	p12 = p[1*4+2];
int	p23 = p[2*4+3];

// int	p01 = p[0*4+1];
address_t	addressof_p01 = memory_tell();					// p01 を宣言
address_t	valueof_p = ( address_t )memory_read( addressof_p, sizeof( address_t ));	// p の場所から読み出した値をシークオフセットとして
value_t	valueof_p01 = memory_read( valueof_p + ( 0*4 + 1 ) * sizeof( int ), sizeof( int ));
									// そこから値を得て
memory_write( addressof_p01, valueof_p01, sizeof( int ));				// p01 の場所にその値を書く
// int	p12 = p[1*4+2];
address_t	addressof_p12 = memory_tell();					// p12 を宣言
address_t	valueof_p = ( address_t )memory_read( addressof_p, sizeof( address_t ));	// p の場所から読み出した値をシークオフセットとして
value_t	valueof_p12 = memory_read( valueof_p + ( 1*4 + 2 ) * sizeof( int ), sizeof( int ));
									// そこから値を得て
memory_write( addressof_p12, valueof_p12, sizeof( int ));				// p12 の場所にその値を書く
int	p23 = p[2*4+3];
address_t	addressof_p23 = memory_tell();					// p23 を宣言
address_t	valueof_p = ( address_t )memory_read( addressof_p, sizeof( address_t ));	// p の場所から読み出した値をシークオフセットとして
value_t	valueof_p23 = memory_read( valueof_p + ( 2*4 + 3 ) * sizeof( int ), sizeof( int ));
									// そこから値を得て
memory_write( addressof_p23, valueof_p23, sizeof( int ));				// p23 の場所にその値を書く

この通りまったく同じことです

ポインタと関数

関数が動作するとき、関数には必ずその関数専用のメモリ領域が存在しています
それは普通スタックというものですが、必ずしもスタックとは限りません
しかしともかく何らかの関数専用メモリ領域というものが存在します
そしてその関数に渡す引数はその関数専用のメモリ領域に置きますし、その関数のローカル変数もそこに置きます

この関数専用メモリ領域をファイルで考えると、関数が呼び出されたときの現在位置から先は好きに書き換えていいよ。関数への引数は現在位置の直前に置いておくよ。というようなことです

次のような関数があったとき、またシステム全体を一つのファイルであるとして考えてみると

void	foo( int	a, int	b, int*	presult ){
	int	result = 0;
	if( a > b ){
		result = a;
	}
	else{
		result = b;
	}
	*presult = result;
}


次のようになります

// void	foo( int	a, int	b, int*	presult ){
address_t	current_address = memory_tell();				// 関数が呼び出されたときの現在位置を覚えておく
address_t	addressof_presult = current_address - sizeof( address_t );	// presult を宣言
address_t	addressof_b = addressof_presult - sizeof( int );		// b を宣言
address_t	addressof_a = addressof_b - sizeof( int );			// a を宣言
int	valueof_a = ( int )memory_read( addressof_a, sizeof( int ));	// a の値を得る
int	valueof_b = ( int )memory_read( addressof_b, sizeof( int ));	// b の値を得る
// 	int	result = 0;
address_t	addressof_result = memory_tell();				// result を宣言
memory_write( 0, sizeof( int ));					// result の場所に 0 を書く
// 	if( a > b ){
if( valueof_a > valueof_b ){
// 		result = a;
memory_write( addressof_result, valueof_a, sizeof( int ));		// result の場所に a の値を書く
// 	}
}
// 	else{
else{
// 		result = b;
memory_write( addressof_result, valueof_b, sizeof( int ));		// result の場所に a の値を書く
// 	}
}
// 	*presult = result;
int	valueof_result = ( int )memory_read( addressof_result, sizeof( int ));
								// result の値を得る
address_t	valueof_presult = ( address_t )memory_read( addressof_presult, sizeof( address_t ));
								// presult の場所から読み出した値をシークオフセットとして
memory_write( valueof_presult, valueof_result, sizeof( int ));		// その場所に result の値を書く
// }
memory_seek( current_address );					// 元の位置の戻す

この foo を呼び出す関数 bar があったとき

void	bar(){
	int	ab = 0;
	foo( 10, 20, &ab );
}


これは次のようになります

// void	bar(){
address_t	current_address = memory_tell();		// 関数が呼び出されたときの現在位置を覚えておく
// 	int	ab = 0;
address_t	addressof_ab = memory_tell();		// ab を宣言
memory_write( 0, sizeof( int ));			// ab の場所に 0 を書く
// 	foo( 10, 20, &ab );
address_t	current_address_before_foo = memory_tell();	// 現在位置を覚える
memory_write( 10, sizeof( int ));			// 現在位置に 10 を書く
memory_write( 20, sizeof( int ));			// 現在位置に 20 を書く
memory_write( addressof_ab, sizeof( address_t ));	// 現在位置に &ab を書く
foo を呼ぶ
memory_seek( current_address_before_foo );		// 元の位置の戻す
// }
memory_seek( current_address );			// 元の位置の戻す

このように関数にポインタを渡して値を返してもらうというのは、ファイルのシークオフセットを渡してそこに値を入れてもらうというのと同じことです
別に難しくも何ともないですね

ポインタと関数:その2

今、関数を呼び出すときには 10, 20, &ab の順番に渡しました
これは受け取る側が presult, b, a の順番で取り出しているからそれに合わせてそうしました
取りだす側が a, b, presult の順で取り出すなら &ab, 20, 10 の順番に渡す必要があります
どちらの順番にするべきでしょうか

また上記の例では元の位置に戻す処理を呼び出す側と呼び出された側と両方でやっていて無駄です
どちらがやっても同じことなのでどちらか一方だけがやればいいことですが、ではどちらがやるべきでしょうか

この引数を渡す順番・取り出す順番やアドレスを元に戻す責任などの決まりを関数の呼び出し規約(calling convention/calling sequence)と言います
conversion じゃないよ

コンパイルスイッチや cdecl, _cdecl, __cdecl, __stdcall, __pascal, __fastcall, __declspec(naked) などなどのコンパイラ予約語で指定している呼び出し規約というのはこの決まりごとのことです

ポインタと関数:その3

ところで関数からローカル変数のアドレスを返してはいけないということはどういうことかおわかりでしょうか
次のコードを見てください

void	foo( int	a, int**	pp ){
	int	aaa[] = { 0, 1, 2, 3, 4, 5 };
	*pp = &aaa[a];
}

//void	foo( int	a, int**	pp ){
address_t	current_address = memory_tell();				// 関数が呼び出されたときの現在位置を覚えておく
address_t	addressof_pp = current_address - sizeof( address_t );		// pp を宣言
address_t	addressof_a = current_address - sizeof( int );			// a を宣言
int	valueof_a = ( int )memory_read( addressof_a, sizeof( int )); 	// a の値を得る
//	int	aaa[] = { 0, 1, 2, 3, 4, 5 };
address_t	addressof_aaa = memory_tell();				// aaa を宣言
memory_write( 0, sizeof( int ));					// aaa の場所に 0 を書く
memory_write( 1, sizeof( int ));					// aaa の場所に 1 を書く
memory_write( 2, sizeof( int ));					// aaa の場所に 2 を書く
memory_write( 3, sizeof( int ));					// aaa の場所に 3 を書く
memory_write( 4, sizeof( int ));					// aaa の場所に 4 を書く
memory_write( 5, sizeof( int ));					// aaa の場所に 5 を書く
//	*pp = &aaa[a];
address_t	valueof_pp = addressof_aaa + valueof_a;
memory_write( addressof_pp, valueof_pp, sizeof( address_t ));  		// pp の場所に &aaa[a] を書く
//}
memory_seek( current_address );					// 元の位置の戻す

これを次のように呼び出してみるとどんなことが起きるでしょうか

int*	p3 = NULL;
foo( 3, &p3 );
int*	p4 = NULL;
foo( 4, &p4 );
int	a3 = *p3;
int	a4 = *p4;

// int*	p3 = NULL;
address_t	addressof_p3 = memory_tell();				// p3 を宣言
memory_write( 0, sizeof( int ));					// p3 の場所に 0 を書く
// foo( 3, &p3 );
address_t	current_address_before_foo1 = memory_tell();			// 現在位置を覚える
memory_write( 3, sizeof( int ));					// 現在位置に 3 を書く
memory_write( addressof_p3, sizeof( address_t ));			// 現在位置に &p3 を書く
foo を呼ぶ
memory_seek( current_address_before_foo1 );				// 元の位置の戻す
// int*	p4 = NULL;
address_t	addressof_p4 = memory_tell();				// p4 を宣言
memory_write( 0, sizeof( int ));					// p4 の場所に 0 を書く
// foo( 4, &p4 );
address_t	current_address_before_foo2 = memory_tell();			// 現在位置を覚える
memory_write( 4, sizeof( int ));					// 現在位置に 4 を書く
memory_write( addressof_p4, sizeof( address_t ));			// 現在位置に &p4 を書く
foo を呼ぶ
memory_seek( current_address_before_foo2 );				// 元の位置の戻す
// int	a3 = *p3;
address_t	addressof_a3 = memory_tell();				// a3 を宣言
address_t	valueof_p3 = ( address_t )memory_read( addressof_p3, sizeof( address_t ));
								// p3 の場所から読み出した値をシークオフセットとして
int	valueof_a3 = ( int )memory_read( valueof_p3, sizeof( int ));	// その場所から読み出した値を
memory_write( valueof_a3, sizeof( int ));				// a3 の場所に書く
// int	a4 = *p4;
address_t	addressof_a4 = memory_tell();				// a4 を宣言
address_t	valueof_p4 = ( address_t )memory_read( addressof_p4, sizeof( address_t ));
								// p4 の場所から読み出した値をシークオフセットとして
int	valueof_a4 = ( int )memory_read( valueof_p4, sizeof( int ));	// その場所から読み出した値を
memory_write( valueof_a4, sizeof( int ));				// a4 の場所に書く

&p3 が 128 番地だったとしましょう
すると p3 自身の大きさがありますから p3 を宣言した後のシークオフセットは 128 + sizeof( int* ) 番地になりますね
そして、foo を呼び出すときに int と int* の分シークオフセットを進めるので、128 + sizeof( int* ) + sizeof( int ) + sizeof( int* ) 番地になります
ここで、sizeof( int ) を 4 バイト、sizeof( int* ) が 8 バイトであるとしてみると foo が呼び出された時点でのシークオフセットは 148 番地であることになります
foo では aaa を宣言していますから 148番地〜172番地までが aaa の領域になります
&aaa[3] は aaa + sizeof( int ) * 3 の位置ですから 148 + 12 で 160 番地になります
従って、最初の foo の呼び出しで p3 には 160 番地が入って戻ってくることになります
さて、foo から戻ってくると foo の中のローカル変数の分や foo に渡した引数の分は元に戻されるので、シークオフセットは 128 + sizeof( int* ) 番地、すなわち 136 番地に戻ります
ここで、p4 を宣言するので &p4 は 136 番地になります
そして、p4 自身の大きさの分シークオフセットを進めると 144 番地になります
ここでまた foo を呼び出すので int と int* の分シークオフセットを進めて、144 + sizeof( int ) + sizeof( int* ) で 156 番地になります
foo では 156番地〜180番地までが aaa の領域になります

さて、先ほど p3 には 160 番地が入っていましたね
ところが今その 160 番地は新しく呼び出された foo の aaa の領域になってしまいました
最初の呼び出しでは 160 番地には 3 が入っていましたが今は 2 になってしまいました

最初に関数専用のメモリ領域について「関数が呼び出されたときの現在位置から先は好きに書き換えていいよ」と言っていたのを覚えているでしょうか
これをもっと詳しく言うと「関数が呼び出される度にそのときそのときで現在位置がどこになっているのかはわからないけれども、どこであったとしてもともかく現在位置から先は好きに書き換えていいよ」ということなのです
呼び出されるたびにどこだかわからないというのですから、それはいつまでもそこにそのままあるものなどではなく、そのときだけの一時的なものなのです
この一時的というのが正確にはどのくらいなのかといえばそれは「その関数が呼び出されてから呼び出し元に戻るまでの間」です
つまり関数のローカル変数が有効なのはその関数の中だけです
だから「ローカル」というのです

ですからそのローカルなものを関数の外に出そうとしても壊れてしまうので、やってはいけませんということなのです

NULL ポインタ

アドレスが 1 ならメモリの先頭から 1バイト目の場所を指していますと言いましたが、ではアドレスが 0 ならメモリの先頭を指しているのでしょうか
そうであってもよさそうなものですが、違います
アドレスが 0 の場合だけは特別扱いになっていて、メモリの先頭を指しているのではなくどこも指していないものとして扱う。という決まりになっています
これが NULL ポインタです
NULL ポインタとはどこも指していないアドレスのことです
NULL ポインタはどこも指していないので NULL ポインタの指している先から値を得ようとすることは、エラーになりますし、NULL ポインタの指しているところに値を書き込もうとしてもエラーになります

ポインタとアセンブラ

ここではメモリ全体を一つのファイルと考えて、C/C++ のコードをファイル操作に置き換えて説明してきましたが、これは実はアセンブラがやっていることそのままです

ポインタやアドレスなどはアセンブラを覚えてしまえば全く当たり前のことになるので、新人さんはアセンブラも覚えてしましょう

アセンブラというと何か難しそうに感じるかもしれませんが、要はここに書いてあるファイル操作と同じ程度のことです
ファイル操作なら別に難しくもなんともないですよね

アセンブラに抵抗がなくなったら「C++プログラマのためのアセンブラ入門」でさらに理解を深めましょう

ポインタ変数の宣言

少し複雑になるとポインタ変数の宣言の読み方がわからなくなってしまうという人は「C/C++ 変数宣言の読み方入門」を読んでみるといいでしょう
規則を覚えてしまえば簡単ですよ