69

よく教えられる標準的な配列サイズ マクロは次のとおりです。

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

または同等のフォーメーション。ただし、この種のことは、ポインターが渡されると暗黙のうちに成功し、不思議なことにバラバラになるまで、実行時にもっともらしい結果が得られます。

この間違いを犯しやすい: ローカル配列変数を持つ関数がリファクタリングされ、配列操作の一部が、配列をパラメーターとして呼び出される新しい関数に移されます。

ARRAYSIZEしたがって、問題は次のとおりです。できればコンパイル時に、C でマクロの誤用を検出するための「衛生的な」マクロはありますか? C++ では、配列引数のみに特化したテンプレートを使用します。C では、配列とポインタを区別する何らかの方法が必要になるようです。(たとえば、配列を拒否したい場合は、(arr=arr, ...)配列の割り当てが違法であるため、単に行います)。

4

9 に答える 9

47

ARRAY_SIZELinux カーネルは、この問題に対処するためにの優れた実装を使用します。

#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]) + __must_be_array(arr))

#define __must_be_array(a) BUILD_BUG_ON_ZERO(__same_type((a), &(a)[0]))

#define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))

もちろん、これは GNU C でのみ移植可能です 。これは、typeof演算子と__builtin_types_compatible_p関数の 2 つの組み込み関数を使用するためです。BUILD_BUG_ON_ZEROまた、GNU C でのみ有効な「有名な」マクロを使用します。

コンパイル時の評価要件 (私たちが望んでいること) を想定すると、このマクロの移植可能な実装はわかりません。

「セミポータブル」実装 (すべてのケースをカバーするわけではありません) は次のとおりです。

#define ARRAY_SIZE(arr)  \
    (sizeof(arr) / sizeof((arr)[0]) + STATIC_EXP(IS_ARRAY(arr)))

#define IS_ARRAY(arr)  ((void*)&(arr) == &(arr)[0])
#define STATIC_EXP(e)  \
    (0 * sizeof (struct { int ARRAY_SIZE_FAILED:(2 * (e) - 1);}))

これによりgcc、引数が配列の場合は警告が表示されません-std=c99 -Wall-pedantic、警告が表示されます。その理由はIS_ARRAY、式が整数定数式ではなく (ポインター型へのキャストと添字演算子は整数定数式では許可されていません)、ビットフィールド幅にSTATIC_EXPは整数定数式が必要であるためです。

于 2013-10-18T17:01:34.263 に答える
20

このバージョンのは、 がポインターの場合はARRAYSIZE()戻り、純粋な配列の場合はサイズを返します。0arr

#include <stdio.h>

#define IS_INDEXABLE(arg) (sizeof(arg[0]))
#define IS_ARRAY(arg) (IS_INDEXABLE(arg) && (((void *) &arg) == ((void *) arg)))
#define ARRAYSIZE(arr) (IS_ARRAY(arr) ? (sizeof(arr) / sizeof(arr[0])) : 0)

int main(void)
{
    int a[5];
    int *b = a;
    int n = 10;
    int c[n]; /* a VLA */

    printf("%zu\n", ARRAYSIZE(a));
    printf("%zu\n", ARRAYSIZE(b));
    printf("%zu\n", ARRAYSIZE(c));
    return 0;
}

出力:

5
0
10

Ben Jackson が指摘したように、実行時例外を強制することができます (0 で割る)

#define IS_INDEXABLE(arg) (sizeof(arg[0]))
#define IS_ARRAY(arg) (IS_INDEXABLE(arg) && (((void *) &arg) == ((void *) arg)))
#define ARRAYSIZE(arr) (sizeof(arr) / (IS_ARRAY(arr) ? sizeof(arr[0]) : 0))

残念ながら、コンパイル時にエラーを発生させることはできません (arg実行時に のアドレスを比較する必要があります)。

于 2013-10-18T15:50:57.493 に答える
6

型パラメーターの代わりに typeof を使用した bluss の回答の変更:

#define ARRAY_SIZE(A) \
    _Generic(&(A), \
    typeof((A)[0]) **: (void)0, \
    default: sizeof(A) / sizeof((A)[0]))
于 2015-02-14T17:15:05.870 に答える
6

C11 では、 を使用して配列とポインターを区別できます_Genericが、要素の型を指定した場合にのみ、それを行う方法を見つけました。

#define ARRAY_SIZE(A, T) \
    _Generic(&(A), \
            T **: (void)0, \
            default: _Generic(&(A)[0], T *: sizeof(A) / sizeof((A)[0])))


int a[2];
printf("%zu\n", ARRAY_SIZE(a, int));

マクロは以下をチェックします: 1) A へのポインターがポインターからポインターではない。2) point-to-elem は、point-to-T です。(void)0ポインターで静的に評価され、失敗します。

これは不完全な答えですが、おそらく読者はそれを改善して、その型パラメーターを取り除くことができます!

于 2014-05-13T12:31:03.633 に答える
1

typeof extensionに依存する別のものを次に示します。

#define ARRAYSIZE(arr) ({typeof (arr) arr ## _is_a_pointer __attribute__((unused)) = {}; \
                         sizeof(arr) / sizeof(arr[0]);})

これは、同一のオブジェクトを設定しようとし、配列指定の初期化子で初期化することによって機能します。配列が渡された場合、コンパイラは満足します。ポインターが渡された場合、コンパイラーは次のように文句を言います。

arraysize.c: In function 'main':
arraysize.c:11: error: array index in non-array initializer
arraysize.c:11: error: (near initialization for 'p_is_a_pointer')
于 2013-10-18T15:40:37.553 に答える
0

はい、ひどいですが、それは機能し、移植可能です。

#define ARRAYSIZE(arr) ((sizeof(arr) != sizeof(&arr[0])) ? \
                       (sizeof(arr)/sizeof(*arr)) : \
                       -1+0*fprintf(stderr, "\n\n** pointer in ARRAYSIZE at line %d !! **\n\n", __LINE__))

これはコンパイル時に何も検出しませんが、エラーメッセージを出力して、それがポインタであるか、配列の長さが 1 である場合に返しますstderr-1

==>デモ<==

于 2013-10-18T15:53:48.340 に答える