LoginSignup
3
3

More than 1 year has passed since last update.

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

Last updated at Posted at 2021-01-27

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

基本

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

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;
}
実行結果
arr[1]の出力結果:b
*(arr+1)の出力結果:b

配列を関数に渡すとき

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

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 */
    }
}
実行結果
arrの中身:0x7ffed07d95a6
arr[5]書換前:f
arr[5]書換後:x

配列名と通常のポインタとの違い

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

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

ポインタの書き換えができない

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

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;
}
実行結果
abcdef

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

main.c(一部抜粋)
    while (*arr != 'g'){
        putchar(*arr);
        arr++;  /* ポインタのアドレスをインクリメントし次の要素を参照 */
    }
実行結果
/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した時の値は通常のポインタと同様の値が返ります。(先述した関数中で配列の要素数を取得できない理由がこれです。)

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));
}
実行結果
sizeof(arr):10
sizeof(ptr):8
sizeof(in_ptr):8

編集履歴

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3