16

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:

  1. 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 :)

  2. Can the check somehow be done at compile-time?

  3. My C compiler thinks that int b[NUM_ELEMS(a)]; is a VLA. Any way to convince him otherwise?

  4. Am I the first to think of this? If so, how many virgins can I expect to be waiting for me in heaven? :)

fredoverflow
  • 256,549
  • 94
  • 388
  • 662
  • 1
    As for part (4), pretty sure that it's *not* 72. I think that's a reserved value for something else... – im so confused Oct 08 '12 at 14:52
  • 1
    Don't you mean `sizeof a[0]`? – Some programmer dude Oct 08 '12 at 14:53
  • Every real c programer knows that keeping track of the size of arrays is their problem, not the compilers and that tricks to "figure it out" are of strictly limited utility because that information is only retained in scope. If you want the compiler to take care of this for you, use a smarter language. I mean, you only need to go to c++ and use `std::vector` or (with c++11) `std::array`, so it's not a huge change. – dmckee --- ex-moderator kitten Oct 08 '12 at 14:54
  • Some platforms have `_countof()` (Mostly windows now that I think of it) which won't compile if you attempt to pass a pointer to it. – Benj Oct 08 '12 at 14:55
  • @JoachimPileborg He's being cheeky...they are the same thing. – dmckee --- ex-moderator kitten Oct 08 '12 at 14:55
  • 4
    @dmckee Actually he's being succinct ... to switch them you would need `(a)[0]` to avoid possible misparsing of the macro parameter ... though `*(a)` works fine. – Jim Balter Oct 08 '12 at 14:57
  • Note that this does not work to initialize an object with static storage duration as the result of a comma expression is never a constant expression (e..g, `1, 2` is not a constant expression). – ouah Oct 08 '12 at 14:57
  • The answer to question 1 is yes: "The assert macro puts diagnostic tests into programs; it expands to a void expression." – Daniel Fischer Oct 08 '12 at 15:00
  • @ouah `1, 2` not being a constant expression seems like a language design mistake to me. – fredoverflow Oct 08 '12 at 15:01
  • 2
    @FredOverflow see the update in my answer with a new solution – ouah Oct 08 '12 at 16:22
  • 1
    @JoachimPileborg: `a[0] == *(a + 0) == *(0 + a) == 0[a]` – Frerich Raabe Oct 12 '12 at 09:18
  • Possible duplicate of [Array-size macro that rejects pointers](https://stackoverflow.com/questions/19452971/array-size-macro-that-rejects-pointers) – T S Aug 25 '17 at 15:42

2 Answers2

10

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 function

Basically 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)))
Community
  • 1
  • 1
ouah
  • 142,963
  • 15
  • 272
  • 331
  • Unfortunately, with `-pedantic-errors`, I get `error: bit-field ‘not_an_array’ width not an integer constant expression [-pedantic]` from gcc, clang gives an error by default :( – Daniel Fischer Oct 12 '12 at 00:06
  • @DanielFischer see my second edit that addresses your comment – ouah Oct 12 '12 at 08:38
  • Loved your solution ; Porting it to IAR C99, I found a compiler surprise: "Error[Pe060]: this operator is not allowed in an integral constant expression" ; "this operator" is sizeof, and the following would not produce the error (nor the requested result): "#define ELEMENTS_NUM(arr) (sizeof (struct {int i:(1 == true);}) * 0 + sizeof (arr) / sizeof (*(arr)))" ; Any thoughts? – lkanab Sep 27 '17 at 07:28
4
  1. Yes. The left expression of a comma operator is always evaluated as a void expression (C99 6.5.17#2). Since assert() is a void expression, no problem to begin with.
  2. Maybe. While the C preprocessor doesn't know about types and casts and can't compare addresses you can use the same trick as for evaluating sizeof() at compile time, e.g. declaring an array the dimension of which is a boolean expression. When 0 it is a constraint violation and a diagnostic must be issued. I've tried it here, but so far have not been successful... maybe the answer actually is "no".
  3. No. Casts (of pointer types) are not integer constant expressions.
  4. Probably not (nothing new under the Sun these days). An indeterminate number of virgins of indeterminate sex :-)
Jens
  • 69,818
  • 15
  • 125
  • 179