Is there a type-safe way of getting an element count for arrays in C? [duplicate]

ε祈祈猫儿з 提交于 2019-11-29 11:11:31
Joseph Quinsey

This is my second answer. And it gives two solutions.

The first solution requires a gcc extension; the OP did say the he'd prefer answers which work in MSVC, but that "any definite answer will do".

The second solution steals ideas from the excellent answer by ouah https://stackoverflow.com/a/12784339/318716, and is probably more portable.

We start with the classic define:

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

For the first solution, in gcc, you can do a test to determine whether any expression evaluates to an array (or it gives a compile error at (x)[0]); I've tested this solution with the 6-year-old 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;

The second solution is:

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

The following is a short test program, on some sample arrays and pointers:

#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;
}

This gives:

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

As you can see, I've commented out four lines which would not or should not compile, three for the three pointers, and one for the incomplete array type.

I had a slight problem with the choice of the third arg of __builtin_types_compatible_p(). The gcc manual (correctly) states "Furthermore, the unused expression (exp1 or exp2 depending on the value of const_exp) may still generate syntax errors." So for now I've set it to a never-instantiated variable, garbage_never_defined, so for some the four commented-out lines, rather than a compile error, we get a compiler warning and a linker error.

Example:

#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;
}

This gives, with gcc 4.1.2, a compile-time warning:

    foo.c:14: warning: division by zero

Out of curiosity, not that we really care, and is likely to be different from version to version, running it gives:

   10
   1
   10
   0

Edit: I made a slight change to the code, removing IS_NOT_POINTER(x) / IS_NOT_POINTER(x). The compile warning is still there, but now at run-time it gives the correct three values followed by Floating point exception (core dumped). Again, we don't care, but this is probably better.

Is there a type-safe way of getting an element count for arrays in C?

I'd say, no. The macro above is nice but it only works correctly when passing a real array.

Macros are only there to simplify your code, you shouldn't relay on them when you want type-safety. If this is what you need, you shouldn't be using C or stick to its rules.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!