ユーニックス総合研究所

  • home
  • archives
  • c-foreach

C言語でforeachマクロを実装する方法

  • 作成日: 2022-01-17
  • 更新日: 2023-12-25
  • カテゴリ: C言語

C言語でforeachマクロを実装する

ほかの言語ではforeach文などに相当する機能があります。
これは添え字を使わないで配列などから要素だけを取り出していく文です。
たとえばPythonなどでは↓のように書くことができます。

lis = [1, 2, 3]  

for el in lis:  
    print(el)  

C言語にこのような機能は存在するのか? というと、結論から言うとありません
ですが、foreach文を書きたい場合はマクロを使って実現することが多いです

この記事ではその方法について解説します。

関連記事

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

int型の配列用のforeachマクロを定義する

↓のような配列があるとします。

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

この配列をforeach的に回したい場合は↓のようなマクロを用意します。

#include <stdio.h>  

// int型の配列用のforeachマクロ  
#define foreach_int_array(el, ary) \  
    for (int i = 0, el = ary[0]; i < sizeof(ary) / sizeof(ary[0]); i += 1, el = ary[i])  

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

    foreach_int_array(el, ary) {  
        printf("%d\n", el);  
    }  

    return 0;  
}  

↑のコードをコンパイルして実行すると↓の結果になります。

1  
2  
3  

まずこのforeach_int_arrayマクロはfor文の初期化部分でint型の変数を2つ定義します。
ielです。
そしてelには配列の0番目の要素を入れておきます。

for文の判定部分ではiが配列の要素数以下かどうか判定します。
sizeof(ary) / sizeof(ary[0])で、ローカル変数の配列の要素数が求まります。
sizeof演算子は変数などのバイト数を求めます。
配列に使うと配列全体のバイト数が求まります。そのバイト数に配列の1要素分のバイト数を割っています。
こうするとたとえばint型(4バイト)の配列で要素数が3つある場合は、その配列全体のバイト数は4バイト * 3で12バイトになります。
要素1つが4バイトなので12バイト / 4バイト = 3で要素数3が求まります。
sizeof演算子は関数の引数の配列などには使えません。その点は注意が必要です。

for文の更新部分ではi += 1でカウント変数iをインクリメントします。
そしてel = ary[i]elの値を現在の添え字の要素で更新します。

このforeach_int_arrayマクロは一見すると問題なく機能しますが、実際には色々問題を含んでいます。
というのも、このマクロはintの配列にしか正しく機能しないからです
このマクロが上手くいっているのは、for文の初期化部分でint i = 0, el = ary[0];とやって、ielを同時に定義しているからです。

C言語のfor文では初期化部分には型は1つしか使えません。
そのため配列の要素がdoubleなどになったら、このマクロは使えなくなります。
カウント変数のiもdouble型にしなければいけないからです。

ですので、汎用的なforeachマクロを定義するにはある程度妥協する必要があります。

汎用的なforeachマクロを定義する

先ほどのマクロではforeach_int_array(el, ary)とやって、要素elをfor文側で定義していました。
汎用的なマクロを定義するにはこの点を妥協する必要があります
つまり要素ではなく添え字を定義するということです。
以下が汎用的なforeachマクロの実装例です。

#include <stdio.h>  

// 汎用的なforeachマクロ  
#define foreach_array(i, ary) \  
    for (size_t i = 0; i < sizeof(ary) / sizeof(ary[0]); i += 1)  

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

    foreach_array(i, iary) {  
        printf("%d\n", iary[i]);  
    }  

    double dary[] = {1.2, 3.4, 5.6};  

    foreach_array(i, dary) {  
        printf("%lf\n", dary[i]);  
    }  

    return 0;  
}  

上記のforeach_arrayマクロは配列の要素ではなく配列の添え字を取り出します。

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

    foreach_array(i, iary) {  
        printf("%d\n", iary[i]);  
    }  

iaryはint型の配列です。添え字であるカウント変数はiになります。
要素を参照するときはiary[i]のように参照します。

このforeach_arrayマクロはdouble型の配列でも機能します。

    double dary[] = {1.2, 3.4, 5.6};  

    foreach_array(i, dary) {  
        printf("%lf\n", dary[i]);  
    }  

マクロも配列の添え字を取り出しているだけなので、ずいぶんシンプルになっています。

マクロの注意点

さきほどのforeach_arrayマクロですが、これは関数の引数の配列に使おうとするとバグになるので注意が必要です。
つまり↓のようなコードは書けません。

void func(int ary[]) {  
    foreach_array(i, ary) {  
        printf("%d\n", ary[i]);  
    }  
}  

理由ですが、sizeofの仕様の問題です。
sizeofは関数の引数の配列は、ポインタ変数と見なします
つまりsizeof aryでは配列全体のサイズではなくポインタ変数のサイズが求まります。
この仕様の影響で、sizeof(ary) / sizeof(ary[0])という配列の要素数を求める式が使えなくなります。
よって関数の引数に対してはforeach_arrayマクロは正しく機能しません。

ポインタ配列をforeachする

NULL番兵がいるポインタ配列などは、NULL番兵をチェックすることでforeachマクロで回すことができます。

#include <stdio.h>  

// NULL番兵がいるポインタ配列を回すマクロ  
#define foreach_ptr_array(i, ary) \  
    for (size_t i = 0; ary[i]; i += 1)  

int main(void) {  
    const char *pary[] = {  
        "Cat", "Dog", "Bird", NULL,  
    };  

    foreach_ptr_array(i, pary) {  
        printf("%s\n", pary[i]);  
    }  

    return 0;  
}  

NULL番兵がいる配列であれば、このマクロは関数の引数に対しても使うことができます。

自作コンテナをforeachマクロで回す

C言語では動的配列などのライブラリを自分で作ることもあります。
そういった独自のコンテナをforeachマクロで回すこともよくあります。

#include <stdio.h>  

typedef struct {  
    size_t len;  
    const char *buf;  
} String;  

#define foreach_string(i, str) \  
    for (size_t i = 0; i < str.len; i += 1)  

int main(void) {  
    String s = {5, "Hello"};  

    foreach_string(i, s) {  
        printf("%c\n", s.buf[i]);  
    }  

    return 0;  
}  

↑の実行結果は↓になります。

H  
e  
l  
l  
o  

こういったマクロを使えば添え字の処理が抽象化されるので、バグを減らすこともできます。

おわりに

C言語のマクロによるforeachは、データ構造に合わせてそれぞれ定義する必要があります。
しかし一度定義してしまえば便利に使うことが可能です。

🦝 < foreachで楽しよう

🐭 < 添え字を取り出そう