ユーニックス総合研究所

  • home
  • archives
  • c-array-elems

C言語の配列の要素数を得る方法

  • 作成日: 2020-08-06
  • 更新日: 2024-03-12
  • カテゴリ: C言語

配列の要素数を求めるには?

今回はC言語の配列の要素数を得る方法を紹介します。
これはC言語を書いているとたまーに使うので、ぜひ覚えておいてください。

結論としては↓のマクロをよく使います。

#define numof(arr) (sizeof arr / sizeof arr[0])  

このマクロは配列の要素数を求めるマクロです。
今回は具体的にこのマクロを解剖していきたいと思います。

C言語や他の言語を扱うYoutubeも公開しています。
興味がある方は以下のリンクからご覧ください。

Youtubeの当チャンネル

関連記事
C言語の配列の使い方
C言語の動的配列のリサイズ方法

目が覚めるC言語のdo-while文の使い方【ループ処理、初心者向け】
明快!C言語のcontinue文の使い方
君はまだC言語のdefineのすべてを知らない【マクロ、プリプロセス】
プログラミングのポインタをわかりやすく解説【C言語】
コードで見るC言語とC++の7つの違い

配列

まず、C言語における配列とは↓のようなものです。

int arr[] = {1, 2, 3};  

これはint型の配列arrを定義しています。
この配列には要素1, 2, 3が入っています。

この配列の要素数を求めたいケースはいろいろあります。
たとえばこの配列をfor文で回したい時です。

for (int i = 0; i < len; i++) {  
    printf("%d\n", arr[i]);  
}  

この配列を回したい時、配列の要素数を求めることが出来ればfor文をサクサク書くことができます。
こういった配列の要素数を求める方法は昔からよく知られている定石があります。
それが先ほど紹介したマクロです。

関連記事
C言語のfor文で配列を扱う方法

sizeof演算子

先ほどのマクロで使われているsizeof演算子は値のバイト数を求めるのに使われます。
たとえばint型やdouble型の変数のバイト数を求めるには↓のようにします。

int a = 0;  
printf("%d\n", sizeof a);  

double b = 0.0;  
printf("%d\n", sizeof b);  

↑の結果はたとえば↓のようになります。

4  
8  

これはint型の変数のバイト数が4バイト, double型の変数のバイト数が8バイトということです。

配列全体のバイト数を求める

このsizeof演算子は配列にも使うことが出来ます。
配列に使った場合は、その配列全体のバイト数を得ることが出来ます。

int arr[] = {1, 2, 3};  
printf("%d\n", sizeof arr);  

↑の結果は↓のようになります。

12  

int型は変数1つが4バイトでした。
配列arrにはそれが3つ分、つまり3要素分あるので、全体のバイト数は4 * 3で12バイトになるということです。

関連記事
C言語の配列の要素数を得る方法

配列の要素1つのバイト数を求める

配列の要素1つ分のバイト数を求めるには、添え字で最初の要素にアクセスして、その要素にsizeof演算子をあてます。

int arr[] = {1, 2, 3};  
printf("%d\n", sizeof arr[0]);  

↑の結果は↓のようになります。

4  

int型の変数のバイト数は4でしたが、int型の配列の要素1つ分も同じバイト数ですね。

要素数を求める数式

ここまでで配列全体のバイト数配列の要素1つ分のバイト数を得ることが出来ました。
あとは↓の数式で配列の要素数を求めることが出来ます。

配列の要素数 = 配列全体のバイト数 / 配列の要素1つ分のバイト数  

割り算ですね。
たとえば配列全体が12バイトだとします。そして要素1つ分のバイト数は4です。
配列には要素(バイト数4)が並んでいるわけですね。
その要素のバイト数を足していくと12に達しますが、その時の要素数は3つになるわけです。

{ 配列 [ 4バイト ] [ 4バイト ] [ 4バイト ] }  

これは「12バイトの中に4バイトが3つある」と言うのと同じです。
これを数式に表したのが先ほどの

配列の要素数 = 配列全体のバイト数 / 配列の要素1つ分のバイト数  

になるわけですね。

数式を使って要素数を求めてみる

先ほどのsizeof演算子と数式を使うと↓のように配列の要素数を求めることができます。

int arr[] = {1, 2, 3};  
int len = sizeof arr / sizeof arr[0];  

printf("len = %d\n", len);  

↑の結果は↓のようになります。

len = 3  

配列の要素数を求めることが出来てますね。

🦝 < やったぜ

関連記事
C言語の配列の要素数を得る方法

マクロにしちゃう

先ほどのsizeof演算子を使った式をいちいち書くのはめんどくさいのでマクロにしちゃいましょう。

#define numof(arr) (sizeof arr / sizeof arr[0])  

このnumofマクロを使うと↓のように書くことが出来ます。

int arr[] = {1, 2, 3};  
int len = numof(arr);  

printf("len = %d\n", len);  

結果は先ほどと同じです。
スッキリしてていいですね。
これはC言語で昔からよく知られているイディオムです。

関連記事
君はまだC言語のdefineのすべてを知らない【マクロ、プリプロセス】

注意事項

さて、便利なマクロですが注意事項があります。
このマクロはポインタ変数関数の引数には使えません。

例えば配列のアドレスを代入してあるポインタ変数にこのマクロを使ってみると……

int arr[] = {1, 2, 3};  
int *p = arr;  

printf("%d\n", numof(p));  

結果は↓のようになります。

2  

へんてこな数字になってますね。
これはsizeof pで求められるバイト数が、ポインタ変数のバイト数だからです。
ポインタ変数には配列のアドレスが入ってますが、あくまでsizeof演算子が求めるのはポインタ変数のバイト数です。

ポインタ変数のバイト数は私の環境では8バイトになります。
そしてsizeof p[0]で配列の要素のバイト数を求めているので、その結果は4になります。
そして8バイトを4バイトで割っているので(8 / 4)、結果は2になるわけです。

これは関数の引数も同じです。
C言語では紛らわしいのですが、引数にも配列のように[]を書くことが出来ます。
例えば↓のようにです。

void func(int arr[]) {  
}  

しかし、この時の引数arrは配列ではなくてただのポインタです
なので、この引数に対してマクロを使っても、要素数を求めることは出来ません。

void func(int arr[]) {  
    printf("%d\n", numof(arr));  
}  

int main(void) {  
    int arr[] = {1, 2, 3};  
    func(arr);  
    return 0;  
}  

↑の結果は↓のようになります。

2  

サンプルコード

では最後にサンプルコードを掲載します。
numofマクロを使ったコードです。

#include <stdio.h>  

#define numof(arr) (sizeof arr / sizeof arr[0])  

int  
main(void) {  
    int arr[] = {1, 2, 3, 4};  

    for (int i = 0; i < numof(arr); i++) {  
        printf("%d\n", arr[i]);  
    }  

    return 0;  
}  

↑のコードはnumofマクロをfor文で使っています。
配列をfor文で回す場合、配列の長さを求めてその長さだけカウント変数をインクリメントさせる必要があります。
numofマクロはこういう時にうってつけです。

↓のようにfor文の比較部分でnumofマクロを使うことで、コードをシンプルにすることができます。

for (int i = 0; i < numof(arr); i++) {  
    ...  
}  

比較してみましょう。↓のコードはnumofマクロを使わないコードです。
どうでしょうか。numofマクロを使ったほうがかっこいいですよね。

for (int i = 0; i < (sizeof arr / sizeof arr[0]); i++) {  
    ...  
}  

ちなみにこういった配列をループで回したいときは、↑のように単純に配列の長さを求める方法の他に、番兵を使った方法もあります。
番兵とは配列の末尾に設置する門番のことです。
この番兵がループしてる中で見つかったらループを終了させると、配列の先頭から末尾まで走査することができます。

#include <stdio.h>  

int  
main(void) {  
    int arr[] = {1, 2, 3, 4, -1};  

    for (int i = 0; arr[i] != -1; i++) {  
        printf("%d\n", arr[i]);  
    }  

    return 0;  
}  

↑のコードは-1の値を番兵に使っているケースです。
番兵を使えば配列の長さを求めなくてもループで配列を回すことが可能です。
この番兵を多用してるのが文字列です。
文字列にはナル文字(\0)という文字列末尾に配置する番兵があります。
文字列の走査ではこの番兵であるナル文字を参照するのがC言語では一般的な方法です。

たとえばstrlen()関数は番兵を使って文字列の長さを求めています。
そういう意味でstrlen()は配列の長さを求める処理に似ている関数と言えます。

それからポインタの配列では一般的にNULLポインタを番兵に使います。
ポインタの配列はC言語ではわりと多用するデータ構造です。

配列の要素数はどんな時に求めるか?

配列の要素数を求めるケース。
これは↓が考えられます。

  • 配列をfor文で回したい時
  • 配列の長さを取得したい時

配列をfor文で回すには配列の要素数が必要です。
ですので今回使ったnumof()マクロなどを使います。

また配列の要素にアクセスしたい時に配列の長さを使う場合もあります。
たとえば配列の一番末尾の要素を取り出す場合は

int len = numof(ary);  
int last = ary[len - 1];  

というようなコードを書くこともあります。
もっとも↑のコードは悪い見本です。
なぜかというとlen0のとき範囲外アクセスになるからです。。

配列の末尾のような配列を後ろから参照したいケースというのは意外と多いものです。
ですのでそういう時に配列の長さを求める必要があります。

自作コンテナ(自作ライブラリ)などを作れば配列と配列の長さをセットで扱って配列の長さをいちいち計算する手間を減らすということも可能です。
興味ある方は自作コンテナにチャレンジしてみてください。

配列の要素数を求めるにはマクロを使おう

まとめです。
配列の要素数を求めるには↓のようにしましょう。

  • sizeof演算子で配列全体のバイト数を求める
  • sizeof演算子で配列の要素1つ分のバイト数を求める
  • 配列全体のバイト数を要素1つ分のバイト数で割る
  • マクロを使う

今回紹介したマクロはこれです。

#define numof(arr) (sizeof arr / sizeof arr[0])  

配列の要素数を求めることが出来れば、色々なシーンで使えると思います。
ぜひこのマクロをあなたのメモ帳に追加してみてください。