コンテンツにスキップ

C言語/配列とポインタ

出典: フリー教科書『ウィキブックス(Wikibooks)』

配列とポインターの基本

[編集]

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

  1. 配列変数の名前は、アドレス式の文脈では先頭要素のアドレスに等しい。
  2. 配列変数の名前に sizeof 演算子を適用すると、 sizeof( 要素型 ) * 要素数が返る(単一の要素型のサイズではなく全体のサイズ)。
  3. Cでは、配列変数の名前は(Fortranの様に)代入の右辺には出来ない。
  4. : このため strcpy():番人型、memcpy():サイズ型の標準ライブラリー関数が用意されている。

また、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 < sizeof a / sizeof *a; i++)
    printf("%d ", a[i]); //配列aの要素を一覧表示する。
  printf("\n");

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

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

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

[編集]
配列変数名とアドレス演算子
#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である必要があります。

脚註

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

参考文献

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