16

すべてのCプログラマーは、このよく知られたマクロを使用して、配列内の要素の数を判別できます。

#define NUM_ELEMS(a) (sizeof(a)/sizeof 0[a])

典型的なユースケースは次のとおりです。

int numbers[] = {2, 3, 5, 7, 11, 13, 17, 19};
printf("%lu\n", NUM_ELEMS(numbers));          // 8, as expected

ただし、プログラマーが配列の代わりに誤ってポインターを渡すことを防ぐものは何もありません。

int * pointer = numbers;
printf("%lu\n", NUM_ELEMS(pointer));

私のシステムでは、これは2を出力します。これは、ポインターが整数の2倍の大きさであるためです。プログラマーが誤ってポインターを渡さないようにする方法を考え、解決策を見つけました。

#define NUM_ELEMS(a) (assert((void*)&(a) == (void*)(a)), (sizeof(a)/sizeof 0[a]))

これが機能するのは、配列へのポインターが最初の要素へのポインターと同じ値であるためです。代わりにポインタを渡すと、ポインタはそれ自体へのポインタと比較されますが、これはほとんどの場合falseです。(唯一の例外は、再帰的なvoidポインター、つまり、それ自体を指すvoidポインターです。私はそれと一緒に暮らすことができます。)

配列の代わりに誤ってポインタを渡すと、実行時にエラーが発生するようになりました。

Assertion `(void*)&(pointer) == (void*)(pointer)' failed.

良い!今、私はいくつかの質問があります:

  1. assertカンマ式の左オペランドとしての私の使用法は有効な標準Cですか?つまり、標準ではassert式として使用できますか?これがばかげた質問ならごめんなさい:)

  2. コンパイル時にどういうわけかチェックを行うことができますか?

  3. int b[NUM_ELEMS(a)];私のCコンパイラはそれがVLAだと思っています。そうでなければ彼を説得する方法はありますか?

  4. 私がこれを最初に考えたのですか?もしそうなら、私は何人の処女が天国で私を待っていると期待できますか?:)

4

2 に答える 2

10

カンマ式の左オペランドとしてassertを使用することは、有効な標準Cですか?つまり、標準では、assertを式として使用できますか?

はい、コンマ演算子の左側のオペランドは型の式にすることができるため、有効ですvoid。そして、assert関数はvoidその戻り型として持っています。

私のCコンパイラは、int b [NUM_ELEMS(a)];と考えています。VLAです。そうでなければ彼を説得する方法はありますか?

カンマ式の結果が定数式になることは決してないため、そう信じられます(たとえば、1、2は定数式ではありません)。

編集1:以下の更新を追加します。

コンパイル時に機能する別のバージョンのマクロがあります。

#define NUM_ELEMS(arr)                                                 \
 (sizeof (struct {int not_an_array:((void*)&(arr) == &(arr)[0]);}) * 0 \
  + sizeof (arr) / sizeof (*(arr)))

これは、静的ストレージ期間を持つオブジェクトの初期化子でも機能するようです。そしてそれはまたあなたの例で正しく動作しますint b[NUM_ELEMS(a)]

EDIT2:

@DanielFischerのコメントに対処します。上記のマクロは、以下を受け入れるという理由だけで機能gcc します。 -pedanticgcc

(void *) &arr == arr

整数定数式として、

(void *) &ptr == ptr

整数定数式ではありません。Cによると、これらは両方とも整数定数式ではなく、を使用すると-pedanticgccどちらの場合も正しく診断を発行します。

私の知る限り、このNUM_ELEMマクロを作成するための100%移植可能な方法はありません。Cには、初期化定数式(C99の6.6p7を参照)を使用したより柔軟なルールがあり、このマクロを作成するために利用できます(たとえばsizeof、複合リテラルを使用)が、ブロックスコープでは、Cは初期化子を定数式にする必要がないため、すべての場合に機能する単一のマクロを持つことが可能です。

EDIT3:

Linuxカーネルには、スパース(カーネル静的分析チェッカー)が実行されたときにそのようなチェックを実装するARRAY_SIZEマクロ(in )があることは言及する価値があると思います。include/linux/kernel.h

彼らのソリューションは移植性がなく、2つのGNU拡張機能を利用しています。

  • typeofオペレーター
  • __builtin_types_compatible_p組み込み関数

基本的には次のようになります。

#define NUM_ELEMS(arr)  \
 (sizeof(struct {int :-!!(__builtin_types_compatible_p(typeof(arr), typeof(&(arr)[0])));})  \
  + sizeof (arr) / sizeof (*(arr)))
于 2012-10-08T15:01:33.797 に答える
4
  1. はい。コンマ演算子の左の式は、常にvoid式として評価されます(C996.5.17#2)。はvoid式なのでassert()、そもそも問題ありません。
  2. 多分。Cプリプロセッサは型とキャストを認識せず、アドレスを比較できませんが、コンパイル時にsizeof()を評価する場合と同じトリックを使用できます。たとえば、次元がブール式である配列を宣言します。0の場合、それは制約違反であり、診断を発行する必要があります。私はここでそれを試しましたが、これまでのところ成功していません...多分答えは実際には「いいえ」です。
  3. いいえ。(ポインタ型の)キャストは整数定数式ではありません。
  4. おそらくそうではありません(最近の太陽の下では何も新しいことはありません)。不確定な性別の不確定な数の処女:-)
于 2012-10-08T15:00:47.790 に答える