【C/C++】配列の要素数を求める色々な方法と注意点


C言語やC++で配列のサイズ(配列の要素数)を求める方法です。『固定長配列/静的配列』と『ポインタ配列/動的配列/可変長配列』で異なる方法を用いる必要があります。

静的配列

配列全体のサイズから型のサイズを割ることで配列の要素数が求められます。

int ary[] = {1, 2, 3};
printf("%ld", sizeof(ary) / sizeof(int)); // 3

配列全体のバイト数sizeof(ary)を配列要素の型サイズsizeof(int)で割っているだけの単純な処理です。これで配列の要素数が簡単に求められます。

式の展開イメージは以下の通りです。

int ary[] = {1, 2, 3};
sizeof(int); //  4byte (int型のサイズ)
sizeof(ary); // 12byte (int型 × 3個分のサイズ)
sizeof(ary) / sizeof(int); // (12 / 4) = 3

マクロにする

この計算式はわりと頻繁に使うので、マクロにしておくと便利です。

#define COUNTOF(array) (sizeof(array) / sizeof(array[0]))

汎用化のためにsizeof(int)array[0]にしました(この式によって配列要素の型サイズが得られます。*arrayとしても良いです)。int型以外の型にも対応出来るようになります。

マクロ名はcountofの他に、numofarraySizeOfARRAY_SIZEARRAY_LENGTH等、様々な候補があります。海外ではcountofがよく使われているようです。私はCOUNTOF派です。ちなみにWindowsの世界では_countofというマクロが既に用意されています

なおsizeof(array)による配列サイズの取得には注意が必要です。次の# ポインタ配列を参考にしてください。

テンプレートにする

C++の場合はマクロの代わりにテンプレートを使うのがオススメです。

template<class T, size_t N> size_t countof(const T (&array)[N]) { return N; }

ポインタ配列

ポインタ配列の場合はsizeofによる配列サイズの取得が行えないため注意してください。先ほど紹介したsizeof(array) / sizeof(array[0])式は使用出来ません。

配列全体のサイズが取得出来るのは、sizeofを配列宣言箇所と同一スコープ内で使用した場合のみです。

ポインタ配列の場合はポインタのサイズが使われてしまいます。

void sizes(int *a, int b[], int c[3]) {
   printf("%ld", sizeof(a)); // 8 (ポインタ型のサイズ)
   printf("%ld", sizeof(b)); // 8 (ポインタ型のサイズ)
   printf("%ld", sizeof(c)); // 8 (ポインタ型のサイズ)
   
   // 同一スコープ内であれば期待値が得られる
   int d[] = {1, 2, 3};
   printf("%ld", sizeof(d)); // 12 (期待値)
}

int main() {
   int ary[] = {1, 2, 3};
   sizes(ary, ary, ary);
}

対応策1

ポインタ配列のサイズを配列とセットで管理する方法が一般的です。配列を関数に渡す際には事前に計算された配列サイズを一緒に渡します。

int ary[] = {1, 2, 3};
print(ary, sizeof(ary) / sizeof(ary[0])); // サイズを事前計算

void print(int ary[], size_t size) {
   for (int i = 0; i < size; i++)
      printf("%d, ", ary[i]); // 1, 2, 3,
}

対応策2

ヌル終端のテクニックを用いる方法もあります。ヌルの代わりに-1が使われることが多いです。

int ary[] = {1, 2, 3, -1}; // この-1が終端
print(ary);

void print(int ary[]) {
   // -1が出現するまで処理を続ける
   for (int i = 0; ary[i] != -1; i++)
      printf("%d, ", ary[i]); // 1, 2, 3,
}

このように予め決められた値(番兵)が出現するまで処理を行うようにすれば、配列サイズを事前に知らせる必要は無くなります。

実はC言語の文字列もこれと同じ発想で実現されています(終端文字は'\0')。

// データ上は同じもの
const char a[] = {'a', 'b', '\0'};
const char *b = "ab";

printf("%d", a[2] == b[2]); // 1

豆知識

対応策1のように配列サイズを直接やり取りする方法もよく使われていますが、可変長配列のようなサイズが頻繁に変化するような配列では対応策2の番兵を用いた方法もよく使われます。

余談ですが、可変長配列は構造体で実現されることもあります。

struct List {
   size_t size;
   int *array;
};

配列のサイズを頻繁に問い合わせる必要がある場合は、構造体や対応策1の方法を取ると良いでしょう。

まとめ

  • 固定長配列の場合はsizeof(a) / sizeof(a[0])で求められる
  • ただし同一スコープ内で宣言された配列に限る
  • 別スコープに配列を渡す場合は一緒に配列サイズを渡す
  • または番兵を用いた配列をやり取りする方法もある

広告