【问题标题】:Can c11's 'Generic' keyword be used within gcc _Static_assert可以在 gcc _Static_assert 中使用 c11 的“通用”关键字吗
【发布时间】:2019-09-19 20:10:49
【问题描述】:

我了解 C11 的“通用”用于什么用途,并且我想在静态断言中使用它来保证两个用户定义的类型(typedef)是相同的原始类型。

我制作了一个宏,将每个原始类型映射到一个枚举值,并验证它是否按预期工作。但是,当我尝试比较静态断言中两种类型的两个结果宏的相等性时,会出现编译器错误。当您注释掉静态断言时,代码会按预期工作。

在评估泛型扩展之前,编译器似乎正在评估静态断言。会是这样吗?我可以去哪里验证它的行为?

示例代码

#include <stdbool.h>
#include <stdio.h>
#include <stdint.h>


typedef enum
{
    UTIL_TYPE_ENUM_BOOL,
    UTIL_TYPE_ENUM_CHAR,
    UTIL_TYPE_ENUM_SCHAR,
    UTIL_TYPE_ENUM_UCHAR,
    UTIL_TYPE_ENUM_SHORT,
    UTIL_TYPE_ENUM_USHORT,
    UTIL_TYPE_ENUM_INT,
    UTIL_TYPE_ENUM_UINT,
    UTIL_TYPE_ENUM_LONG,
    UTIL_TYPE_ENUM_ULONG,
    UTIL_TYPE_ENUM_LONG_LONG,
    UTIL_TYPE_ENUM_ULONG_LONG,
    UTIL_TYPE_ENUM_FLOAT,
    UTIL_TYPE_ENUM_DOUBLE,
    UTIL_TYPE_ENUM_LONG_DOUBLE,
    UTIL_TYPE_ENUM_OTHER,
} UtilTypeEnum_t;


// returns the enumerated value representing a primitive type
#define UTIL_TYPE_GET_TYPE_ENUM(x) _Generic((x), \
    _Bool: UTIL_TYPE_ENUM_BOOL, \
    char: UTIL_TYPE_ENUM_CHAR, \
    signed char: UTIL_TYPE_ENUM_SCHAR, \
    unsigned char: UTIL_TYPE_ENUM_UCHAR, \
    short int: UTIL_TYPE_ENUM_SHORT, \
    unsigned short int: UTIL_TYPE_ENUM_USHORT, \
    int: UTIL_TYPE_ENUM_INT, \
    unsigned int: UTIL_TYPE_ENUM_UINT, \
    long int: UTIL_TYPE_ENUM_LONG, \
    unsigned long int: UTIL_TYPE_ENUM_ULONG, \
    long long int: UTIL_TYPE_ENUM_LONG_LONG, \
    unsigned long long int: UTIL_TYPE_ENUM_ULONG_LONG, \
    float: UTIL_TYPE_ENUM_FLOAT, \
    double: UTIL_TYPE_ENUM_DOUBLE, \
    long double: UTIL_TYPE_ENUM_LONG_DOUBLE, \
    default: UTIL_TYPE_ENUM_OTHER)


typedef int32_t foo_t;
typedef float bar_t;

// IF YOU COMMENT OUT THE STATIC ASSERT, THE CODE WILL COMPILE AND WORKS AS EXPECTED
_Static_assert((UTIL_TYPE_GET_TYPE_ENUM(foo_t)==UTIL_TYPE_GET_TYPE_ENUM(bar_t)),"ERROR");

int main(void)
{
    foo_t foo;
    bar_t bar;

    printf("foo's type = %d\n", UTIL_TYPE_GET_TYPE_ENUM(foo));    
    printf("bar's type = %d\n", UTIL_TYPE_GET_TYPE_ENUM(bar));

    if (UTIL_TYPE_GET_TYPE_ENUM(foo) != UTIL_TYPE_GET_TYPE_ENUM(bar))
    {
        printf("Not the same type!\n");
    }
    else
    {
        printf("Same type!\n");
    }
    return 0;
}

#endif 

编译器错误:

$ gcc foo.c
foo.c:35:49: error: expected expression before ‘,’ token
 #define UTIL_TYPE_GET_TYPE_ENUM(x) _Generic((x), \
                                                 ^
foo.c:77:17: note: in expansion of macro ‘UTIL_TYPE_GET_TYPE_ENUM’
 _Static_assert((UTIL_TYPE_GET_TYPE_ENUM(foo_t)==UTIL_TYPE_GET_TYPE_ENUM(bar_t)),"ERROR");
                 ^~~~~~~~~~~~~~~~~~~~~~~
foo.c:35:49: error: expected expression before ‘,’ token
 #define UTIL_TYPE_GET_TYPE_ENUM(x) (_Generic((x), \
                                                 ^
foo.c:77:49: note: in expansion of macro ‘UTIL_TYPE_GET_TYPE_ENUM’
 _Static_assert((UTIL_TYPE_GET_TYPE_ENUM(foo_t)==UTIL_TYPE_GET_TYPE_ENUM(bar_t)),"ERROR");
                                                 ^~~~~~~~~~~~~~~~~~~~~~~
foo.c:77:16: error: expression in static assertion is not an integer
 _Static_assert((UTIL_TYPE_GET_TYPE_ENUM(foo_t)==UTIL_TYPE_GET_TYPE_ENUM(bar_t)),"ERROR");

【问题讨论】:

    标签: c gcc compiler-errors c11 static-assert


    【解决方案1】:

    _Generic 选择的参数必须是有效的 C 表达式,然后检查其类型。您提供一个类型名称,它根本不是一个表达式。

    要获得您所追求的类型的表达式,您可以使用复合文字:

    _Static_assert((UTIL_TYPE_GET_TYPE_ENUM((foo_t){0})==UTIL_TYPE_GET_TYPE_ENUM((bar_t){0})),"ERROR");
    

    (foo_t){0}(bar_t){0} 现在是您要比较的类型的表达式,因此可以在泛型选择中使用。

    【讨论】:

    • 顺便说一句,在这里使用枚举只是人为地限制了您可以处理的类型范围。只需使用_Static_assert(_Generic((foo_t){0}, bar_t: 1, default: 0));
    【解决方案2】:

    您可以通过

    实现您想要的,或者至少是接近的目标
    #define SAME_TYPE(t1,t2) _Generic((t1){0}, t2: 1, default: 0)
    _Static_assert(SAME_TYPE(foo_t, bar_t));
    

    这不涉及假定一组有限类型的枚举(不支持结构类型等),也不依赖于“GNU C”typeof 扩展(不是 C 语言的一部分)。

    这适用于类型,不适用于表达式。它可以很容易地扩展到一个参数是类型而另一个参数是表达式的情况。如果您需要断言两个表达式具有相同的类型,那么纯粹在 C 中至少有点难以做到,并且可能是不可能的。如果你在它们是变量的特殊情况下,表达式

    1 ? &var1 : &var2
    

    如果var1var2 具有不同的类型,则违反约束,但遗憾的是,GCC 默认将此视为警告而不是错误。我不知道在没有完整的-Werror 的情况下将其变为错误的方法,因为它似乎不在自己的警告组中,只是默认的匿名警告...

    【讨论】:

    • _Generic((t1)+0, 可以用来将 _Generic 的参数转换为表达式(无论它是类型还是表达式)的一个有点脏的技巧。解释here.
    【解决方案3】:

    正如另一个答案中已经指出的那样,您不能将类型作为第一个参数传递给_Generic。您需要提供一个表达式,如果表达式的类型与列表匹配,编译器将推断。

    可以简化您的代码。您不需要使用这些枚举。 支持 typeof 扩展,它允许您执行以下操作:

    #define EXPR_HAVE_SAME_TYPE(E1, E2) \
            _Generic((E1), typeof(E2): 1, default: 0)
    
    _Static_assert(EXPR_HAVE_SAME_TYPE(foo, foo), "...");
    _Static_assert(!EXPR_HAVE_SAME_TYPE(foo, bar), "...");
    

    如果您创建表达式,您可以将其用于类型:

    #define SAME_TYPE(T1, T2) \
            EXPR_HAVE_SAME_TYPE(*(T1 *)0, *(T2 *)0)
    
    _Static_assert(SAME_TYPE(foo_t, foo_t), "...");
    _Static_assert(!SAME_TYPE(foo_t, bar_t), "...");
    

    R.'s commentanswer 中提到了类似的概念。

    【讨论】:

    • 这取决于typeof,它不是 C,而是“GNU C”扩展。 OP 想要的东西可以在没有任何扩展或枚举的情况下完成。
    • @R..:对于比较类型,是的。但是 OP 的代码也在比较表达式(嗯,变量)。 OP 的问题已标记为 GCC,所以我认为这是公平的游戏。
    猜你喜欢
    • 1970-01-01
    • 2014-05-23
    • 2015-08-17
    • 1970-01-01
    • 2013-01-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多