How to use static assert in C to check the types of parameters passed to a macro

前端 未结 2 983
南笙
南笙 2020-12-11 12:24

I need to write a C macro that checks to ensure all parameters passed to it are unsigned and of the same integer type. Ex: all input params are uint8_t

2条回答
  •  心在旅途
    2020-12-11 12:29

    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.

    A generic example:

    #include 
    
    #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.

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

    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

    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.

    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 \
       ) \
    )
    

    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), \
          |                                  ^
    

    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.

提交回复
热议问题