5

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.

GManNickG
  • 494,350
  • 52
  • 494
  • 543
  • 1
    If you want type safety, perhaps C isn't the language for you. :P – cHao Oct 11 '12 at 22:45
  • 1
    @cHao: It isn't the language for me. :) – GManNickG Oct 11 '12 at 22:47
  • I can only think of a way of doing the opposite, checking if its a pointer. – Keith Nicholas Oct 11 '12 at 23:03
  • just in case it helps someone think of a way to do the same the other way around ... #define COUNTOF(arr) (arr=arr)?(sizeof(arr) / sizeof(arr[0])) : 0 – Keith Nicholas Oct 11 '12 at 23:05
  • Distinguishing between arrays and pointers isn't enough. For array declarations which don't specify a length (nothing between square brackets), COUNTOFF cannot work, too. – Sebastian Oct 11 '12 at 23:14
  • 1
    @Sebastian "The sizeof operator shall not be applied to an expression that has function type or an incomplete type, ...". That would require a diagnostic, most probably it will be a compilation failure. But if you declare and initialize an array as `int arr[] = {0,2,3};`, it will work, and should, the type of `arr` is `int[3]`. – Daniel Fischer Oct 11 '12 at 23:59
  • For gcc, you could use the `__builtin_types_compatible_p` and `typeof`, use them as `STATIC_ASSERT_EXPR(__builtin_types_compatible_p(typeof(arr), typeof(&(arr)[0])))`, I don't think there's a portable solution though. – nos Oct 11 '12 at 23:59
  • @DanielFischer: Ah, thanks for finding that, my searching failed. Indeed a dup. – GManNickG Oct 12 '12 at 00:27

3 Answers3

1

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.

Community
  • 1
  • 1
Joseph Quinsey
  • 9,553
  • 10
  • 54
  • 77
  • Reviewing the comments, I see that nos had already suggested using gcc's `__builtin_types_compatible_p()`. But he has an extra `&` in his comment; I haven't tested it yet. – Joseph Quinsey Oct 14 '12 at 02:26
  • Is `(void*)&(x) == (x)` a constant expression? MSVC is complaining that the value for `!!(e)` needs to be a constant expression (rightly so obviously) and failing to compile. I see that `(void*)&(x) == (x)` could be known at compile-time in principle, but I think maybe it is not required. – GManNickG Oct 16 '12 at 22:25
  • 1
    To self-answer my comment, it is not. I'm confident there is no general solution, as the duplicate question also indicates. However, I'm accepting this as a good approximation. Thanks. – GManNickG Jan 08 '13 at 02:41
  • **Edit**: In the last paragraph, "the third arg of `__builtin_types_compatible_p()`" should of course read "the third arg of `__builtin_choose_expr()`". – Joseph Quinsey Feb 14 '14 at 19:24
0

Example:

#include <stdio.h>

#define IS_NOT_POINTER(x)  (sizeof(x) != sizeof 42[x])
#define COUNTOF(x)         ((int)(sizeof(x) / sizeof 42[x])) // signed is convenient
#define COUNTOF_SAFE(x)    (COUNTOF(x) / IS_NOT_POINTER(x))

extern int x[10];
extern int *y;

int main(void) {
   printf("%d\n", COUNTOF(x));
   printf("%d\n", COUNTOF(y));
   printf("%d\n", COUNTOF_SAFE(x));
   printf("%d\n", COUNTOF_SAFE(y));
   return 0;
}

This gives, with gcc 4.1.2, a compile-time warning:

    foo.c:14: warning: division by zero

Out of curiosity, not that we really care, and is likely to be different from version to version, running it gives:

   10
   1
   10
   0

Edit: I made a slight change to the code, removing IS_NOT_POINTER(x) / IS_NOT_POINTER(x). The compile warning is still there, but now at run-time it gives the correct three values followed by Floating point exception (core dumped). Again, we don't care, but this is probably better.

Joseph Quinsey
  • 9,553
  • 10
  • 54
  • 77
  • No warning here, and output is 10, 2, 10, 2. I get a warning for a `long*`, though, as expected, and two for `long x[1]; long *y;`. – Daniel Fischer Oct 12 '12 at 00:44
  • @DanielFischer: I need to rename COUNTOF to COUNTOF_UNSAFE, and then rename COUNTOF_SAFE to just COUNTOF. Then you could use COUNTOF_UNSAFE for your one-element array `long x[1]`. But I don't understand where your two 2's are coming from. – Joseph Quinsey Oct 12 '12 at 00:51
  • 1
    Eight byte pointers. So with `int *y`, `sizeof(y) / sizeof 42[y] = sizeof(int*) / sizeof(int) = 8/4 = 2`. `sizeof(x) != sizeof 42[x]` yields 0 for all arrays of length 1, and 1 for all `T*` with `sizeof(T*) != sizeof(T)`, which is the majority of types, regardless of what size your pointers have. – Daniel Fischer Oct 12 '12 at 00:55
  • What does `sizeof(42[x])` do? – antak Oct 12 '12 at 01:07
  • @DanielFischer: Thank you--I'm still stuck in the 32-bit world. So my answer needs some tweaking. And I haven't yet tested it with MSVC. – Joseph Quinsey Oct 12 '12 at 01:09
  • @antak It's a sneaky way to do `sizeof *(x + 42)` or `sizeof (x)[42]`. It's kind of reasonable here because `x` is a macro argument, and thus `42[x]` saves the parentheses necessary for `(x)[42]`. – Daniel Fischer Oct 12 '12 at 01:12
  • @antak: If you google `42`, the first entry (for me) is http://en.wikipedia.org/wiki/42_%28number%29. But the expression `42[x]` is a bit of an obfuscation: I maybe should have written `(x)[42]`. – Joseph Quinsey Oct 12 '12 at 01:15
  • This fails if the array has a size of one. :( I had a solution that had the same issue as well. – GManNickG Oct 12 '12 at 01:24
  • @GManNickG: Of course. That is why you need a `COUNTOF_UNSAFE` version: if used, the programmer is taking a solemn vow that the argument is not a pointer, but could possibly be an array of size 1. – Joseph Quinsey Oct 12 '12 at 01:27
  • @JosephQuinsey: Surely it defeats the point of calling the macro safe if it's not safe to use in general. – GManNickG Oct 12 '12 at 01:37
  • @JosephQuinsey Cheers. Didn't know the two parts could be swapped. – antak Oct 12 '12 at 01:41
  • @GManNickG: Your question is very good, and inspired a change to my own version of `COUNTOF.` But with respect to safety, I am only talking about `compiler warnings` here, *not* the run-time behavior. So if your colleague in the next cubicle happens to need an array of constant length 1, fine, just use `COUNTOF_UNSAFE.` But, judging from your question, I think you may agree that your original `COUNTOF` is much, ***much***, better than the hundreds of `#define MY_ARRAY_LEN 123` in a typical project. (But sometimes the `#define` is better: e.g. zip-code length). – Joseph Quinsey Oct 12 '12 at 02:00
  • @JosephQuinsey: The point is the guy in the next cubicle is going to go "hm, I need the count of elements, oh yeah, the `COUNTOF` macro"; why would he decide he wants the unsafe version of presumably the same macro? SAFE to me means it works in generally, safely. If it doesn't work in general over the entire domain, then it doesn't deserve a regular macro name, let alone safe! – GManNickG Oct 12 '12 at 02:17
  • Perhaps nothing "work[s] in general over the entire domain". For example, `int x; ...; x << 1;` may, or may not, be UB. The programmer here is making a ***vow*** that the second msb of `x` is not set (whether he/she realizes it or not). The use of a macro with `UNSAFE` as part of its name seems safer. – Joseph Quinsey Oct 12 '12 at 02:28
-2

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

I'd say, no. The macro above is nice but it only works correctly when passing a real array.

Macros are only there to simplify your code, you shouldn't relay on them when you want type-safety. If this is what you need, you shouldn't be using C or stick to its rules.

Pablo
  • 13,271
  • 4
  • 39
  • 59
  • 1
    There are times when these sort of "principled" answers make sense, but this isn't one of them. There's no principled reason why I can't try to both use macros to simplify code yet do it in a safer-than-naive way. – GManNickG Oct 12 '12 at 01:24
  • 1
    And tell how am I supposed to know that this time isn't one of them? My answer is still true: there is no way to be 100% type-safe when you use macros unless you use some compiler extension that allows you to test the type of the variable. – Pablo Oct 12 '12 at 13:16