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

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

本文关键字:验证 参数 类型 ARRAY 预处理 编译 c++      更新时间:2023-10-16

是否有任何方法可以在编译时在c宏中验证参数是数组?

e。

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

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

我尝试使用CTC(X)宏,但无法找到任何方法来验证/警告,如果arr不是一个数组。

这里有一个纯C的解决方案,它不会调用未定义行为:

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

如果需要确保值是数组(否则会导致编译时错误),可以简单地将其用作enum语句(或静态变量)的初始化项,如下所示:

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替换为创建编译时错误的内容。

纯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下程序segfaults)。

完整的计划:

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

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

在宏内部使用std::is_array。或者忘记宏,直接使用std::is_array

关于ARRAY_SIZE

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

根据我的评论:

如果类型不是"可索引"类型,

sizeof((x)[0])将给出错误-但是,如果它恰好是指针,它将很高兴接受这一点。并且,如果x类型有一个operator[]

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

(对于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清除指针。: -)

__pragma是用来抑制L4警告的。

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

& lt;编辑>
找到拒绝指针的数组大小宏
原始答案如下:
& lt;/编辑>

我使用的宏是:
#define ASSERT_EXPR(condition,return_value) 
(((char(*)[(condition)?1:-1])0)?(return_value):(return_value))

原则:
0被强制转换为指向数组的指针(大小为1(条件为真)或- 1(条件为假,产生错误))。然后将该空指针用作三元操作符的条件。虽然我们知道,它总是只计算第三个操作数(空指针意味着false),但第二个操作数也是return_value——这样,结果类型与return_value的类型相同。

使用这个,(和IS_ARRAY从理查德J.罗斯三世的回答)我可以定义我的安全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)的错误…

在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数组)。