【问题标题】:Validate an argument is ARRAY type in c/c++ pre processing macro on compile time [duplicate]在编译时验证参数是 C/C++ 预处理宏中的 ARRAY 类型[重复]
【发布时间】:2013-05-23 14:13:18
【问题描述】:

有什么方法可以在编译时验证 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 不是数组,我找不到任何验证/警告的方法。

【问题讨论】:

  • 我注意到你有 cc++ 标签。 c++ 中有一些不错的模板方法。您需要同时适用于两种语言的答案吗?
  • 如果类型不是“可索引”类型, sizeof((x)[0]) 将给出错误 - 但是,如果它恰好是指针,它会很高兴地接受它。当然,如果有operator[] 类型的x
  • 是的,但它可以是 2 种不同的方法。
  • @0x90:对于 C 和 C++ 有不同答案的问题,您应该提出单独的问题,因为您只能将一个答案标记为已接受,但对于不同的语言可能会有单独的答案。

标签: c++ c gcc compiler-errors


【解决方案1】:

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


找到Array-size macro that rejects pointers
原始答案如下:

我使用的宏是:

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

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

使用它(以及来自Richard J. Ross III's answerIS_ARRAY)我可以定义我的安全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)的错...

【讨论】:

    【解决方案2】:

    (对于 C++)我目前在 VS2010 中的 clear 宏是:

    #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 警告。

    【讨论】:

    • 这可能很棒,但目前无法阅读。请格式化!
    【解决方案3】:

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

    【讨论】:

    • 此答案中的当前第一种方法适用于实际目的,但在技术上不正确,因为它对于使用void *x = &amp;x; 定义的x 失败。
    • @EricPostpischil 在技术上是正确的:最好的正确。不幸的是,我认为没有任何办法,即使使用 restrict 指针的怪异。
    • @EricPostpischil 我找不到任何关于它的特别之处,除了表达式的类型应为T (*)[size]。我会假设在大多数实现中地址保持不变,但我没有一整天的时间来讨论标准。
    • __builtin_choose_expr(...,1,0) 相对于__builtin_types_compatible_p(...) 有什么优势?
    • @TS 我怀疑在这种情况下是否存在任何技术差异。我在 4 年前写了这个答案,但我认为我的初始解决方案(在发布之前)涉及一些基于类型检查是否成功的额外逻辑。然后我意识到额外的检查是相当多余的。
    【解决方案4】:

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

    【讨论】:

    • 我怀疑这在技术上会调用 UB。指针运算仅针对同一数组对象的下标元素定义。但除此之外,这非常整洁!
    • 我的回答很好的忍者副本:)
    • @OliCharlesworth 更新为使用==,我认为这没问题。
    • @EricPostpischil 仅仅因为它没有保证并不意味着我们不能对它做出假设。如果你想玩标准游戏,你可以整天这样做,但如果编译器做一些不同的事情,那就真的意味着杰克。
    • C11 6.6“常量表达式”中的文本似乎并没有说明具有两个地址常量的 == 运算符会生成可在初始化程序中使用的常量表达式。尽管此代码在某些情况下对我有用,但它必须在 /10 以下(即实现可能定义其他代码)。
    【解决方案5】:

    根据我的评论:

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

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

    【讨论】:

      【解决方案6】:

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

      【讨论】:

      • arr 是一个指针时,这无法提供错误。
      • 指针不是数组,数组也不是指针。
      【解决方案7】:

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

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

      关于ARRAY_SIZE

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

      【讨论】:

      • @EricPostpischil 它专门询问了一个 C 宏,它没有说任何让我认为它仅限于 C 的内容。无论如何,如果它被标记为 C++,我会给出一个C++ 答案。
      • @EricPostpischil 我相应地更新了问题。
      • @EricPostpischil 如果您检查问题上的 cmets,他们想要两者的答案,并且他们“可以是 2 种不同的方法”。所以这至少回答了一半的问题。
      猜你喜欢
      • 2013-03-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-02-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多