11

A classic macro to compute the number of elements in an array is this:

#define countof(a)  (sizeof(a) / sizeof(*(a)))

The problem with this is it fails silently if the argument is a pointer instead of an array. Is there a portable way to ensure this macro is only used with an actual array by generating a compile time error if a is not an array?

EDIT: my question seems to be a duplicate of this one: Array-size macro that rejects pointers

chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • 2
    Compile-time assert that sizeof(&table[0]) != sizeof(table[0]) and you'll be there in most cases, definitely when multi-member structs are involved. The sizeof a pointer is also the the sizeof of a pointer to a pointer. For structs this can only be true for a very simple struct. Not perfect, but rather close to your goal in most cases. And portable. – B. Nadolson Jun 19 '17 at 03:00
  • @B.Nadolson While it may work for some specific cases, I would probably put a lot of comments around where you use it (as per principle of least surprise). There are many cases where that check does not work correctly but a programmer might assume it does. – tangrs Jun 19 '17 at 04:08
  • 2
    The property `(intptr_t)&table == (intptr_t)table` is valid for an array, in case of pointer it holds only if it points to itself, which is rare. – Marian Jun 19 '17 at 08:31
  • @Marian: that's a very good find. I'm afraid it will not evaluate to false at compile time but generate code instead. This solution can be used as an `assert` but not a static assertion. – chqrlie Jun 19 '17 at 10:09
  • Possible duplicate of [Validate an argument is ARRAY type in c/c++ pre processing macro on compile time](https://stackoverflow.com/questions/16794900/validate-an-argument-is-array-type-in-c-c-pre-processing-macro-on-compile-time) – phuclv Nov 10 '18 at 12:08
  • @phuclv: indeed, my question seems to be a duplicate and the question you refer to is itself a duplicate of this one: https://stackoverflow.com/questions/19452971/array-size-macro-that-rejects-pointers which baffles me since the latter was posted 5 months after the former. – chqrlie Nov 10 '18 at 21:43
  • Possible duplicate of [Array-size macro that rejects pointers](https://stackoverflow.com/questions/19452971/array-size-macro-that-rejects-pointers) – bolov Nov 10 '18 at 21:58
  • @chqrlie don't be too serious about time. [*The general rule is to keep the question with the best collection of answers, and close the other one as a duplicate*](https://meta.stackexchange.com/q/10841/230282) – phuclv Nov 10 '18 at 22:55

3 Answers3

6

Using a non-portable built-in function, here is a macro to perform a static assertion that a is an array:

#define assert_array(a) \
     (sizeof(char[1 - 2 * __builtin_types_compatible_p(typeof(a), typeof(&(a)[0]))]) - 1)

It works with both gcc and clang. I use it to make the countof() macro safer:

#define countof(a)  (sizeof(a) / sizeof(*(a)) + assert_array(a))

But I don't have a portable solution for this problem.

chqrlie
  • 131,814
  • 10
  • 121
  • 189
0

In C11 you could use _Static_assert in conjunction with _Generic, but you'll also need to provide type info, which I see as a good thing as it provides extra granularity; you get the ability to assert based on element type, as well as whether it's an array or not from _Generic, and you get a nice friendly message from _Static_assert... For example:

assert_array_type.c:6:33: error: static assertion failed: "expected array of int; got (char[42]){0}"
assert_array_type.c:6:33: error: static assertion failed: "expected array of int; got (int *){0}"

These errors are produced by the following testcase, depending upon how you compile:

#define array_type(a, T) _Generic(a, T *:  _Generic(&a, T **:    0 \
                                                      , default: 1)\
                                                      , default: 0)

#define assert_array_type(a, T) _Static_assert(array_type(a, T), "expected array of " #T "; got " #a)

int main(void) {
    assert_array_type((int [42]){0}, int); // this should pass
#   if defined(TEST_POINTER_FAIL)
    assert_array_type((int  *  ){0}, int); // this should fail
#   endif
#   if defined(TEST_ELEMENT_FAIL)
    assert_array_type((char[42]){0}, int); // this should fail
#   endif
}

The two testcases can observed by defining TEST_POINTER_FAIL and/or TEST_ELEMENT_FAIL, i.e.

  • cc -std=c11 -D'TEST_POINTER_FAIL' should cause an assertion failure at compilation time due to the fact that a pointer is passed, rather than an array.
  • cc -std=c11 -D'TEST_ELEMENT_FAIL' should cause an assertion failure at compilation time due to the fact that the array is meant to be an array of int, rather than an array of char.
autistic
  • 1
  • 3
  • 35
  • 80
  • 1
    Interesting idea, but I do not want to have to specify the type of the array element. This goal is to write a generic macro `countof(a)` that computes the number of elements of `a` if `a` is an array of any possible type and fails if `a` is not an array. – chqrlie Aug 26 '18 at 09:43
  • 1
    The question is pretty simple: *Is there a portable way to ensure this macro is only used with an actual array?* Your answer, which I upvoted, gives an interesting direction using the C11 `_Generic` construction but not a solution to the original problem. – chqrlie Aug 26 '18 at 09:55
  • Okay, fair enough, I get that... that's the reason you have the "tick of approval", right? So that you can mark an answer that you accept as solving your problem... To be clear, I racked my head about how to remove the type argument, but I doubt there's a *generic* way. However, there's nothing to say that other visitors might not find a way to remove the type argument per their own requirements... – autistic Aug 26 '18 at 10:08
  • I guess I should offer a bounty for this question. Btw, I wonder why anybody would downvote your answer... – chqrlie Aug 26 '18 at 18:02
0

AFAIK, to make it generic in >=C11, you only need __typeof as a nonstandard extension:

#define STATICALLY_ENFORCE_TYPES_NOT_COMPATIBLE(X,Y) \
    sizeof((char){_Generic((__typeof(X)*){0}, \
                  __typeof(__typeof(Y)*):(void)0,default:1)})

#define ARRAY_SIZEOF(X) \
   ((!STATICALLY_ENFORCE_TYPES_NOT_COMPATIBLE(X, &(X)[0]))?0:sizeof(X)) 

#define countof(X) (ARRAY_SIZEOF(X)/sizeof(*(X)))
Petr Skocik
  • 58,047
  • 6
  • 95
  • 142
  • 1
    I wish `__typeof`, `({ })`, and `__label__` (+ `goto *&&label;` and/or bit-twiddling and overflow-checking builtins) got standardized. They've been around long enough and they make can make C a truly worthy opponent to C++'s generic programming + RAII. But the C committee don't seem to like standardizing existing stuff. They'd rather standardize something awkward and unnecessary and untested. :D – Petr Skocik Nov 10 '18 at 21:59