5

C で配列の要素数を取得する通常の方法は、次のようになります。

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

これにより、整数定数式が得られます。これは、非常に優れたプラスでもあります。

問題は、タイプセーフではないことです: int* i; COUNTOF(i); /* compiles :( */. 実際には、これが発生することはめったにありませんが、正確さのために、これをタイプ セーフにすることをお勧めします。


C++03 では、これは簡単です (C++11 ではさらに簡単です。読者の演習として残します)。

template <typename T, std::size_t N>
char (&countof_detail(T (&)[N]))[N]; // not defined

#define COUNTOF(arr) (sizeof(countof_detail(arr)))

これは、テンプレート推定を使用しNて配列のサイズを取得し、それを型のサイズとしてエンコードします。

しかし、C ではその言語機能を利用できません。これは私が作った小さなフレームワークです:

// if `condition` evaluates to 0, fails to compile; otherwise results in `value`
#define STATIC_ASSERT_EXPR(condition, value) \
        (sizeof(char[(condition) ? 1 : -1]), (value))

// usual type-unsafe method
#define COUNTOF_DETAIL(arr) (sizeof(arr) / sizeof(arr[0]))

// new method:
#define COUNTOF(arr)                            \
        STATIC_ASSERT_EXPR(/* ??? */,           \
                           COUNTOF_DETAIL(arr)) \

/* ??? */望ましい動作を得るために何を入れることができますか? それともこれは不可能ですか?

MSVC(つまり、C89)で回答が機能することをさらに望んでいますが、好奇心のために、明確な回答は何でも構いません。

4

3 に答える 3

1

これが私の2番目の答えです。そしてそれは2つの解決策を与えます。

最初のソリューションにはgcc拡張が必要です。OPは、MSVCで機能する回答を好むと言っていましたが、「明確な回答であれば問題ありません」と述べました。

2番目のソリューションは、ouah https://stackoverflow.com/a/12784339/318716による優れた回答からアイデアを盗み出し、おそらくより移植性が高くなります。

古典的な定義から始めます:

#define NUMBER_naive(x) ((int)(sizeof(x) / sizeof(x)[0])) // signed is optional

最初の解決策として、gccで、式が配列に評価されるかどうかを判断するためのテストを実行できます(または、でコンパイルエラーが発生します(x)[0])。私はこのソリューションを6歳のgcc4.1.2でテストしました。

#define NUMBER(x) __builtin_choose_expr(                      \
   __builtin_types_compatible_p(typeof(x), typeof((x)[0])[]), \
   NUMBER_naive(x), garbage_never_defined)
extern void *garbage_never_defined;

2番目の解決策は次のとおりです。

#define ASSERT_zero(e) (!sizeof(struct{int:!!(e);})) // BUILD_BUG_ON_ZERO()
#define NUMBER(x) (NUMBER_naive(x) * !ASSERT_zero((void *)&(x) == (x)))

以下は、いくつかのサンプル配列とポインターに関する短いテストプログラムです。

#include <stdio.h>
#define ASSERT_zero(e) (!sizeof(struct{int:!!(e);})) // BUILD_BUG_ON_ZERO()
#define NUMBER_naive(x) ((int)(sizeof(x) / sizeof(x)[0]))
#define NUMBER(x) (NUMBER_naive(x) * !ASSERT_zero((void*)&(x) == (x)))

int a1[10];
extern int a2[];
extern int a3[10];
int *p;
int square[10][10];

static void foo(int param[10]) {
// printf("foo param    %d\n", NUMBER(param));
}
static void bar(int param[][10]) {
// printf("bar param    %d\n", NUMBER(param));
   printf("bar param[0] %d\n", NUMBER(param[0]));
   printf("bar *param   %d\n", NUMBER(*param));
}
int main(void) {
   printf("a1 %d\n", NUMBER(a1));
// printf("a2 %d\n", NUMBER(a2));
   printf("a3 %d\n", NUMBER(a3));
// printf("p  %d\n", NUMBER(p));
   printf("square  %d\n", NUMBER(square));
   printf("*square %d\n", NUMBER(*square));
   foo(a1);
   bar(square);
   return 0;
}

これは与える:

a1 10
a3 10
square  10
*square 10
bar param[0] 10
bar *param   10

ご覧のとおり、コンパイルしない、またはコンパイルしない4行、3つのポインター用に3行、不完全な配列型用に1行をコメントアウトしました。

の3番目の引数の選択に少し問題がありました__builtin_types_compatible_p()gccのマニュアルには(正しく)記載"Furthermore, the unused expression (exp1 or exp2 depending on the value of const_exp) may still generate syntax errors."されているので、今のところインスタンス化されていない変数に設定しているgarbage_never_definedので、コンパイルエラーではなく、コメントアウトされた4行について、コンパイラ警告リンカエラーが発生します。

于 2012-10-14T00:09:18.920 に答える
0

例:

#include <stdio.h>

#define IS_NOT_POINTER(x)  (sizeof(x) != sizeof 42[x])
#define COUNTOF(x)         ((int)(sizeof(x) / sizeof 42[x])) // signed is convenient
#define COUNTOF_SAFE(x)    (COUNTOF(x) / IS_NOT_POINTER(x))

extern int x[10];
extern int *y;

int main(void) {
   printf("%d\n", COUNTOF(x));
   printf("%d\n", COUNTOF(y));
   printf("%d\n", COUNTOF_SAFE(x));
   printf("%d\n", COUNTOF_SAFE(y));
   return 0;
}

これにより、gcc 4.1.2 でコンパイル時の警告が表示されます。

    foo.c:14: warning: division by zero

好奇心から、私たちが本当に気にしているわけではなく、バージョンごとに異なる可能性が高いため、実行すると次のようになります。

   10
   1
   10
   0

編集:コードをわずかに変更して、を削除しIS_NOT_POINTER(x) / IS_NOT_POINTER(x)ました。コンパイルの警告はまだ表示されていますが、実行時に正しい 3 つの値が表示され、その後に [Floating point exception (core dumped).繰り返しますが、気にしませんが、おそらくこれの方が適切です] が表示されます。

于 2012-10-12T00:33:31.647 に答える
-2

Cで配列の要素数を取得するタイプセーフな方法はありますか?

いいえ。上記のマクロは素晴らしいですが、実際の配列を渡す場合にのみ正しく機能します。

マクロはコードを単純化するためだけに存在するものであり、タイプ セーフが必要な場合はマクロを中継すべきではありません。これが必要な場合は、C を使用したり、その規則に固執したりしないでください。

于 2012-10-11T23:40:38.397 に答える