在编译时在c / c ++预处理宏中验证参数是ARRAY类型

时间:2013-05-28 14:40:57

标签: c++ c gcc compiler-errors

有没有办法在c宏的编译时验证一个参数是一个数组?

例如在这两个宏中:

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

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

我尝试了使用CTC(X) macro 的内容,但找不到任何方法来验证/警告arr是否不是数组。

7 个答案:

答案 0 :(得分:11)

这是纯C中的一个解决方案,它不会调用任何未定义的行为:

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

如果你需要确保该值是一个数组(如果没有,则导致编译时错误),你可以简单地将它用作枚举语句(或静态变量)的初始化器,如下所示:

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

我不完全确定VLA会发生什么,但玩一下​​应该会很快找到答案。


旧答案:

由于这是标记为C(和GCC),我将在此处尝试解决方案:

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

另一种解决方案,使用C11的_Generic功能& typeof

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

基本上,它所做的只是使用GCC的一些奇特的功能来确定参数的类型是否与参数元素类型的数组兼容。它将返回0或1,如果您愿意,可以用创建编译时错误的内容替换0。

答案 1 :(得分:6)

纯C99解决方案:

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

这利用了一个事实,即数组的地址与其第一个成员的地址相同,并且枚举成员必须是一个整数常量。如果编译器足够聪明,可以告诉指向指针的指针具有不同的地址,那么它将会阻塞第二个语句。

我们仍然需要第一个语句,因为否则支持运行时大小的数组(例如gcc 4.7)的编译器将在运行时执行地址比较并调用未定义的行为,因为运行时数组的大小为负(例如,在gcc下的程序)段错误)。

完整计划:

#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;
}

答案 2 :(得分:4)

  

如何在c宏中验证参数是ARRAY类型

在宏内使用std::is_array。或忘记themacro并使用std::is_array

关于ARRAY_SIZE

constexpr size_t size(T const (&)[N])
{
  return N;
}

答案 3 :(得分:2)

据我所知,还没有人提供一种方法来确保ARRAY_SIZE的参数实际上是一个数组。

编辑:
刚刚找到Array-size macro that rejects pointers

答案 4 :(得分:1)

根据我的评论:

如果类型不是“可索引”类型,

sizeof((x)[0])将给出错误 - 但是,如果它恰好是指针,它将很乐意接受它。此外,如果operator[]的类型有x

C很难做到这一点,但C ++可能允许一些模板类型的解决方案(我实际上并不知道如何做到这一点,因为我从来没有尝试过这样做,或者与模板类似的东西)。

答案 5 :(得分:0)

(对于C ++)我在VS2010中的当前清晰宏是:

#define CLEAR(v)    do { __pragma(warning(suppress: 4127 4836)) typedef std::remove_reference< decltype(v)>::type T; static_assert( std::is_pod<T>::value || (__has_trivial_constructor(T) && __has_trivial_destructor(T)), "must not CLEAR a non-POD!" ); static_assert( !std::is_pointer<T>::value, "pointer passed to CLEAR!" );  memset(&(v), 0, sizeof(v)); } while(0)

您可以使用type_traits标题中的内容来弥补您的变体。

带有解释的格式化验证:

#define CLEAR(v) \
do { \ 
    __pragma(warning(suppress: 4127 4836)) \
    typedef std::remove_reference< decltype(v)>::type T; \
    static_assert( \
        std::is_pod<T>::value \
        || (__has_trivial_constructor(T) && __has_trivial_destructor(T)), \
        "must not CLEAR a non-POD!" ); \
     static_assert( !std::is_pointer<T>::value, "pointer passed to CLEAR!" ); \
     memset(&(v), 0, sizeof(v)); \
  } while(0)

外部do-while使其可以在所有地方使用,如if / else。

需要Remove_reference,因此它适用于左值,单独的decltype使得int *和int *&amp;不同的和is_pointer报告后者是假的。

is_pod检查适用于一般情况,附加条件允许结构A1:A;案例工作,其中A是POD,A1只添加更多POD成员。对于is_pod目的而言,它是错误的,但要清除它也是有道理的。

当你在指针上得到间接错误或者在混乱中传递struct的地址时,

is_pointer check会检查预期的错误类型。请使用= NULL清除指针。 ; - )

__pragma用于抑制否则发出的L4警告。

答案 6 :(得分:-3)

在C中,这应该有效:

#define VALIDATE_ARRAY(arr) (void)(sizeof((arr)[0]))

int *a, b;
int main() {
        VALIDATE_ARRAY(a);
        VALIDATE_ARRAY(b);
        return 0;
}

此程序无法编译,因为b不是数组。这是因为b[0]无效。它不会区分指针和数组 - 我认为你不能这样做。

这种形式的宏只能在函数内部使用。如果要在函数外部使用它,则必须对其进行修改(例如,声明extern数组)。