One commonly used macro in C programming is ASIZE()
, generally defined as something like this
#define ASIZE(a) (sizeof(a)/sizeof(a[0]))
and used to calculate the number of elements in an array.
The main problem with this macro, as written, is that it doesn’t distinguish between arrays and pointers. If passed a pointer, it will silently produce wrong results:
Code
#include <stdio.h> #define ASIZE(a) (sizeof (a) / sizeof((a)[0])) int main(void) { short a[3]; short *b; int c[2]; int *d; long long e[5][4]; char *f[4]; char (*g)[4]; (void)a; (void)b; (void)c; (void)d; (void)e; (void)f; (void)g; printf("ASIZE() accepts pointers, producing invalid results.\n"); printf("%zu\n", ASIZE( a )); printf("%zu\n", ASIZE( b )); printf("%zu\n", ASIZE( c )); printf("%zu\n", ASIZE( d )); printf("%zu\n", ASIZE( e )); printf("%zu\n", ASIZE( f )); printf("%zu\n", ASIZE( g )); return 0; }
3 2 2 1 5 4 1
By adding a new macro checking if the parameter is an array, we can define a safer ASIZE()
:
#define CHECK_ARRAY(a) ((void)(0&&((int (*)(__typeof__(a[0])(*)[ASIZE(a)]))NULL)(&(a)))) #define ASIZE_SAFE(a) (CHECK_ARRAY(a), ASIZE(a))
Checking this new version, we see it gets the correct results when passed arrays, but now the compilation fails when applied to pointers:
Code
#include <stdio.h> #define ASIZE(a) (sizeof (a) / sizeof((a)[0])) #define CHECK_ARRAY(a) ((void)(0&&((int (*)(__typeof__(a[0])(*)[ASIZE(a)]))NULL)(&(a)))) #define ASIZE_SAFE(a) (CHECK_ARRAY(a), ASIZE(a)) int main(void) { short a[3]; short *b; int c[2]; int *d; long long e[5][4]; char *f[4]; char (*g)[4]; (void)a; (void)b; (void)c; (void)d; (void)e; (void)f; (void)g; printf("ASIZE() accepts pointers, producing invalid results.\n"); printf("%zu\n", ASIZE( a )); printf("%zu\n", ASIZE( b )); printf("%zu\n", ASIZE( c )); printf("%zu\n", ASIZE( d )); printf("%zu\n", ASIZE( e )); printf("%zu\n", ASIZE( f )); printf("%zu\n", ASIZE( g )); printf("ASIZE_SAFE() only accepts arrays (try uncommenting).\n"); printf("%zu\n", ASIZE_SAFE( a )); //printf("%zu\n", ASIZE_SAFE( b )); printf("%zu\n", ASIZE_SAFE( c )); //printf("%zu\n", ASIZE_SAFE( d )); printf("%zu\n", ASIZE_SAFE( e )); //printf("%zu\n", ASIZE_SAFE( f )); //printf("%zu\n", ASIZE_SAFE( g )); return 0; }
ASIZE() accepts pointers, producing invalid results. 3 2 2 1 5 4 1 ASIZE_SAFE() only accepts arrays (try uncommenting). 3 2 5
It works in a relatively straightforward way, though I have put the details in a gist to avoid spoiling them.