Every C programmer can determine the number of elements in an array with this well-known macro:
#define NUM_ELEMS(a) (sizeof(a)/sizeof 0[a])
Here is a typical use case:
int numbers[] = {2, 3, 5, 7, 11, 13, 17, 19};
printf("%lu\n", NUM_ELEMS(numbers)); // 8, as expected
However, nothing prevents the programmer from accidentally passing a pointer instead of an array:
int * pointer = numbers;
printf("%lu\n", NUM_ELEMS(pointer));
On my system, this prints 2, because apparently, a pointer is twice as large as an integer. I thought about how to prevent the programmer from passing a pointer by mistake, and I found a solution:
#define NUM_ELEMS(a) (assert((void*)&(a) == (void*)(a)), (sizeof(a)/sizeof 0[a]))
This works because a pointer to an array has the same value as a pointer to its first element. If you pass a pointer instead, the pointer will be compared with a pointer to itself, which is almost always false. (The only exception is a recursive void pointer, that is, a void pointer that points to itself. I can live with that.)
Accidentally passing a pointer instead of an array now triggers an error at runtime:
Assertion `(void*)&(pointer) == (void*)(pointer)' failed.
Nice! Now I have a couple of questions:
Is my usage of
assert
as the left operand of the comma expression valid standard C? That is, does the standard allow me to useassert
as an expression? Sorry if this is a dumb question :)Can the check somehow be done at compile-time?
My C compiler thinks that
int b[NUM_ELEMS(a)];
is a VLA. Any way to convince him otherwise?Am I the first to think of this? If so, how many virgins can I expect to be waiting for me in heaven? :)
assert()
is a void expression, no problem to begin with.Yes, it is valid as the left operand of the comma operator can be an expression of type
void
. Andassert
function hasvoid
as its return type.It believes so because the result of a comma expression is never a constant expression (e..g, 1, 2 is not a constant expression).
EDIT1: add the update below.
I have another version of your macro which works at compile time:
and which seems to work even also with initializer for object with static storage duration. And it also work correctly with your example of
int b[NUM_ELEMS(a)]
EDIT2:
to address @DanielFischer comment. The macro above works with
gcc
without-pedantic
only becausegcc
accepts :as an integer constant expression, while it considers
is not an integer constant expression. According to C they are both not integer constant expressions and with
-pedantic
,gcc
correctly issues a diagnostic in both cases.To my knowledge there is no 100% portable way to write this
NUM_ELEM
macro. C has more flexible rules with initializer constant expressions (see 6.6p7 in C99) which could be exploited to write this macro (for example withsizeof
and compound literals) but at block-scope C does not require initializers to be constant expressions so it will not be possible to have a single macro which works in all cases.EDIT3:
I think it is worth mentioning that the Linux kernel has an
ARRAY_SIZE
macro (ininclude/linux/kernel.h
) that implements such a check when sparse (the kernel static analysis checker) is executed.Their solution is not portable and make use of two GNU extensions:
typeof
operator__builtin_types_compatible_p
builtin functionBasically it looks like something like that: