nneonneo
nneonneo

Reputation: 179552

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

Upvotes: 80

Views: 21505

Answers (12)

Leo
Leo

Reputation: 827

Yes another possibly futile attempt to make peace with array function arguments decaying to pointers (now using C23 generics):

#define countof_is_pointer(p) _Generic(&(p), typeof(*p)**: 1, default: 0)
#define countof_sizeof_array_element(a) (!countof_is_pointer(a) ? sizeof((a)[0]) : 0)
#define countof(a) (sizeof(a) / countof_sizeof_array_element(a))

static void foo(int a[2], int* p) {
    (void)p; (void)a;
    int b[3];
    int *r = b;
    int i = 0; (void)i;
    struct { int x, y; } s = {0, 0}; (void)s;
    printf("countof(b) = %zd\n", countof(b));
    // any line below will generate compiler warning or error
//  printf("countof(a) = %zd\n", countof(a));
//  printf("countof(p) = %zd\n", countof(p));
//  printf("countof(r) = %zd\n", countof(r));
//  printf("countof(i) = %zd\n", countof(i));
//  printf("countof(s) = %zd\n", countof(s));
}

int main(void) {
    int a[2];
    int* p = a;
    foo(a, p);
}

Why? Because every year at least once I make the same mistake using countof() like inside a function.

I am aware that division by zero is undefined by C language specification but in all practicality because countof() is evaluated as a constant expression at compile time it should result in error or at least warning.

https://godbolt.org/z/3M8Wvqsse

PS: Did not test with VLA yet.

Upvotes: 0

Leo
Leo

Reputation: 827

Late for the party. But even in mid-2024 Microsoft`s cl.exe (19.40.33811) still does not (even in C17 mode):

  1. does not implement typeof()
  2. does not compile ((void*)&(a) == &(a)[0]) as constant expression
  3. does not compile sizeof(struct { int:-1; }) at all

If it any help to anyone, probably the most trivial solution below at least generates "Integer division by zero" exception at runtime.

    #define count_of(a) (sizeof(a) / sizeof(a[1]) + (1 - 1 / ((void*)&(a) == &(a)[0])))
    int a[5];    
    int *b = a;
    printf("%d\n", (int)count_of(a));
    printf("%d\n", (int)count_of(b));

which probably works the same with gcc/clang/cl and may work with other compilers.

But a bit more portable way would be:

     #define count_of(a) (sizeof((a)) / sizeof((a)[1]) + \
             (((void*)&(a) == &(a)[0]) ? 0 : (size_t)raise(SIGSEGV)))

Upvotes: 1

James Z.M. Gao
James Z.M. Gao

Reputation: 646

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.

Upvotes: 1

hurufu
hurufu

Reputation: 553

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

Upvotes: 1

not-a-user
not-a-user

Reputation: 4327

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"

Upvotes: 1

4566976
4566976

Reputation: 2499

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

Upvotes: 6

David Ranieri
David Ranieri

Reputation: 41026

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)

Upvotes: 21

bluss
bluss

Reputation: 13772

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!

Upvotes: 7

ouah
ouah

Reputation: 145899

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.

Upvotes: 56

Digital Trauma
Digital Trauma

Reputation: 16016

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

Upvotes: 1

Michael M.
Michael M.

Reputation: 2584

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 <==

Upvotes: 0

Adam Rosenfield
Adam Rosenfield

Reputation: 400454

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.

Upvotes: 2

Related Questions