carraysmacrosc-preprocessor

Array-size macro that rejects pointers


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


Solution

  • 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.