未使用的通用选择中的 _Static_assert

Chr*_*ons 6 c language-lawyer c11

看起来该typeof运算符很可能会被下一个 C 标准所接受,我正在寻找是否有一种方法可以利用它来创建一个使用可移植 ISO-C 的宏,该宏可以获取传递给它的数组的长度或者如果将指针传递给它,则无法编译。通常,在使用不需要的类型时,可以使用泛型选择来强制编译器错误,方法是将其排除在泛型关联列表之外,但在这种情况下,我们需要一个默认关联来处理任何长度的数组,因此我尝试强制对我们不想要的类型的泛型关联产生编译器错误。下面是宏的示例:

#define ARRAY_SIZE(X) _Generic(&(X), \
        typeof(&X[0]) *: sizeof(struct{_Static_assert(0, "Trying to get the array length of a pointer"); int _a;}), \
        default: (sizeof(X) / sizeof(X[0])) \
)
Run Code Online (Sandbox Code Playgroud)

问题是,_Static_assert即使选择的通用关联是默认关联,也会发生故障。为了简单起见,由于当前的问题与 C23 中引入的任何内容无关,我们将制作一个测试程序,该程序显式地拒绝指向 int 的指针:

#include <stdio.h>
#include <stdlib.h>

#define ARRAY_SIZE(X) _Generic(&(X), \
        int **: sizeof(struct{_Static_assert(0, "Trying to get the array length of a pointer"); int _a;}), \
        default: (sizeof(X) / sizeof(X[0])) \
)

int main(void) {

    int x[100] = {0};
    int *y = x;
    int (*z)[100] = {&x};

    printf("length of x: %zu\n", ARRAY_SIZE(x));
    printf("length of y: %zu\n", ARRAY_SIZE(y));
    printf("length of z: %zu\n", ARRAY_SIZE(z));
    printf("length of *z: %zu\n", ARRAY_SIZE(*z));
    return EXIT_SUCCESS;

}
Run Code Online (Sandbox Code Playgroud)

使用 构建上述内容-std=c11,我发现当我期望仅在使用通用关联的指针出现问题时,_Static_assert所有扩展都会被绊倒。ARRAY_SIZEint **

根据通用选择 C11 标准 6.5.1.1 p3,

不会评估通用选择的任何其他通用关联中的任何表达式

这是 gcc 和 clang 中的错误,还是我在标准中遗漏了一些东西,会导致_Static_assert在未使用的通用关联中对此进行编译时评估?

Lun*_*din 1

您不能真正与_Static_assert应该返回值的表达式混合使用,例如类似函数的宏。你也许可以用“穷人的静态断言”来解决这个问题,就像我们在 C11 之前使用的丑陋技巧之一:

#define POOR_STATIC_ASSERT(expr) (int[expr]){0}

#define CHECK(X) _Generic((&X), \
        int **: 0,\
        default: (sizeof(X) / sizeof(X[0])) \
)

#define ARRAY_SIZE(X) ( (void)POOR_STATIC_ASSERT(CHECK(X)), CHECK(X) )
Run Code Online (Sandbox Code Playgroud)

这里调用逗号运算符以使宏CHECK返回大小或零,以防类型有效或无效。然后再次调用同一个宏,让该宏从类似函数的宏中返回ARRAY_SIZE。这将导致 ISO C 编译器出现一些神秘错误,例如“错误:ISO C 禁止零大小数组”。


下一个问题是&(X)in_Generic绝不保证可以归结为 a int**,因此该宏不安全或不可靠。不过,关于数组大小,我们可以使用一个技巧。指向无大小数组(不完整类型)的指针与相同元素类型的每个数组兼容,无论其大小如何。该宏可以重写为:

#define POOR_STATIC_ASSERT(expr) (int[expr]){0}

#define CHECK(X) _Generic((&X),              \
        int (*)[]: sizeof(X) / sizeof(X[0]), \
        default: 0)

#define ARRAY_SIZE(X) ( (void)POOR_STATIC_ASSERT(CHECK(X)), CHECK(X) )
Run Code Online (Sandbox Code Playgroud)

这对于任何数组都有效,int无论大小如何,但对于其他数组则失败。