ぷるぷるの雑記

低レイヤーがんばるぞいなブログ. 記事のご利用は自己責任で.

C言語のポインタ

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型へのポインタの視点から理解すれば、プログラムが早く書けるようになる.