且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

如何在C中使用静态断言来检查传递给宏的参数的类型

更新时间:2023-12-01 09:08:52

如果最重要的方面是如果ab是不同类型,则希望它无法编译,则可以使用C11的_Generic以及GCC的__typeof__扩展名来进行管理.

If the most important aspect here is that you want it to fail to compile if a and b are different types, you can make use of C11's _Generic along with GCC's __typeof__ extension to manage this.

一个通用示例:

#include <stdio.h>

#define TYPE_ASSERT(X,Y) _Generic ((Y), \
    __typeof__(X): _Generic ((X), \
        __typeof__(Y): (void)NULL \
    ) \
)

int main(void)
{
    int a = 1; 
    int b = 2;
    TYPE_ASSERT(a,b);
    printf("a = %d, b = %d\n", a, b);
}

现在,如果我们尝试编译此代码,它将可以正常编译,并且每个人都很高兴.

Now if we try to compile this code, it will compile fine and everybody is happy.

但是,如果将b的类型更改为unsigned int,它将无法编译.

If we change the type of b to unsigned int, however, it will fail to compile.

之所以起作用,是因为_Generic选择使用控制表达式的类型(在这种情况下为(Y))来选择要遵循的规则并插入与该规则相对应的代码.在这种情况下,我们仅提供了__typeof__(X)的规则,因此,如果(X)不是(Y)的兼容类型,则没有合适的规则可供选择,因此无法编译.为了处理具有控制表达式的数组,该表达式将衰减为指针,我添加了另一个_Generic,该数组以另一种方式确保它们必须彼此兼容,而不是接受单向兼容性.而且,就我所关心的而言,我们只想确保它在不匹配时无法编译,而不是在匹配时执行某些特定的操作,因此我给相应的规则指定了什么都不做的任务:(void)NULL

This works because _Generic selection uses the type of a controlling expression ((Y) in this case) to select a rule to follow and insert code corresponding to the rule. In this case, we only provided a rule for __typeof__(X), thus if (X) is not a compatible type for (Y), there is no suitable rule to select and therefore cannot compile. To handle arrays, which have a controlling expression that will decay to a pointer, I added another _Generic that goes the other way ensuring they must both be compatible with one another rather than accepting one-way compatibility. And since--as far as I particularly cared--we only wanted to make sure it would fail to compile on a mismatch, rather than execute something particular upon a match, I gave the corresponding rule the task of doing nothing: (void)NULL

在这种情况下,这种技术绊倒了:_Generic不能处理可变可修改类型,因为它是在编译时处理的.因此,如果您尝试使用可变长度数组来执行此操作,它将无法编译.

There is a corner case where this technique stumbles: _Generic does not handle Variably Modifiable types since it is handled at compile time. So if you attempt to do this with a Variable Length Array, it will fail to compile.

要处理固定宽度无符号类型的特定用例,我们可以修改嵌套的_Generic来处理它,而不是处理数组的特殊性:

To handle your specific use-case for fixed-width unsigned types, we can modify the nested _Generic to handle that rather than handling the pecularities of an array:

#define TYPE_ASSERT(X,Y) _Generic ((Y), \
    __typeof__(X): _Generic ((Y), \
        uint8_t: (void)NULL, \
        uint16_t: (void)NULL, \
        uint32_t: (void)NULL, \
        uint64_t: (void)NULL \
   ) \
)

传递不兼容类型时的示例GCC错误:

Example GCC error when passing non-compatible types:

main.c: In function 'main':
main.c:7:34: error: '_Generic' selector of type 'signed char' is not compatible with any association
    7 |         __typeof__(X): _Generic ((Y), \
      |                                  ^

值得一提的是,作为ccc扩展的__typeof__并不是一个可移植到所有编译器的解决方案.不过,它似乎确实可以与Clang一起使用,因此这是另一个支持它的主要编译器.

It is worth mentioning that __typeof__, being a GCC extension, will not be a solution that is portable to all compilers. It does seem to work with Clang, though, so that's another major compiler supporting it.