MIN and MAX in C

后端 未结 14 1400
攒了一身酷
攒了一身酷 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:22

    Looks like Windef.h (a la #include <windows.h>) has max and min (lower case) macros, that also suffer from the "double evaluation" difficulty, but they're there for those that don't want to re-roll their own :)

    0 讨论(0)
  • 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 <stdio.h>
    
    #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;
    }
    
    0 讨论(0)
  • 2020-11-22 14:28

    It's worth pointing out I think that if you define min and max with the ternary operation such as

    #define MIN(a,b) (((a)<(b))?(a):(b))
    #define MAX(a,b) (((a)>(b))?(a):(b))
    

    then to get the same result for the special case of fmin(-0.0,0.0) and fmax(-0.0,0.0) you need to swap the arguments

    fmax(a,b) = MAX(a,b)
    fmin(a,b) = MIN(b,a)
    
    0 讨论(0)
  • 2020-11-22 14:30

    If you need min/max in order to avoid an expensive branch, you shouldn't use the ternary operator, as it will compile down to a jump. The link below describes a useful method for implementing a min/max function without branching.

    http://graphics.stanford.edu/~seander/bithacks.html#IntegerMinOrMax

    0 讨论(0)
  • 2020-11-22 14:33

    The simplest way is to define it as a global function in a .h file, and call it whenever you want, if your program is modular with lots of files. If not, double MIN(a,b){return (a<b?a:b)} is the simplest way.

    0 讨论(0)
  • 2020-11-22 14:34

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

    They aren't.

    What is the best way to implement these, as generically and type safe as possible (compiler extensions/builtins for mainstream compilers preferred).

    As functions. I wouldn't use macros like #define MIN(X, Y) (((X) < (Y)) ? (X) : (Y)), especially if you plan to deploy your code. Either write your own, use something like standard fmax or fmin, or fix the macro using GCC's typeof (you get typesafety bonus too) in a GCC statement expression:

     #define max(a,b) \
       ({ __typeof__ (a) _a = (a); \
           __typeof__ (b) _b = (b); \
         _a > _b ? _a : _b; })
    

    Everyone says "oh I know about double evaluation, it's no problem" and a few months down the road, you'll be debugging the silliest problems for hours on end.

    Note the use of __typeof__ instead of typeof:

    If you are writing a header file that must work when included in ISO C programs, write __typeof__ instead of typeof.

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