C言語のポインタおよびポインタへのポインタの使い方をまとめてみた.
主にint型とint型へのポインタを例に説明する.
アドレス演算子&と間接演算子*
単項演算子の&をアドレス演算子という. 二項演算子の&はビット演算子の1つであり、&&は論理演算子の1つである.
同様に単項演算子の*を間接演算子という. 二項演算子の*は算術演算子の1つである. 変数の宣言時に*をつけるとその型へのポインタであることを表す.
記号 | 意味 |
---|---|
&a | 変数aのアドレス |
a&b | aとbの各ビットのANDをとる |
a==1 && b==0 | 左項と右項の論理積をとる |
記号 | 意味 |
---|---|
*a | 変数aの間接参照 |
a*b | aとbの掛け算 |
int *a | int型へのポインタを宣言 |
int **a | int型へのポインタへのポインタを宣言 |
ポインタの考え方
ここではアドレス演算子や間接演算子をアドレスと値を行き来する演算子と考えてみよう.
重要なのは、int型もint型へのポインタもアドレスと値を持つ ということである.
例えば、以下のようなC言語のコードがあるとする.
int a = 4; int *pa = &a; int **ppa = &pa; printf("%p\n", &a); // 0xffffff00 printf("%d\n", a); // 4 printf("%p\n", &pa); // 0xffffff04 printf("%p\n", pa); // 0xffffff00 printf("%d\n", *pa); // 4 printf("%p\n", &ppa); // 0xffffff08 printf("%p\n", ppa); // 0xffffff04 printf("%p\n", *ppa); // 0xffffff00 printf("%d\n", **ppa); // 4
つまりは以下のことが言える.
これを図にしたものが以下である.
アドレス演算子と間接演算子の関係をアドレスと値の視点から理解すれば、ポインタへのポインタや、ポインタへのポインタへのポインタも動きも理解しやすくなる(ただし実用上はポインタへのポインタまでしか使わない).
ポインタを含む演算
ここではアドレス演算子や間接演算子をint型とint型へのポインタを行き来する演算子と考えてみよう.
ここでは便宜上、int型へのポインタをint*型、int型へのポインタへのポインタをint**型と呼称する.
int*型に間接演算子*をつけるとint型になる. int**型に間接演算子*をつけるとint*型になる. つまり、間接演算子*を1つつけるごとに変数宣言時の*が一つ取り除かれた型になる. 取り除く*がないint型には間接演算子*をつけることはできない(コンパイルエラーになる).
aの宣言 | 間接演算子込みの表記 | 間接演算子込みの型 or エラーメッセージ |
---|---|---|
int a | *a | invalid type argument of unary ‘*’ (have ‘int’) |
int *pa | *pa | int |
int *pa | **pa | invalid type argument of unary ‘*’ (have ‘int’) |
int **ppa | *ppa | int* |
int **ppa | **ppa | int |
int **ppa | ***ppa | invalid type argument of unary ‘*’ (have ‘int’) |
int型にアドレス演算子&をつけるとint*型になる. int*型にアドレス演算子&をつけるとint**型になる. つまり、アドレス演算子&を1つつけるごとに変数宣言時の*が一つ増えた型になる. ただし、間接演算子とは異なりアドレス演算子&は1つしかつけることはできない(コンパイルエラーになる).
aの宣言 | アドレス演算子込みの表記 | アドレス演算子込みの型 or エラーメッセージ |
---|---|---|
int a | &a | int* |
int a | &&a | label ‘a’ used but not defined |
int *pa | &pa | int** |
int *pa | &&pa | label ‘pa’ used but not defined |
int **ppa | &ppa | int*** |
int **ppa | &&ppa | label ‘ppa’ used but not defined |
C言語の四則演算や代入は基本的には同じ型に揃える必要がある. 関数の引数がint*型とint**型だった場合は、int*型の変数にアドレス演算子&をつけるか、int**型の変数に間接演算子*をつけて2つの変数の型を揃える必要がある.
void hoge(int* pa, int** ppa) { ppa = &pa; // int**型に揃える場合 pa = *ppa; // int*型に揃える場合 }
アドレス演算子と間接演算子の関係をint型とint型へのポインタの視点から理解すれば、プログラムが早く書けるようになる.