【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(ary); // 12byte (int型 × 3個分のサイズ)
sizeof(int); // 4byte (int型のサイズ)
sizeof(ary) / sizeof(int); // 3 (12 / 4)

マクロにする

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

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

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

マクロ名はcountofの他、numofarraySizeOfARRAY_SIZEARRAY_LENGTH等、様々な候補があります。海外ではcountofがよく使われているようです。

ちなみにWindowsの世界では要素数計算用に_countofというマクロが事前に用意されています。

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

テンプレートにする

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

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

次期C++規格(C++17)ではstd::sizeの利用が可能になる予定です。

template <class T, std::size_t N>
constexpr std::size_t size(const T (&array)[N]) noexcept { return N; }

またC++標準ライブラリのextentクラスで配列の要素数を求めることもできます。

// #include <type_traits>
int a[] = {3, 2, 1};
std::extent<decltype(a), 0>::value; // 3

動的配列、可変長配列、ポインタ渡しされた配列

ポインタ形式の動的配列/可変長配列やポインタ渡しされた配列の場合は、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};
size_t size = sizeof(ary) / sizeof(ary[0]); // サイズを事前計算
print(ary, size); // 配列とサイズを渡す

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 (true)

豆知識

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

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

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

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

多次元配列

2次元配列や3次元配列に対して、次元別の要素数を求めることも可能です。方法と原理は# 固定長配列の時と同じです。

int a[4][2] = {{0, 0}, {0, 1}, {1, 0}, {1, 1}};
sizeof(a) / sizeof(a[0]);       // 4 (一次元目の要素数)
sizeof(a[0]) / sizeof(a[0][0]); // 2 (二次元目の要素数)

このようにsizeof(a[0])とすることで、二次元目の配列サイズを取得することが出来ます。これをint型の要素サイズ(sizeof(a[0][0]))で除算することで、二次元目の要素数が求められるわけです。

sizeof(a[0][0]); //  4 (sizeof(int))
sizeof(a[0]);    //  8 (2 * sizeof(a[0][0]))
sizeof(a);       // 32 (4 * sizeof(a[0]))

なお、三次元配列の場合に三次元目の要素数を求めたい場合には、sizeof(a[0][0]) / sizeof(a[0][0][0])という形で要素数を求めます。

int a[3][2][1] = {{{0}, {1}}, {{1}, {0}}, {{1}, {1}}};
sizeof(a[0][0]) / sizeof(a[0][0][0]); // 1 (三次元目の要素数)

// 豆知識: *a は a[0]と同じ意味。**a は a[0][0]と同じ意味
sizeof(a) / sizeof(*a);     // 3 (一次元目の要素数)
sizeof(*a) / sizeof(**a);   // 2 (二次元目の要素数)
sizeof(**a) / sizeof(***a); // 1 (三次元目の要素数)

C++の場合はextentというテンプレートクラスを用いることで、より簡単に次元別の要素数を取得することが出来ます。

// #include <type_traits>
int a[][2] = {{0, 0}, {0, 1}, {1, 0}, {1, 1}};
std::extent<decltype(a), 0>::value; // 4 (一次元目の要素数)
std::extent<decltype(a), 1>::value; // 2 (二次元目の要素数)

まとめ

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

広告

関連するオススメの記事