Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Validate an argument is ARRAY type in c/c++ pre processing macro on compile time [duplicate]

Is there any way to validate on compile time in a c macro that an argument is an array ?

e.g in this two macros:

#define CLEAN_ARRAY(arr) \
    do { \
        bzero(arr, sizeof(arr)); \
    } while (0)

And

#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))

I tried something using CTC(X) macro , but couldn't find any way to validate/warn if arr isn't an array.

like image 960
0x90 Avatar asked May 28 '13 14:05

0x90


3 Answers

Here's a solution in pure C which invokes no undefined behavior:

#define IS_INDEXABLE(arg) (sizeof(arg[0]))
#define IS_ARRAY(arg) (IS_INDEXABLE(arg) && (((void *) &arg) == ((void *) arg)))

If you need to ensure that the value is an array (then cause a compile time error if not), you can simply use it as an initializer to an enum statement (or a static variable), like this:

static int __ ## arg ## _is_array = IS_ARRAY(arg); // works for an array, fails for pointer.

I'm not entirely sure what will happen with VLA's, but playing around a bit should find that answer out rather fast.


Old answers:

Since this is tagged C (and GCC), I will attempt a solution here:

#define IS_ARRAY(arg) __builtin_choose_expr(__builtin_types_compatible_p(typeof(arg[0]) [], typeof(arg)), 1, 0)

Another solution, using C11's _Generic feature & typeof:

#define IS_ARRAY(arg) _Generic((arg),\
    typeof(arg[0]) *: 0,\
    typeof(arg[0]) [sizeof(arg) / sizeof(arg[0])]: 1\
)

Basically, all it does is use some fancy features of GCC to determine if the type of the argument is compatible with an array of the type of the argument's elements. It will return 0 or 1, and you could replace the 0 with something that creates a compile time error if you wish.

like image 68
Richard J. Ross III Avatar answered Sep 29 '22 06:09

Richard J. Ross III


A pure C99 solution:

enum { must_be_an_array_1 = ((void *) &(arr)) == ((void *) (arr)) };
typedef char must_be_an_array_2[((void *) &(arr)) == ((void *) (arr)) ? 1 : -1];

This exploits the fact that the address of an array is the same as the address of its first member, and that an enum member must be an integral constant. If the compiler is smart enough to tell that a pointer-to-a-pointer has a distinct address, it will choke on the second statement.

We still need the first statement because otherwise a compiler with support for runtime sized arrays (e.g. gcc 4.7) will perform the address comparison at runtime and invoke undefined behaviour as the size of the runtime array is negative (e.g. under gcc the program segfaults).

Full program:

#include <strings.h>

#define CLEAN_ARRAY(arr) \
    do { \
        enum { must_be_an_array_1 = ((void *) &(arr)) == ((void *) (arr)) }; \
        typedef char must_be_an_array_2[((void *) &(arr)) == ((void *) (arr)) ? 1 : -1]; \
        bzero(arr, sizeof(arr)); \
    } while (0)

int main() {
    int arr[5];
    CLEAN_ARRAY(arr);
    int *ptr;
    CLEAN_ARRAY(ptr);  // error: enumerator value for ‘must_be_an_array’ is not an integer constant
    return 0;
}
like image 29
ecatmur Avatar answered Sep 29 '22 07:09

ecatmur


How to validate in c macro the argument is of ARRAY type

Use std::is_array inside of the macro. Or forget themacro and just use std::is_array.

Concerning ARRAY_SIZE,

constexpr size_t size(T const (&)[N])
{
  return N;
}
like image 45
juanchopanza Avatar answered Sep 29 '22 07:09

juanchopanza