C言語の[] 演算子と*演算子、配列とポインタの関係

C言語を学習していると、割と初期のほうに出てくる配列ですが、実は、C言語の壁と言われるポインタと深い関係があります。
この記事ではC言語の配列とポインタの関係と、`[]`演算子と`*`（間接演算子）の関係をまとめました。ある程度ポインタのことがわかる人向けです。

# 基本
`ptr[i]`と`*(ptr+i)`は同じ意味になります。そのため、ソースコード中で相互に書き換え可能です。
（`ptr[i][j]`などの2次元配列を扱う場合は、`*(*(ptr+i)+j)`と置き換えできます。@SaitoAtsushi さんありがとうございます。）

```c:main.c
#include <stdio.h>

int main (void)
{
    char arr[10]={'a','b','c','d','e','f','g','h','j','k'};
    
    printf("arr[1]の出力結果：%c\n",arr[1]);
    printf("*(arr+1)の出力結果：%c\n",*(arr+1));
    
    return 0;
}
```

```plaintext:実行結果
arr[1]の出力結果：b
*(arr+1)の出力結果：b
```

# 配列を関数に渡すとき
配列名に[]をつけなかった場合、配列の先頭アドレスを示します。
配列の先頭アドレスを関数に渡すことで、関数中で配列を参照・書き換えすることが可能です。
また、配列に関数を渡す際は、要素数の情報を別途与える必要があります。（`sizeof(in_ptr)`では不可能、理由は後述）

```c:main.c
#include <stdio.h>
void func1(char *in_ptr, int len);

int main (void)
{
    char arr[10]={'a','b','c','d','e','f','g','h','j','k'};
    printf("arrの中身:%p\n",arr); /* 配列名に[]を付けない場合は配列の先頭アドレスを示す */
    printf("arr[5]書換前：%c\n",arr[5]);
    func1(arr, sizeof(arr)/sizeof(char));/* 配列の先頭アドレスを関数に渡す */
    printf("arr[5]書換後：%c\n",arr[5]);
    return 0;
}

void func1(char *in_ptr, int len)
{
    int i;
    for(i=0;i<len;i++){
        in_ptr[i] = 'x'; /* in_ptr[i]の代わりに*(in_ptr+i)でもOK */
    }
}
```

```plaintext:実行結果
arrの中身:0x7ffed07d95a6
arr[5]書換前：f
arr[5]書換後：x
```

# 配列名と通常のポインタとの違い
配列名は基本的にはポインタと同列に扱うことができますが、以下の違いがあります。

- ポインタの書き換えができない
- `sizeof`したときに配列サイズを持つ

### ポインタの書き換えができない
ポインタであれば、以下のようにポインタをインクリメントして、配列の次の要素に移動することができます。

```c:main.c
#include <stdio.h>

int main (void)
{
    char arr[10]={'a','b','c','d','e','f','g','h','j','k'};
    char *ptr;
    
    ptr = arr; /* 配列の先頭アドレスをポインタに格納 */
    
    while (*ptr != 'g'){
        putchar(*ptr);
        ptr++;  /* ポインタのアドレスをインクリメントし次の要素を参照 */
    }
    
    return 0;
}
```

```plaintext:実行結果
abcdef
```

しかし、これと同様のことを配列名でやろうとするとエラーになります。読み取り専用のポインタとして考えるのが一番自然かと思います。

```c:main.c（一部抜粋）
    while (*arr != 'g'){
        putchar(*arr);
        arr++;  /* ポインタのアドレスをインクリメントし次の要素を参照 */
    }
```

```plaintext:実行結果
/home/ec2-user/environment/ctest/main.c: In function ‘main’:
/home/ec2-user/environment/ctest/main.c:12:12: error: lvalue required as increment operand
         arr++;  /* ポインタのアドレスをインクリメントし次の要素を参照 
```

### `sizeof`したときに配列サイズを持つ
配列名で`sizeof`を実施すると、型のサイズ(今回はcharなので1)×要素数(10)の値が返ってきます。一方、通常のポインタの場合は、常に同じ値（64ビット環境であれば8、32ビット環境であれば4）が返ってきます。
配列を関数に渡す際は、あくまでも先頭アドレスを渡すことになるため、sizeofした時の値は通常のポインタと同様の値が返ります。(先述した関数中で配列の要素数を取得できない理由がこれです。)

```c:main.c（一部抜粋）
#include <stdio.h>
void func1 (char *in_ptr);

int main (void)
{
    char arr[10]={'a','b','c','d','e','f','g','h','j','k'};
    char *ptr;
    
    ptr = arr; /* 配列の先頭アドレスをポインタに格納 */
    
    printf("sizeof(arr):%zu\n",sizeof(arr));
    printf("sizeof(ptr):%zu\n",sizeof(ptr));
    
    func1(arr); /* 配列の先頭アドレスを関数に渡す */
    
    return 0;
}

void func1 (char *in_ptr)
{
    printf("sizeof(in_ptr):%zu\n",sizeof(in_ptr));
}
```

```plaintext:実行結果
sizeof(arr):10
sizeof(ptr):8
sizeof(in_ptr):8
```

# 編集履歴
- @fujitanozomu さんより、size_t型の書式指定子の編集リクエストをいただいたので反映しました。signedかunsignedかを考えず、とりあえず`%d`にするのは私の悪い癖です。ただ、`%zu`は初見だったので調べたところ、以下の記事が参考になりました。
　　[printf() > size_t型の書式指定子 > %zu][1]
- @SaitoAtsushi さんより、コメントをいただいたので検証して反映しました。


[1]:https://qiita.com/7of9/items/ca5ebf9b7861acbcf656