Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

The usual approach to getting an array's element count in C in something like this:

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

This results in an integral-constant expression, which is a very nice plus as well.

The problem is that it isn't type-safe: int* i; COUNTOF(i); /* compiles :( */. In practice, this should come up rarely, but for the sake of correctness it would be nice to make this type-safe.


In C++03 this is easy (and in C++11 it's even easier, left as an exercise for the reader):

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

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

This uses template deduction to get N, the size of the array, then encodes that as the size of a type.

But in C we don't get that language feature. This is the small framework I've made:

// 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)) \

What can I put in /* ??? */ to get my desired behavior? Or is this impossible?

I'd further prefer answers work in MSVC (i.e., C89), but for the sake of curiosity any definite answer will do.

like image 791
GManNickG Avatar asked Nov 26 '22 10:11

GManNickG


1 Answers

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.

like image 135
Joseph Quinsey Avatar answered Dec 15 '22 14:12

Joseph Quinsey