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 use assert
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? :)
To count the elements in an array that match a condition: Use the filter() method to iterate over the array. On each iteration, check if the condition is met. Access the length property on the array to get the number of elements that match the condition.
Is my usage of assert as the left operand of the comma expression valid standard C? That is, does the standard allow me to use assert as an expression?
Yes, it is valid as the left operand of the comma operator can be an expression of type void
. And assert
function has void
as its return type.
My C compiler thinks that int b[NUM_ELEMS(a)]; is a VLA. Any way to convince him otherwise?
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:
#define NUM_ELEMS(arr) \
(sizeof (struct {int not_an_array:((void*)&(arr) == &(arr)[0]);}) * 0 \
+ sizeof (arr) / sizeof (*(arr)))
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 because gcc
accepts :
(void *) &arr == arr
as an integer constant expression, while it considers
(void *) &ptr == ptr
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 with sizeof
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 (in include/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:
#define NUM_ELEMS(arr) \
(sizeof(struct {int :-!!(__builtin_types_compatible_p(typeof(arr), typeof(&(arr)[0])));}) \
+ sizeof (arr) / sizeof (*(arr)))
assert()
is a void expression, no problem to begin with.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With