MIN and MAX in C

后端 未结 14 1416
攒了一身酷
攒了一身酷 2020-11-22 13:32

Where are MIN and MAX defined in C, if at all?

What is the best way to implement these, as generically and type safely as possible? (Compil

14条回答
  •  难免孤独
    2020-11-22 14:24

    Avoid non-standard compiler extensions and implement it as a completely type-safe macro in pure standard C (ISO 9899:2011).

    Solution

    #define GENERIC_MAX(x, y) ((x) > (y) ? (x) : (y))
    
    #define ENSURE_int(i)   _Generic((i), int:   (i))
    #define ENSURE_float(f) _Generic((f), float: (f))
    
    
    #define MAX(type, x, y) \
      (type)GENERIC_MAX(ENSURE_##type(x), ENSURE_##type(y))
    

    Usage

    MAX(int, 2, 3)
    

    Explanation

    The macro MAX creates another macro based on the type parameter. This control macro, if implemented for the given type, is used to check that both parameters are of the correct type. If the type is not supported, there will be a compiler error.

    If either x or y is not of the correct type, there will be a compiler error in the ENSURE_ macros. More such macros can be added if more types are supported. I've assumed that only arithmetic types (integers, floats, pointers etc) will be used and not structs or arrays etc.

    If all types are correct, the GENERIC_MAX macro will be called. Extra parenthesis are needed around each macro parameter, as the usual standard precaution when writing C macros.

    Then there's the usual problems with implicit type promotions in C. The ?:operator balances the 2nd and 3rd operand against each other. For example, the result of GENERIC_MAX(my_char1, my_char2) would be an int. To prevent the macro from doing such potentially dangerous type promotions, a final type cast to the intended type was used.

    Rationale

    We want both parameters to the macro to be of the same type. If one of them is of a different type, the macro is no longer type safe, because an operator like ?: will yield implicit type promotions. And because it does, we also always need to cast the final result back to the intended type as explained above.

    A macro with just one parameter could have been written in a much simpler way. But with 2 or more parameters, there is a need to include an extra type parameter. Because something like this is unfortunately impossible:

    // this won't work
    #define MAX(x, y)                                  \
      _Generic((x),                                    \
               int: GENERIC_MAX(x, ENSURE_int(y))      \
               float: GENERIC_MAX(x, ENSURE_float(y))  \
              )
    

    The problem is that if the above macro is called as MAX(1, 2) with two int, it will still try to macro-expand all possible scenarios of the _Generic association list. So the ENSURE_float macro will get expanded too, even though it isn't relevant for int. And since that macro intentionally only contains the float type, the code won't compile.

    To solve this, I created the macro name during the pre-processor phase instead, with the ## operator, so that no macro gets accidentally expanded.

    Examples

    #include 
    
    #define GENERIC_MAX(x, y) ((x) > (y) ? (x) : (y))
    
    #define ENSURE_int(i)   _Generic((i), int:   (i))
    #define ENSURE_float(f) _Generic((f), float: (f))
    
    
    #define MAX(type, x, y) \
      (type)GENERIC_MAX(ENSURE_##type(x), ENSURE_##type(y))
    
    int main (void)
    {
      int    ia = 1,    ib = 2;
      float  fa = 3.0f, fb = 4.0f;
      double da = 5.0,  db = 6.0;
    
      printf("%d\n", MAX(int,   ia, ib)); // ok
      printf("%f\n", MAX(float, fa, fb)); // ok
    
    //printf("%d\n", MAX(int,   ia, fa));  compiler error, one of the types is wrong
    //printf("%f\n", MAX(float, fa, ib));  compiler error, one of the types is wrong
    //printf("%f\n", MAX(double, fa, fb)); compiler error, the specified type is wrong
    //printf("%f\n", MAX(float, da, db));  compiler error, one of the types is wrong
    
    //printf("%d\n", MAX(unsigned int, ia, ib)); // wont get away with this either
    //printf("%d\n", MAX(int32_t, ia, ib)); // wont get away with this either
      return 0;
    }
    

提交回复
热议问题