77

The standard array-size macro that is often taught is

#define ARRAYSIZE(arr) (sizeof(arr) / sizeof(arr[0]))

or some equivalent formation. However, this kind of thing silently succeeds when a pointer is passed in, and gives results that can seem plausible at runtime until things mysteriously fall apart.

It's all-too-easy to make this mistake: a function that has a local array variable is refactored, moving a bit of array manipulation into a new function called with the array as a parameter.

So, the question is: is there a "sanitary" macro to detect misuse of the ARRAYSIZE macro in C, preferably at compile-time? In C++ we'd just use a template specialized for array arguments only; in C, it seems we'll need some way to distinguish arrays and pointers. (If I wanted to reject arrays, for instance, I'd just do e.g. (arr=arr, ...) because array assignment is illegal).

M.M
  • 138,810
  • 21
  • 208
  • 365
nneonneo
  • 171,345
  • 36
  • 312
  • 383
  • 1
    This is going to be rough, as arrays decay into pointers in virtually all contexts. –  Oct 18 '13 at 15:13
  • 1
    Why would anyone be in need of such a macro? This only works with arrays that have been defined by a fixed size in the code, why would you need to calculate what you know you wrote? If the answer is "maybe you are in another part of your code and you don't have this info anymore" my subsequent question is: How is that possible with the array not decaying to a pointer, in a non-weird non-specificly-designed-to-make-this-happen piece of code? – Eregrith Oct 18 '13 at 15:18
  • 1
    @Eregrith:it also works with VLAs, variable-length arrays. But that's a nitpick. It 'only works with arrays where the array definition is in scope' is closer to accurate, but not very dissimilar from what you said. – Jonathan Leffler Oct 18 '13 at 15:43
  • 7
    @Eregrith By extension that point of view may as well be "why would anyone need any kind of compile-time calculation or metaprogramming, ever"? The idea that "you know what you wrote" is both *ridiculous* and useless. No law says you had to write it by hand in the first place. – Alex Celeste Oct 18 '13 at 15:48
  • VLAs are allocated with a given size parameter anyway, and following C99 section _6.19 Arrays of Variable Length_ you know that outside the scope of the declaring function they are deallocated. That said, you could easily pass along the size-defining parameter. – Eregrith Oct 18 '13 at 15:50
  • @Leushenko I'm sorry but compile-time calculation should occur on _un-previously-known_ data. Would you really write `char a[100];` and then couple lines under this `printf("My array is of length %d\n", ARRAYZISE(a));`? Would you really? On the other hand you can't guess what, for example, a user inputted size would be. – Eregrith Oct 18 '13 at 15:52
  • @Leushenko And the idea that "you know what you wrote" is not useless at all. Why would you need to recalculate everything everytime? `#define ARRAYSIZE 100` `char arr[ARRAYSIZE];` and roll with it! – Eregrith Oct 18 '13 at 15:54
  • 7
    @Eregrith I would see absolutely nothing wrong with writing `char a[MAGIC_STUFF(COMPLICATED(X, Z+FOO(G)))];` and not wanting to type that out again lower down. If the information is there and the toolset is there, use it. – Alex Celeste Oct 18 '13 at 15:55
  • @Leushenko Well in that case okay, thank you. – Eregrith Oct 18 '13 at 15:56
  • 3
    @Eregrith: At least two situatons come to mind: (1) The array size might not be specified, but might be inferred from the initlialization list; (2) It may be useful to have a macro like `#define SEND_FIXED_COMMAND(cmd) send_command((arr), sizeof (arr))` so as to avoid having to specify both the name of the array and the name of a constant giving the array's size. – supercat Jun 01 '15 at 06:17
  • Possilbe duplicate: [stackoverflow.com/questions/12849714/is-there-a-type-safe-way-of-getting-an-element-count-for-arrays-in-c](https://stackoverflow.com/questions/12849714/is-there-a-type-safe-way-of-getting-an-element-count-for-arrays-in-c) Related: [stackoverflow.com/questions/16794900/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/) – T S Aug 25 '17 at 14:33
  • Another possible duplicate: [stackoverflow.com/questions/12784136/reliably-determine-the-number-of-elements-in-an-array?noredirect=1&lq=1](https://stackoverflow.com/questions/12784136/reliably-determine-the-number-of-elements-in-an-array?noredirect=1&lq=1) – T S Aug 25 '17 at 15:25
  • 1
    arrays of size 1 are rare, hence `#define likely_array(arr) ((sizeof(arr) / sizeof(arr[0]) != 1)` might work – phuclv Nov 10 '18 at 12:10
  • GCC 8 warns with `-Wall` if this is used with a pointer – alx - recommends codidact Aug 18 '19 at 10:18

10 Answers10

54

Linux kernel uses a nice implementation of ARRAY_SIZE to deal with this issue:

#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]) + __must_be_array(arr))

with

#define __must_be_array(a) BUILD_BUG_ON_ZERO(__same_type((a), &(a)[0]))

and

#define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))

Of course this is portable only in GNU C as it makes use of two instrinsics: typeof operator and __builtin_types_compatible_p function. Also it uses their "famous" BUILD_BUG_ON_ZERO macro which is only valid in GNU C.

Assuming a compile time evaluation requirement (which is what we want), I don't know any portable implementation of this macro.

A "semi-portable" implementation (and which would not cover all cases) is:

#define ARRAY_SIZE(arr)  \
    (sizeof(arr) / sizeof((arr)[0]) + STATIC_EXP(IS_ARRAY(arr)))

with

#define IS_ARRAY(arr)  ((void*)&(arr) == &(arr)[0])
#define STATIC_EXP(e)  \
    (0 * sizeof (struct { int ARRAY_SIZE_FAILED:(2 * (e) - 1);}))

With gcc this gives no warning if argument is an array in -std=c99 -Wall but -pedantic would gives a warning. The reason is IS_ARRAY expression is not an integer constant expression (cast to pointer types and subscript operator are not allowed in integer constant expressions) and the bit-field width in STATIC_EXP requires an integer constant expression.

ouah
  • 142,963
  • 15
  • 272
  • 331
21

This version of ARRAYSIZE() returns 0 when arr is a pointer and the size when its a pure array

#include <stdio.h>

#define IS_INDEXABLE(arg) (sizeof(arg[0]))
#define IS_ARRAY(arg) (IS_INDEXABLE(arg) && (((void *) &arg) == ((void *) arg)))
#define ARRAYSIZE(arr) (IS_ARRAY(arr) ? (sizeof(arr) / sizeof(arr[0])) : 0)

int main(void)
{
    int a[5];
    int *b = a;
    int n = 10;
    int c[n]; /* a VLA */

    printf("%zu\n", ARRAYSIZE(a));
    printf("%zu\n", ARRAYSIZE(b));
    printf("%zu\n", ARRAYSIZE(c));
    return 0;
}

Output:

5
0
10

As pointed out by Ben Jackson, you can force a run-time exception (dividing by 0)

#define IS_INDEXABLE(arg) (sizeof(arg[0]))
#define IS_ARRAY(arg) (IS_INDEXABLE(arg) && (((void *) &arg) == ((void *) arg)))
#define ARRAYSIZE(arr) (sizeof(arr) / (IS_ARRAY(arr) ? sizeof(arr[0]) : 0))

Sadly, you can't force a compile-time error (the address of arg must be compared at run-time)

David Ranieri
  • 39,972
  • 7
  • 52
  • 94
  • 1
    Better yet would be if you could get a compile time error (divide by 0?) in the bad case. – Ben Jackson Oct 18 '13 at 15:58
  • What is the need for `IS_INDEXABLE(arg)`? As far as I can tell, this always returns non-zero – Digital Trauma Oct 18 '13 at 17:23
  • 3
    @DigitalTrauma, Because it raises an error when the argument is not an array (or a pointer). `error: subscripted value is neither array nor pointer nor vector` – David Ranieri Oct 18 '13 at 17:31
  • 1
    @AlterMann - Thanks - yes, thats a nice a extra test to put in – Digital Trauma Oct 18 '13 at 17:36
  • What strange black magic is this? I'm completely befuddled by `((void*)&arg) == ((void*)arg)`. In what context does anything equal its address? And how does that indicate an array? What am I missing? – Jeremy West Oct 19 '13 at 04:31
  • 1
    An array :) The address of the array will be the same as the the value of the array since the value decays into the pointer to the first element. – tangrs Oct 19 '13 at 04:55
  • Well, you learn something new every day. In retrospect, it should have been obvious that a statically allocated array variable's address is the address of the array. – Jeremy West Oct 19 '13 at 17:53
  • I think it is better to return 1 instead of 0 for `ARRAYSIZE` on pointer. It can hold at least 1 element. `#define ARRAYSIZE(arr) (sizeof(arr) / (IS_ARRAY(arr) ? sizeof(arr[0]) : 1))` – i486 Mar 08 '18 at 12:05
  • @DigitalTrauma I also think that IS_INDEXABLE is superfluous since only an array satisfies the second condition `(((void *) &arg) == ((void *) arg)))` anyway (aside from the pathological situation that an integral type contains its own address). – Peter - Reinstate Monica Nov 14 '19 at 11:14
7

With C11, we can differentiate arrays and pointers using _Generic, but I have only found a way to do it if you supply the element type:

#define ARRAY_SIZE(A, T) \
    _Generic(&(A), \
            T **: (void)0, \
            default: _Generic(&(A)[0], T *: sizeof(A) / sizeof((A)[0])))


int a[2];
printf("%zu\n", ARRAY_SIZE(a, int));

The macro checks: 1) pointer-to-A is not pointer-to-pointer. 2) pointer-to-elem is pointer-to-T. It evaluates to (void)0 and fails statically with pointers.

It's an imperfect answer, but maybe a reader can improve upon it and get rid of that type parameter!

bluss
  • 12,472
  • 1
  • 49
  • 48
  • Instead of checking that "pointer-to-A is not pointer-to-pointer", why not directly check, that pointer-to-A is pointer-to-array? `_Generic(&(A), T(*)[sizeof(A) / sizeof((A)[0])]: sizeof(A) / sizeof((A)[0]))` This makes the second test unnecessary and I think the error message `error: '_Generic' selector of type 'int **' is not compatible with any association` is better understandable than `error: invalid use of void expression`. Sadly I still have no idea how to get rid of that type parameter. :-( – T S Mar 16 '21 at 20:50
  • If you can pass the element type, then it's actually quite easy. `#define ARRAYSIZE(arr, T) _Generic(&(arr), T(*)[sizeof(arr)/sizeof(arr[0])]: sizeof(arr)/sizeof(arr[0]))` This creates an array pointer to an array of the specified type. If the passed parameter is not an array of the correct type or size, you'll get compiler errors. 100% portable standard C. – Lundin Jul 01 '21 at 13:57
6

Modification of bluss's answer using typeof instead of a type parameter:

#define ARRAY_SIZE(A) \
    _Generic(&(A), \
    typeof((A)[0]) **: (void)0, \
    default: sizeof(A) / sizeof((A)[0]))
4566976
  • 2,419
  • 1
  • 10
  • 14
  • 1
    `typeof` is a GCC extension, so this code only works in GCC. If you are specific to GCC, then you could much better use something based on `__builtin_types_compatible_p` or similar - that works in any case where this code would work, but it would additionally work in older versions of GCC or if an older standard was specified via the `-std=` option. – T S Mar 16 '21 at 20:19
2

Here's one possible solution using a GNU extension called statement expressions:

#define ARRAYSIZE(arr) \
    ({typedef char ARRAYSIZE_CANT_BE_USED_ON_POINTERS[sizeof(arr) == sizeof(void*) ? -1 : 1]; \
     sizeof(arr) / sizeof((arr)[0]);})

This uses a static assertion to assert that sizeof(arr) != sizeof(void*). This has an obvious limitation -- you can't use this macro on arrays whose size happens to be exactly one pointer (e.g. a 1-length array of pointers/integers, or maybe a 4-length array of bytes on a 32-bit platform). But those particular instances can be worked around easily enough.

This solution is not portable to platforms which don't support this GNU extension. In those cases, I'd recommend just using the standard macro and not worry about accidentally passing in pointers to the macro.

Community
  • 1
  • 1
Adam Rosenfield
  • 390,455
  • 97
  • 512
  • 589
1

Here's another one which relies on the typeof extension:

#define ARRAYSIZE(arr) ({typeof (arr) arr ## _is_a_pointer __attribute__((unused)) = {}; \
                         sizeof(arr) / sizeof(arr[0]);})

This works by attempting to set up an identical object and initializing it with an array designated initializer. If an array is passed, then the compiler is happy. If pointer is passed the compiler complains with:

arraysize.c: In function 'main':
arraysize.c:11: error: array index in non-array initializer
arraysize.c:11: error: (near initialization for 'p_is_a_pointer')
Digital Trauma
  • 15,475
  • 3
  • 51
  • 83
  • This is nice! Actually, it works better if you use `= {};`: if you pass a pointer, you get "empty scalar initializer". This makes it portable to e.g. struct arrays. – nneonneo Oct 18 '13 at 16:12
  • @nneonneo - `= {};` didn't work for me :( - if I pass a simple int array, then I also get "error: empty scalar initializer". But I can pass arrays of ints, arrays of pointers or arrays of structs to the `= 0;` version without difficulty. – Digital Trauma Oct 18 '13 at 16:41
  • The `{[0] = 0}` version does produce some warnings, however, about missing braces if you have an array of arrays or array of structs. – Adam Rosenfield Oct 18 '13 at 16:46
  • 1
    @DigitalTrauma: Sorry, I might have been confusing. The code is `#define ARRAYSIZE(arr) ({typeof(arr) arr##_is_pointer = {}; sizeof(arr)/sizeof(arr[0]);})`. No designated initializer. This works properly for both int arrays and struct arrays with no warnings. – nneonneo Oct 18 '13 at 16:49
  • 1
    @nneonneo - yes, thanks for clarifying - that makes sense - I'll update the answer, as that is clearly an improvement. – Digital Trauma Oct 18 '13 at 16:58
  • @nneonneo - Since it is explicit this uses gcc, I'm also adding ` __attribute__((unused))` to suppress "warning: unused variable 'x_is_a_pointer'" – Digital Trauma Oct 18 '13 at 17:02
  • It fails with VLA arrays – hurufu Dec 04 '17 at 00:14
  • 1
    @hurufu and what's worse: when using this on a VLA, clang doesn't generate an error for the line where this macro is used, but for the line where the VLA was declared :-( – T S Mar 18 '21 at 10:03
1

my personal favorite, tried gcc 4.6.3 and 4.9.2:

#define STR_(tokens) # tokens

#define ARRAY_SIZE(array) \
    ({ \
        _Static_assert \
        ( \
            ! __builtin_types_compatible_p(typeof(array), typeof(& array[0])), \
            "ARRAY_SIZE: " STR_(array) " [expanded from: " # array "] is not an array" \
        ); \
        sizeof(array) / sizeof((array)[0]); \
    })

/*
 * example
 */

#define not_an_array ((char const *) "not an array")

int main () {
    return ARRAY_SIZE(not_an_array);
}

compiler prints

x.c:16:12: error: static assertion failed: "ARRAY_SIZE: ((char const *) \"not an array\") [expanded from: not_an_array] is not an array"
not-a-user
  • 4,088
  • 3
  • 21
  • 37
  • 1
    Small hitch: `__builtin_types_compatible_p` version fails for an array that's behind a const pointer (because const and non-const types don't match) – M.M Jul 31 '18 at 04:03
1

One more example to the collection.

#define LENGTHOF(X) ({ \
    const size_t length = (sizeof X / (sizeof X[0] ?: 1)); \
    typeof(X[0]) (*should_be_an_array)[length] = &X; \
    length; })

Pros:

  1. It works with normal arrays, variable-length arrays, multidimensional arrays, arrays of zero sized structs
  2. It generates a compilation error (not warning) if you pass any pointer, struct or union
  3. It does not depend on any of C11's features
  4. It gives you very readable error

Cons:

  1. It depends on some of the gcc extensions: Typeof, Statement Exprs, and (if you like it) Conditionals
  2. It depends on C99 VLA feature
hurufu
  • 545
  • 7
  • 10
  • 1
    As a cons, this also creates a variable "length" that may conflict with another one in the code – Stoogy Aug 15 '18 at 08:46
  • 2
    It will not conflict because `({ ... })` notation creates new scope. The only problem is when you use it like this: `double length[234]; const size_t size = LENGTHOF(length); `. And you can always just duplicate `(sizeof X / (sizeof X[0] ?: 1))` and don't use any temporary variable at all ;) – hurufu Mar 04 '19 at 10:24
0

Awful, yes, but that works and it is portable.

#define ARRAYSIZE(arr) ((sizeof(arr) != sizeof(&arr[0])) ? \
                       (sizeof(arr)/sizeof(*arr)) : \
                       -1+0*fprintf(stderr, "\n\n** pointer in ARRAYSIZE at line %d !! **\n\n", __LINE__))

This will not detect anything at compile time but will print out an error message in stderr and return -1 if it is a pointer or if the array length is 1.

==> DEMO <==

Michael M.
  • 2,556
  • 1
  • 16
  • 26
  • 7
    This one fails for me with `int arr2[2];` on my 64-bit box. In this case `sizeof(arr)` and `sizeof(&arr[0])`c are both equal to 8 – Digital Trauma Oct 18 '13 at 17:39
  • PRO:Reports problem in the case where I use an array allocated on the heap. Even the google chromium COUNT_OF macro will return 2 in this case for an array of any size. CON: doesn't compile with pedantic warnings. – KANJICODER Mar 19 '19 at 23:19
  • `sizeof(arr) != sizeof(&arr[0])` is a bad test. 1) It's misleading: On first glance one would probably assume, that `sizeof(&arr[0])` somehow depends on `arr` when in fact it almost never does. On all platforms I've ever known it's equivalent to `sizeof(void*)`. (Do you happen to know a platform where `sizeof(int*)!=sizeof(void*)`?)    2) As noted by @DigitalTrauma this error check easily leads to false positives. Why not use `(((void *) &arg) == ((void *) arg))`? If you would change that, I could upvote - a runtime error message can at least for debug builds be very useful. – T S Mar 18 '21 at 10:22
0

with typeof in c and template matching in c++:

#ifndef __cplusplus
   /* C version */
#  define ARRAY_LEN_UNSAFE(X) (sizeof(X)/sizeof(*(X)))
#  define ARRAY_LEN(X) (ARRAY_LEN_UNSAFE(X) + 0 * sizeof((typeof(*X)(*[1])[ARRAY_LEN_UNSAFE(X)]){0} - (typeof(X)**)0))
#else
   /* C++ version */
   template <unsigned int N> class __array_len_aux    { public: template <typename T, unsigned int M> static const char (&match_only_array(T(&)[M]))[M]; };
   template <>               class __array_len_aux<0> { public: template <typename T>                 static const char (&match_only_array(T(&)))[0]; };
#  define ARRAY_LEN(X) sizeof(__array_len_aux<sizeof(X)>::match_only_array(X))
#endif

// below is the checking codes with static_assert
#include <assert.h>

void * a0[0];
void * a1[9];
void * aa0[0];
void * aa1[5][10];
void *p;
struct tt {
    char x[10];
    char *p;
} t;

static_assert(ARRAY_LEN(a0) == 0, "verify [0]");
static_assert(ARRAY_LEN(aa0) == 0, "verify [0][N]");
static_assert(ARRAY_LEN(a1) == 9, "verify [N]");
static_assert(ARRAY_LEN(aa1) == 5, "verify [N][M]");
static_assert(ARRAY_LEN(t.x) == 10, "verify array in struct");
//static_assert(ARRAY_LEN(p) == 0, "should parse error");
//static_assert(ARRAY_LEN(t.p) == 0, "should parse error");```

This `ARRAY_LEN` accepts any dim array, and also accepts 0-size arrays, but rejects a pointer and 0-size array.
James Z.M. Gao
  • 516
  • 1
  • 8
  • 13