是否有一种类型安全的方法来获取C中数组的元素数?

在C中获取数组元素的通常方法是这样的:

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

这导致了一个整数常量表达式,这也是一个非常好的加号。

问题是它不是类型安全的: int* i; COUNTOF(i); /* compiles :( */ int* i; COUNTOF(i); /* compiles :( */ int* i; COUNTOF(i); /* compiles :( */ 。在实践中,这应该很少出现,但为了正确起见,这将是很好的使这种类型安全。


在C ++ 03中,这很容易(在C ++ 11中,它更容易,留给读者练习):

 template  char (&countof_detail(T (&)[N]))[N]; // not defined #define COUNTOF(arr) (sizeof(countof_detail(arr))) 

这使用模板推导来获得N ,即数组的大小,然后将其编码为类型的大小。

但在C语言中,我们没有获得该语言function。 这是我做的小框架:

 // 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)中使用答案,但为了好奇,任何明确的答案都可以。

这是我的第二个答案。 它提供了两种解决方案。

第一个解决方案需要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  #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手册 (正确)声明"Furthermore, the unused expression (exp1 or exp2 depending on the value of const_exp) may still generate syntax errors." 所以现在我已经将它设置为一个从未实例化的变量, garbage_never_defined ,所以对于一些注释掉的行而不是编译错误,我们得到编译器警告链接器错误。

例:

 #include  #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). 我们再也不关心,但这可能更好。

是否有一种类型安全的方法来获取C中数组的元素数?

我会说,不。 上面的宏很不错,但只有在传递真实数组时才能正常工作。

宏仅用于简化代码,当您需要类型安全时,不应该继续使用它们。 如果这是你需要的,你不应该使用C或坚持其规则。