C言語/配列とポインタ

配列とポインターの基本編集

配列とポインターには密接な関係がある。

式の中では、配列は「その先頭要素へのポインター」に読み替えられる(この場合、ポインターのしめす型は配列の要素型であるが多次元配列では複合的になるので次数の計算が必要になる 後述)。

また、p[i]は*(p+i)の糖衣構文である[1]

1次元の配列と要素型へのポインター編集

次の例のように、ポインターに配列のアドレスを代入し、ポインター演算によって配列の要素を一括で参照することができる。(この他、一括ではなく要素ひとつひとつにポインタを作ることも可能だが、実用性が乏しいので、ひとつひとつのポインタについては本ページでは省略する。)ポインタ *pa には参照先配列の先頭のアドレス値が入るので、宣言するポインタの書式については、配列宣言ではなく一変数の宣言になる。

1次元の配列と要素型へのポインターの使用例
#include <stdio.h>

int main(void) {
  int a[5] = {2, 3, 5, 7, 11};

  for (int i = 0; i < 4; i++)
    printf("%d ", a[i]); //配列aの要素を一覧表示する。
  printf("\n");

  int *pa = a; /* 配列名(この場合は「a」)は配列の先頭要素(int)のアドレスを示す。
                  &a は配列のアドレスを表すのでその場合は、int (*)[10] が型となる。 */
  
  printf("%d\n", *(pa + 2)); //ポインターpaを使って配列aの要素を表示する。
}
実行結果
2 3 5 7
5

一般に配列の先頭要素のアドレスを格納したポインター変数 pa では、 +0 によって配列の0項目。+1によって配列の1項目、+2によって配列の2項目、・・・を参照できます。 このため、上記のようなというコードで、配列のそれぞれの要素を参照できます。

1次元の配列と配列へのポインター編集

配列の要素型へのポインターではなく、配列へのポインターを使うと以下のようになります。

1次元の配列と配列へのポインター
#include <stdio.h>

int main(void) {
  int a[5] = {2, 3, 5, 7, 11};
  int (*pp)[sizeof a / sizeof *a] = &a; // 配列へのポインターを配列のアドレスで初期化

  for (int i = 0; i < 4; i++)
    printf("%d ", (*pp)[i]);
  printf("\n");
  
  printf("%d\n", *((*pp) + 2)); //配列へのポインターppを使って配列aの要素を表示する。
}
実行結果
2 3 5 7
5

配列変数名とアドレス演算子編集

配列変数名とアドレス演算子
#include <stdio.h>
int main(void) {
  int a[10];

  printf("In : int a[10]\n");
  printf("  a = %p\n", a);
  printf("  &a = %p\n", &a);
  printf("  &a[0] = %p\n", &a[0]);
  printf("  sizeof a = %zu\n", sizeof a);
  printf("  sizeof &a = %zu\n", sizeof &a);
  printf("  sizeof *a = %zu\n", sizeof *a);
  printf("  sizeof *&a = %zu\n", sizeof *&a);
  printf("  sizeof a[0] = %zu\n", sizeof a[0]);
  printf("  sizeof(int) = %zu\n", sizeof(int));
}
実行結果
In : int a[10]
  a = 0x7ffcbd471ab0
  &a = 0x7ffcbd471ab0
  &a[0] = 0x7ffcbd471ab0
  sizeof a = 40
  sizeof &a = 8
  sizeof *a = 4
  sizeof *&a = 40
  sizeof a[0] = 4
  sizeof(int) = 4
a
配列変数aの先頭要素(a[0])のアドレス
&a
配列変数aのアドレス
&a[0]
配列変数aの先頭要素(a[0])のアドレス
sizeof a
配列変数aのバイト数
sizeof &a
配列変数aのアドレスのバイト数
*a
配列変数aの先頭要素(a[0])のバイト数
*&a
配列変数aのアドレスの示すオブジェクトのバイト数
sizeof a[0]
配列変数aの先頭要素のバイト数
sizeof(int)
int型のバイト数

a&a[0]&aも同じアドレスですが、sizeof演算子を適用したときの値のに違いがあることに気がつくと思います。

配列の要素数を求めるイディオム編集

配列について sizeof a / sizeof *a というイディオムを用いて、配列の要素数を求めることができます。

  • 配列へのポインターを宣言する場合の要素数
  • 要素個数分イテレーションするfor文のカウンター変数の上限

などで有用です。 もし、計算によらずハードコードしてしまった場合、変更漏れの原因となり発見困難なバグの原因になります(上記の2例はコンパイラーの警告の対照にすることが極めて困難(不可能?)です。)。

多次元配列と多次元配列へのポインター編集

多次元配列の各次元の要素数を得るためには、次の様に隣り合った次元間のsizeof数の商を使います。

多次元配列の各次元の要素数を得る
#include <stdio.h>

int main(void) {
  int a[][4] = {
      {0, 1, 2, 3,},
      {4, 5, 6, 7,},
      {8, 9, 10, 11} };

  printf("Y = %lu\n", sizeof a / sizeof *a);
  printf("X = %lu\n", sizeof *a / sizeof **a);
  for (int y = 0; y < sizeof a / sizeof *a; y++) {
    for (int x = 0; x < sizeof *a / sizeof **a; x++) {
      printf("%2d ", a[y][x]);
    }
    printf("\n");
  }
  
  int (*p)[sizeof a / sizeof *a][sizeof *a / sizeof **a] = &a;
  for (int y = 0; y < sizeof a / sizeof *a; y++) {
    for (int x = 0; x < sizeof *a / sizeof **a; x++) {
      printf("%+2d ", (*p)[y][x]);
    }
    printf("\n");
  }
  printf("sizeof *p = %zu \n", sizeof *p);
}
実行結果
Y = 3
X = 4
 0  1  2  3 
 4  5  6  7 
 8  9 10 11 
+0 +1 +2 +3 
+4 +5 +6 +7 
+8 +9 +10 +11 
sizeof *p = 48
配列の配列へのポインターの宣言の例
  int (*p)[sizeof a / sizeof *a][sizeof *a / sizeof **a] = &a;
(*p)( )で括らないとポインターの配列の配列になってしまうので、( ) は必須です。
また、aではなく&aである必要があります。

構造体配列のポインター編集

構造体を要素とする一次元配列に関するポインターも、原理的には一次元配列に対するポインターと同様の手法で一括に参照可能です。 通称「構造体配列」とは何かについては『C言語/構造体#構造体の配列』を参照してください。 

ただしポインタ *pa には参照先配列の先頭のアドレス値が入るので、宣言するポインタの書式については、配列宣言ではなく一変数の宣言になる。

コード例
#include <stdio.h>

typedef struct {
  char* name;
  int price;
} product_entry;

int main(void) {
  // const があるとなぜか後工程でエラーになるのでconst除去。MinGW 確認
         product_entry products[] = {{"牛乳", 200},
                                    {"オレンジジュース", 150},
                                    {"トマトジュース", 180},
                                    {"りんごジュース", 220}};

  for (size_t i = 0; i < sizeof(products) / sizeof(products[0]); i++) {
    printf("商品名: %s, 価格: %d円\n", products[i].name, products[i].price);
  } 

// ここまで『C言語/構造体#構造体の配列』の内容。
// 以下、本ページ独自の内容。

  product_entry *pa = products;
  printf("%s ", (*(pa + 2)).name   ); //ポインターpaを使って配列aの要素を表示する。
  printf("\n");

}

実行結果

商品名: 牛乳, 価格: 200円
商品名: オレンジジュース, 価格: 150円
商品名: トマトジュース, 価格: 180円
商品名: りんごジュース, 価格: 220円
トマトジュース


脚註編集

  1. ^ p[i] ⇒ *(p+i) ⇒ *(i+p) ⇒ i[p] との交換則が成り立ち、難読化プログラミングでしばしば用いられる。
    C言語のほかの糖衣構文]としては、 p->m ⇒ (*p).m があげられる。

参考文献編集

  • 前橋和弥著『C言語 ポインター完全制覇』平成13年6月25日初版第4刷発行