【问题标题】:Compile-time assertion to determine if pointer is an array编译时断言以确定指针是否为数组
【发布时间】:2019-05-18 18:43:22
【问题描述】:

目前,我有以下代码块来进行安全的字符串复制(它有效):

#define STRCPY(dst, src) do { assert(((void*)(dst)) == ((void*) & (dst))); \
                              strlcpy(dst, src, sizeof(dst)); } while (0)

所以它接受如下结构:

const char *src = "hello";
char dest[5];
STRCPY(dest, src); //hell

并否认以下内容:

void (char *dst) {
    STRCPY(dst, "heaven"); //unknown size of dst
}

问题是代码块创建了一个断言。有没有办法对编译时间进行检查?

所以我希望在编译时出现错误(如creating an array with negative size),而不是在可能的情况下崩溃代码。

【问题讨论】:

  • sizeof *dst 在您的情况下是未知的,那么您期望什么?并且检查 is 在编译时完成。您的意思是:有没有办法在运行时间执行此检查?
  • 如果指针通过(不是数组)则编译错误
  • edit你的问题,并把所有的澄清那里
  • 这不是重复的。 OP想知道宏的参数是指针还是不是编译时间

标签: c assertion


【解决方案1】:

如果标准 C 可用,那么您可以这样做:

#define STRCPY(dst, src)                                        \
  _Generic(&(dst),                                              \
           char(*)[sizeof(dst)]: strlcpy(dst,src,sizeof(dst)) )

解释:

您不能在数组类型上使用_Generic 表达式,因为它不是不受“数组衰减”规则(C17 6.3.2.1 §3)豁免的特殊情况之一。因此,在我的示例中简单地使用_Generic((dst), ...dst 在计算表达式时最终会成为char*,然后我们将丢失其原始类型的信息。

但是,如果我们使用& 获取数组的地址,我们确实会利用其中一种特殊情况,并且不会发生数组衰减。相反,我们最终得到了一个数组指针,这意味着_Generic 必须检查预期类型和大小的数组指针:char(*)[sizeof(dst)]


作为旁注/安全考虑,我从不使用 do-while(0) 宏并劝阻它们,但那是另一回事了。

【讨论】:

  • do-while(0) 有什么问题?我对那个故事很感兴趣:)
  • @Michael 我写了一个关于它的小故事here
  • 谢谢。因此 '{ ...}' (没有 do-while )有助于强制执行“始终使用大括号”的编码风格。 (但仍有一些极端情况,如“anatolyg”所指出的嵌套“if-else”
【解决方案2】:

对于dst 是否为数组的编译时断言, 如果 C11 可用,我会使用 @Lundin 解决方案(或 _Static_assert)。

否则,我会使用以下内容:

#define BUILD_BUG_ON_NON_ARRAY_TYPE(e) (sizeof(struct { int:-!!(((void*)(e)) != ((void*) & (e))); }))

这基本上采用您的编译时评估表达式((void*)(dst)) == ((void*) & (dst)),但不是在运行时使用它assert,而是以编译时断言方式使用它。

所以,总的来说,我会将您的宏更改为:

#define STRCPY(dst, src) do { BUILD_BUG_ON_NON_ARRAY_TYPE(dst); \
                              strlcpy(dst, src, sizeof(dst)); } while (0)

【讨论】:

    【解决方案3】:

    我发现我的帖子因重复而被关闭了一段时间,并关注了提到的the link。 该解决方案仅适用于 GCC,但对我来说很好,因为我也在 GCC 上进行了夜间构建。所以代码的初稿是:

    #if defined(__GNUC__)
    #define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); }))
    #define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))
    #define __must_be_array(a) BUILD_BUG_ON_ZERO(__same_type((a), &(a)[0]))
    #define STRCPY(dst,src) do{__must_be_array(dst);strlcpy(dst, src, sizeof(dst));}while(0)
    #else
    #define STRCPY(dst,src) do{strlcpy(dst, src, sizeof(dst));}while(0)
    #endif
    

    【讨论】:

    • 如果可以接受非标准 gcc,您应该在问题中提及这一点,并使用 gcc 标签对其进行标记。无论如何,自 C11 以来,不再需要像这样的非标准技巧。另外,来自 _Generic 的编译器错误(“char** 类型的选择器与任何关联都不兼容”)比“无法声明负大小的位域”或该宏归结为的任何内容更具可读性。
    【解决方案4】:

    如果你想检查它,我能想到的唯一方法是:

    assert((sizeof(dst)) != (sizeof(void*)));
    

    但只有当数组的大小与 OPs 系统上的指针大小不同时它才有效

    【讨论】:

    • 如果在数组作为指针传递的范围内检查这将失败,这通常是有问题的情况。
    • @AlexLop。这不是这里的情况。定义中没有像 passing 这样的东西 - 只是替换。,
    • 但是您的建议并不能回答 OP 问题。这里的问题是dst 是函数内部的一个参数,它是一个指针。所以你的断言总是会失败。关键是没有办法检查指针是否为数组。
    • @AlexLop。但他问的是宏定义而不是函数。这是完全不同的东西。
    • 我认为您误解了这个问题。 OP 特别指出了他的宏不起作用的情况。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多