通过以下方式获取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)中使用答案,但为了好奇,任何明确的答案都可以。
答案 0 :(得分:1)
这是我的第二个答案。它提供了两种解决方案。
第一个解决方案需要gcc扩展; OP确实说他更喜欢在MSVC中有效的答案,但是“任何明确的答案都可以”。
第二种解决方案从ouah https://stackoverflow.com/a/12784339/318716的优秀答案中窃取了想法,并且可能更具便携性。
我们从经典的定义开始:
#define NUMBER_naive(x) ((int)(sizeof(x) / sizeof(x)[0])) // signed is optional
对于第一个解决方案,在gcc中,您可以进行测试以确定是否有任何表达式求值为数组(或者它在(x)[0]
处给出编译错误);我用6岁的gcc 4.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;
第二个解决方案是:
#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
正如您所看到的,我已经注释掉了四行不会或不应该编译的行,三行用于三个指针,一行用于不完整的数组类型。
我在选择__builtin_types_compatible_p()
的第三个arg时遇到了一些问题。 gcc manual(正确)状态"Furthermore, the unused expression (exp1 or exp2 depending on the value of const_exp) may still generate syntax errors."
所以现在我将它设置为一个从未实例化的变量garbage_never_defined
,所以对于一些注释掉的行,而不是编译错误,我们得到编译器警告和链接器错误。
答案 1 :(得分: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)
。编译警告仍然存在,但现在在运行时它会提供正确的三个值,然后是Floating point exception (core dumped).
再次,我们不关心,但这可能更好。
答案 2 :(得分:-2)
是否有一种类型安全的方法来获取C中数组的元素数?
我会说,不。上面的宏很不错,但只有在传递真实数组时才能正常工作。
宏仅用于简化代码,当您需要类型安全时,不应该继续使用它们。如果这是你需要的,你不应该使用C或坚持其规则。