A proper way to check size of an array in C
21 / 12 / 2023

Everyone learing C, learn to check the amount of elements in the array using sizeof like that:

int array[1337];
printf("%zu\n", sizeof(array) / sizeof(array[0]));

/* prints: 1337 */

This takes the total size of an object in bytes and divides it by size of a single element. This works great, unless the array changes the type from a builtin array into pointer. The code still compiles, but computes the wrong thing, which might lead to some hard to spot bugs.

int array_[1337];
int *array = array_;
printf("%zu\n", sizeof(array) / sizeof(array[0]));

/* prints: 2 */

The right thing to do is to check if the object, we are checking the size of is an builtin array. That's exactly what Linux kernel does, so we can check out their tricks :

#include <stdio.h>

#define BUILD_BUG_ON_ZERO(expr) ((int)(sizeof(struct { int:(-!!(expr)); })))
#define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))
#define ARRAY_SIZE(arr) \
        (BUILD_BUG_ON_ZERO(__same_type((arr), &(arr)[0])), (sizeof(arr) / sizeof((arr)[0])))

int main(void)
{
        int tab[1337];
        int *x = tab;

        printf("%zu\n", ARRAY_SIZE(tab));
        printf("%zu\n", ARRAY_SIZE(x));

        return 0;
}

I leave the exploration on how and why BUILD_BUG_ON_ZERO and __same_type work to the reader, but the above code works better than the previous approach, because It would give us an error if we pass a pointer instead of an array to the ARRAY_SIZE macro.

main.c:3:54: error: negative width in bit-field ‘’
    3 | #define BUILD_BUG_ON_ZERO(expr) ((int)(sizeof(struct { int:(-!!(expr)); })))
      |                                                      ^
main.c:6:10: note: in expansion of macro ‘BUILD_BUG_ON_ZERO’
    6 |         (BUILD_BUG_ON_ZERO(__same_type((arr), &(arr)[0])), (sizeof(arr) / sizeof((arr)[0])))
      |          ^~~~~~~~~~~~~~~~~
main.c:14:25: note: in expansion of macro ‘ARRAY_SIZE’
   14 |         printf("%zu\n", ARRAY_SIZE(x));
      |                         ^~~~~~~~~~

I checked it on Clang 16 and GCC 13 and works on both.