6

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.

4

7 回答 7

12

这是纯 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 替换为会产生编译时错误的内容。

于 2013-05-28T15:13:08.847 回答
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;
}
于 2013-05-28T15:22:23.890 回答
4

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

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

关于ARRAY_SIZE,

constexpr size_t size(T const (&)[N])
{
  return N;
}
于 2013-05-28T14:50:38.843 回答
1

(对于 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*& 不同,并且 is_pointer 报告后者为 false。

is_pod 检查适用于一般情况,附加条件允许 struct A1 : A; A 是 POD 且 A1 仅添加更多 POD 成员的案例工作。出于 is_pod 的目的,它是错误的,但清除它具有同样的意义。

当您在指针上获得错误的间接或混淆传递结构的地址时,is_pointer 检查会保护预期的错误类型。请使用 = NULL 清除指针。;-)

用于抑制以其他方式发出的__pragmaL4 警告。

于 2013-05-28T14:58:38.693 回答
1

根据我的评论:

sizeof((x)[0])如果该类型不是“可索引”类型,则会给出错误 - 但是,如果它恰好是一个指针,它会很高兴地接受它。而且,如果有一个operator[]for 的类型x

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

于 2013-05-28T15:10:33.783 回答
1

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

<Edit>
找到拒绝指针的数组大小的宏
原始答案如下:
</Edit>

我用于此的宏是:

#define ASSERT_EXPR(condition,return_value) \
(((char(*)[(condition)?1:-1])0)?(return_value):(return_value))

原理:
0转换为指向数组的指针(大小为一(条件为真)或减一(条件为假,产生错误))。这个空指针然后用作三元运算符的条件。虽然我们知道,它总是只计算第三个操作数(空指针表示 false),但第二个操作数也是return_value- 这样生成的类型与 的类型相同return_value

使用它(以及IS_ARRAY来自Richard J. Ross III 的回答)我可以ARRAY_SIZE如下定义我的安全宏:

#define IS_ARRAY(arg) __builtin_choose_expr(__builtin_types_compatible_p(typeof(arg[0]) [], typeof(arg)), 1, 0)
#define ARRAY_SIZE(x) ASSERT_EXPR(IS_ARRAY(x), (sizeof(x)/sizeof((x)[0])) )

我没有设法让它与Richard J. Ross III的另外两个IS_ARRAY变体一起工作,但这可能是我(或 gcc 的)的错......

于 2017-08-24T15:48:27.913 回答
-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数组)。

于 2013-05-28T14:56:37.060 回答