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); // 3 (12 / 4)

マクロにする

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

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

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

なおsizeof(array)による配列サイズの取得には注意が必要です。次の「動的配列の要素数を求める」を参考にしてください。

マクロ名はcountofCOUNTOFの他、numofarraySizeOfARRAY_SIZEARRAY_LENGTH等、様々な候補があります。海外ではcountofがよく使われているようです。ちなみにWindowsの世界では要素数計算用に_countofというマクロが事前に用意されています。

_countof マクロ - msdn.microsoft.com

テンプレートにする

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); // いずれもポインタ渡し
}

次に紹介するいずれかの方法を用いて、配列のサイズを管理、または求める必要があります。

配列と配列のサイズを一緒に管理する方法

配列と配列サイズをセットで管理する方法が一般的に用いられています。配列を関数に渡す際に、事前に計算された配列サイズを一緒に渡すようにします。

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,
}

ヌル終端による番兵を用いる方法

ヌル終端のテクニックを用いる方法もあります。要素が数値型の場合はヌルの代わりに-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)

要素がポインタ型の場合には、番兵にヌルを用いると良いでしょう。以下はヌル終端配列の走査テクニックです。このような走査用の一時的なポインタ変数を宣言することによって、簡潔な記述を実現することができます。

const char *schemes[] = {"http", "https", "ftp", NULL};
const char **each = schemes;
while (*each) puts(*each++); // "http\nhttps\nftp\n"

どちらを使うべきか

一つ目の「# 配列と配列のサイズを一緒に管理する方法」のように配列の要素数を直接やり取りする方法が広く利用されていますが、簡潔な設計やAPIを実現したい場合や、可変長配列のようなサイズが頻繁に変化するような配列では、2つ目の「# ヌル終端による番兵を用いる方法」もよく使われます。

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

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

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

多次元配列の要素数を求める

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])で求められる
  • ただし同一スコープ内で宣言された配列に限る
  • 別スコープに配列を渡す場合は一緒に配列サイズを渡す
  • または番兵を用いた配列をやり取りする方法もある
広告