Thursday, April 16, 2020

C言語で配列の要素数を安全に数える話

C言語で配列の要素数を数えるイディオムってのがあって、
sizeof(array) / sizeof(array)
なんだけど、配列名が長くなって、たとえば
sizeof(var.that_has_an_array.as_a.member) /
    sizeof(var.that_has_an_array.as_a.member[0])
とかになるとカオス。

なので、ベンダーによっては、
#define _countof(array) (sizeof(array) / sizeof(array[0]))
みたいなマクロを提供していたりするんだけど、こうやって、何も考えずに使えるようにしていくと、配列ではなくポインタを引数に渡しちゃって、サイズ計算ミスって変な動作する懸念が増してくる。

なので、Twitterで

と聞いたところ、mattnさんから

と教えてもらったので、
#define PTLS_BUILD_ASSERT(cond) \
    ((void)sizeof(char[2 * !!(!__builtin_constant_p(cond) || (cond)) - 1]))

#define PTLS_ASSERT_IS_ARRAY(a) \
    PTLS_BUILD_ASSERT(
        __builtin_types_compatible_p(__typeof__(a[0])[], __typeof__(a)))

 #define PTLS_ELEMENTSOF(x) \
    (PTLS_ASSERT_IS_ARRAY(x), sizeof(x) / sizeof((x)[0]))
こんな感じにして取り込みました。これで、先の冗長な例も
PTLS_ELEMENTSOF(var.that_has_an_array.as_a.member)
と書くことができる。便利。

完全な変更は add PTLS_ELEMENTSOF for counting the number of elements in an array by kazuho · Pull Request #301 · h2o/picotls をご覧ください。


PS. 配列かポインタかの確認方法については、yuguiさんから下のようなコンパイラ非依存の解も教えてもらったのですが、残念ながらコンパイル時に動かすことが難しいようでした。実行時判定が必要なケースなら、この方法のほうがいいかも。