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

前端 未结 2 970
南笙
南笙 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 <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.

    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.

    0 讨论(0)
  • 2020-12-11 12:44

    What you want is doable in standard C11, no extensions or GCC required.

    We'll build up to the final answer, so all can follow.


    According to the C11 standard [6.7.10], static_assert-declaration: _Static_assert( constant-expression , string-literal ) is a Declaration. Thus if we are going to use a macro, we had best provide a scope for a declaration, to keep things tidy. Typically of the usual form:

    #define MY_AMAZING_MACRO() do {_Static_assert(...some magic...);} while(0)
    

    Next, so that our _Static_assert within the macro at least repeats via stdio the actual issue if the assert fails, well use familiar stringification setup:

    #define STATIC_ASSERT_H(x)  _Static_assert(x, #x)
    #define STATIC_ASSERT(x)    STATIC_ASSERT_H(x)
    

    Next, we'll use C11's Generic selection feature to declare a macro that returns a constant 1 if the object is of the type we're looking for, and zero otherwise:

    #define OBJ_IS_OF_TYPE(Type, Obj) _Generic(Obj, Type: 1, default: 0)
    

    Next we''l make a macro to test if all four of your inputs are of the same type:

    #define ALL_OBJS_ARE_OF_TYPE(Type, Obj_0, Obj_1, Obj_2, Obj_3)  \
        (OBJ_IS_OF_TYPE(Type, Obj_0) &&                             \
         OBJ_IS_OF_TYPE(Type, Obj_1) &&                             \
         OBJ_IS_OF_TYPE(Type, Obj_2) &&                             \
         OBJ_IS_OF_TYPE(Type, Obj_3))
    

    Next, using the above, well make a macro to test if all four of your inputs are further one of the four types:

    #define IS_ACCEPTABLE(Type_0, Type_1, Type_2, Type_3, Obj_0, Obj_1, Obj_2, Obj_3)   \
        (ALL_OBJS_ARE_OF_TYPE(Type_0, Obj_0, Obj_1, Obj_2, Obj_3) ||                    \
         ALL_OBJS_ARE_OF_TYPE(Type_1, Obj_0, Obj_1, Obj_2, Obj_3) ||                    \
         ALL_OBJS_ARE_OF_TYPE(Type_2, Obj_0, Obj_1, Obj_2, Obj_3) ||                    \
         ALL_OBJS_ARE_OF_TYPE(Type_3, Obj_0, Obj_1, Obj_2, Obj_3))
    

    And FINALLY, putting it all together:

    #define TEST_FUNC(a,b,c,d)                                              \
    do                                                                      \
    {                                                                       \
        STATIC_ASSERT(IS_ACCEPTABLE(uint8_t, uint16_t, uint32_t, uint64_t,  \
                                    a,       b,        c,        d));       \
    } while(0)
    

    Of course, you could separate the above into more distinct, individual STATIC_ASSERTs, as you wish, if you want more verbose error output if any of the _Static_asserts fail.

    0 讨论(0)
提交回复
热议问题