C言語の「*p」のあれこれ(*p++の悪夢)

#「*p」のあれこれ
C言語のポインタ学んでたら、「* p」周りで頭がこんがらかってきたので、整理しておく。

### まずは基本「*p」
```c
#include<stdio.h>
int main(void)
{
	int a[3], * p;
	p = a;
	a[0] = 10, a[1] = 20, a[2] = 30;
	printf("*p...%d\n", *p);
}
// 出力結果：*p...10
```
これは簡単。
「p = a」で、pにa[0]のアドレスを入れる。
そして、a[0]の中身を取り出しているだけ。
### 「*p+1」
```c:
#include<stdio.h>
int main(void)
{
	int a[3], * p;
	p = a;
	a[0] = 10, a[1] = 20, a[2] = 30;
	printf("*p+1...%d\n", *p+1);
}
// 出力結果：*p+1...11
```
これもそう難しくはない。
a[0]の中身を取り出して、それに1を足しているだけだ。
(C言語の文法的な仕様で、「*(参照)」のほうが「+」よりも優先度が高く、先に処理される)

### 「*(p+1)」
```c:
#include<stdio.h>
int main(void)
{
	int a[3], * p;
	p = a;
	a[0] = 10, a[1] = 20, a[2] = 30;
	printf("*(p+1)...%d\n", *(p+1));
}
// 出力結果：*(p+1)...20
```
さて、今回は括弧が付いた。
括弧は最も優先度の高い演算子である。
よって、「p+1」が先に処理される。
「p+1」は、**pが指定する「a[0]」からアドレスを1つ進める**ということだ。
すなわち。**「a[0+1]」と捉えることができるので、ポインタpは「a[1]」と同値である**ことが分かる。
これに「*」が付くので、ポインタの指す中身が取り出されて、出力結果は20。

### 「*p+=1」
```c:
#include<stdio.h>
int main(void)
{
	int a[3], * p;
	p = a;
	a[0] = 10, a[1] = 20, a[2] = 30;
	printf("*p+=1...%d\n", *p+=1);
}
// 出力結果：*p+=1...11
```
これはどうだろう。
「アスタリスク」(参照)は「+=」よりも優先順位が高い。
そのため、**「*p」が先に処理される**。
「*p」は「*a[0]」と同値であるから、示す値は「10」。
その後、「+=1」されるので、出力結果は11。

### ここからが鬼門「*p++」
```c:
#include<stdio.h>
int main(void)
{
	int a[3], * p;
	p = a;
	a[0] = 10, a[1] = 20, a[2] = 30;
	printf("*p++...%d\n", *p++);
}
// 出力結果：*p++...10
```
多くの初学者は、インクリメントしているので「10+1で11」だと思うであろう。私もそうだった。
しかしそれは間違いである。

ここで重要なことは、**インクリメントには「前置インクリメント(例:++a)」「後置インクリメント(例:a++)」の２種類があり**、その違いを理解することである。

------

というわけで、先に前置/後置インクリメントの違いを確かめておこう。

```c:前置インクリメントと後置インクリメント
...
int a, a_before, a_after;

a = 0;
printf("a...%d\n", a);

// 前置インクリメント
a_before = ++a; 
printf("a_before(前置、++a)...%d\n", a_before);
printf("その後のa...%d\n", a);

// 後置インクリメント
a = 0;
a_after = a++; 
printf("a_after(後置、a++)...%d\n", a_after);
printf("その後のa...%d\n", a);

...
// 出力結果：a...0
//         a_before(前置、++a)...1
//         その後のa...1
//         a_after(後置、a++)...0
//         その後のa...1
```

こちらのサンプルコードの実行結果から分かるように、
**・前置インクリメント→値を渡す前に値が変化
・後置インクリメント→値を渡したのちに値が変化**
という違いがある。

より正しく書くと、
**後置インクリメントは、「値を利用してから変化させる」と言える。**


---------

さて、ここで「* p++」について再び考えてみよう。

```c
// 中略
p = a;
a[0] = 10, a[1] = 20, a[2] = 30;
printf("*p++...%d\n", *p++);
// 中略
```

**これは後置インクリメントだ**。
**先に、ポインタpの参照先である「a[0]」に格納されている値「10」がprintf関数へ渡されて出力される。**
ここではじめてpの値が使われる。このあと、格納されている値(アドレス)が進むのである。

ところで、本当に最終的にポインタpのアドレスは進められているのか？実験をしてみよう。
追記してみて、**その後の値がどうなっているのか調べてみる。**

```c:
// 中略
printf("*p++...%d\n", *p++);
// 追加：【実験】その後の値はどうなっているのか？
printf("その後の*p...%d\n", *p);
// 出力結果：*p++...10
//          その後の*p...20
```
これでハッキリとした。
**後置インクリメント**なので、
**「値を使ってから」
「ポインタ内のアドレスを進める」**のである。


### まぁ当然だよね「(*p)++」
```c:
#include<stdio.h>
int main(void)
{
	int a[3], * p;
	p = a;
	a[0] = 10, a[1] = 20, a[2] = 30;
	printf("(*p)++...%d\n", (*p)++);
}
// 出力結果：(*p)++...10
```
これは簡単だ。
さっきの例を見たあとでは混乱するかもしれないが、冷静に考えてみてほしい。
後置インクリメントより、**括弧のほうが優先順位が高い。**
先に括弧内の処理がされるのである。

### 驚きの後置インクリメントパワー「*(p++)」
```c:
#include<stdio.h>
int main(void)
{
	int a[3], * p;
	p = a;
	a[0] = 10, a[1] = 20, a[2] = 30;
	printf("*(p++)...%d\n", *(p++));
}
// 出力結果：*(p++)...10
```


ここで思い出してほしいのが、後置インクリメントによって変化するのは、**「値を使われてから」**という特徴である。
printf関数に値が渡されてはじめて値が使われるので、出力結果ではインクリメントされていない状態である。
よって、＊p++と同じ動作をする。
ややこしいと感じるなら、以下のように普通のint型変数として考えればわかりやすいだろう。

```c
int a;
a = 0;
printf("a++...%d", a++);
a = 0;
printf("(a++)...%d", (a++));
// 出力結果：a++...0
//          (a++)...0
```



### インクリメントの場所変えてみようぜ「*++p」
```c:
#include<stdio.h>
int main(void)
{
	int a[3], * p;
	p = a;
	a [0] = 10, a[1] = 20, a[2] = 30;
	printf("*++p...%d\n", *++p);
}
// 出力結果：*++p...20
```

これは**前置インクリメント**だ。
前置インクリメントは、**値を使う前にインクリメントされる**。
つまり、はじめにポインタpがa[0]からa[1]へ進む。
そして、printf関数にポインタaの参照先a[1]の値「20」が渡される。

### 最後に、一番前でインクリメントしようぜ「++*p」
```c:
#include<stdio.h>
int main(void)
{
	int a[3], * p;
	p = a;
	a[0] = 10, a[1] = 20, a[2] = 30;
	printf("++*p...%d\n", ++*p);
}
// 出力結果：++*p...11
```
これは単純に「*p」で取り出した値「10」をインクリメントしている。
つまり「11」。

#まとめ
インクリメントの前置・後置の違いを理解すると、何も難しくない。
とても勉強になった。