Min and Max in C

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? (Compiler extensions/builtins for mainstream compilers preferred.)

1

15 Answers

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.

22

It's also provided in the GNU libc (Linux) and FreeBSD versions of sys/param.h, and has the definition provided by dreamlax.


On Debian:

$ uname -sr
Linux 2.6.11

$ cat /etc/debian_version
5.0.2

$ egrep 'MIN\(|MAX\(' /usr/include/sys/param.h
#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))

$ head -n 2 /usr/include/sys/param.h | grep GNU
This file is part of the GNU C Library.

On FreeBSD:

$ uname -sr
FreeBSD 5.5-STABLE

$ egrep 'MIN\(|MAX\(' /usr/include/sys/param.h
#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))

The source repositories are here:

4

There's a std::min and std::max in C++, but AFAIK, there's no equivalent in the C standard library. You can define them yourself with macros like

#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define MIN(x, y) (((x) < (y)) ? (x) : (y))

But this causes problems if you write something like MAX(++a, ++b).

5

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;
}
1

@David Titarenco nailed it here, but let me at least clean it up a bit to make it look nice, and show both min() and max() together to make copying and pasting from here easier. :)

Update 25 Apr. 2020: I've also added a Section 3 to show how this would be done with C++ templates too, as a valuable comparison for those learning both C and C++, or transitioning from one to the other. I've done my best to be thorough and factual and correct to make this answer a canonical reference I can come back to again and again, and I hope you find it as useful as I do.

1. The old C macro way:

This technique is commonly used, well-respected by those who know how to use it properly, the "de facto" way of doing things, and fine to use if used properly, but buggy (think: double-evaluation side effect) if you ever pass expressions including variable assignment in to compare:

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

Marcus Vance

Cybersecurity & Digital Privacy Researcher

Marcus Vance is a cybersecurity auditor and technology writer dedicated to educating the public about online safety, data privacy regulations, enterprise security, and emerging cyber threats.