it-swarm.com.de

MIN und MAX in C

Wo sind MIN und MAX wenn überhaupt in C definiert?

Wie lassen sich diese am besten so generisch und sicher wie möglich implementieren? (Compiler-Erweiterungen/Builtins für Mainstream-Compiler bevorzugt.)

238
Matt Joiner

Wo sind MIN und MAX wenn überhaupt in C definiert?

Sie sind nicht.

Was ist der beste Weg, diese zu implementieren, so generisch und typsicher wie möglich (Compiler-Erweiterungen/Builtins für Mainstream-Compiler bevorzugt).

Als Funktionen. Ich würde keine Makros wie #define MIN(X, Y) (((X) < (Y)) ? (X) : (Y)) verwenden, insbesondere wenn Sie Ihren Code bereitstellen möchten. Entweder schreiben Sie Ihre eigenen, verwenden etwas wie standard fmax oder fmin , oder reparieren Sie das Makro mit GCCs Typof (Sie erhalten auch einen Sicherheitssicherheitsbonus):

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

Jeder sagt "Oh, ich weiß über doppelte Bewertung, es ist kein Problem" und in ein paar Monaten werden Sie stundenlang die dümmsten Probleme lösen.

Beachten Sie die Verwendung von __typeof__ anstelle von typeof:

Wenn Sie eine Header-Datei schreiben, die muss funktionieren, wenn sie in ISO C enthalten sind schreiben Sie __typeof__ anstelle von typeof.

318
David Titarenco

Es ist auch in der GNU libc (Linux) - und FreeBSD-Version von sys/param.h enthalten und hat die Definition von dreamlax.


Auf 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.

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

Die Quell-Repositories sind hier:

81
Mikel

Es gibt einen std::min und einen std::max in C++, aber AFAIK gibt es in der C-Standardbibliothek keine Entsprechung. Sie können sie mit Makros wie definieren

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

Dies verursacht jedoch Probleme, wenn Sie etwas wie MAX(++a, ++b) schreiben.

67
dan04

Vermeiden Sie nicht standardmäßige Compiler-Erweiterungen und implementieren Sie es als vollständig typsicheres Makro in dem reinen Standard C (ISO 9899: 2011).

Lösung

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

Verwendungszweck  

MAX(int, 2, 3)

Erklärung

Das Makro MAX erstellt ein weiteres Makro, das auf dem Parameter type basiert. Wenn dieses Steuermakro für den angegebenen Typ implementiert ist, wird es verwendet, um zu überprüfen, ob beide Parameter den richtigen Typ haben. Wenn die Variable type nicht unterstützt wird, liegt ein Compilerfehler vor.

Wenn x oder y nicht den richtigen Typ haben, wird in den ENSURE_-Makros ein Compiler-Fehler angezeigt. Weitere solche Makros können hinzugefügt werden, wenn weitere Typen unterstützt werden. Ich habe davon ausgegangen, dass nur arithmetische Typen (Ganzzahlen, Gleitkommazahlen, Zeiger usw.) verwendet werden und keine Strukturen oder Arrays usw.

Wenn alle Typen korrekt sind, wird das GENERIC_MAX-Makro aufgerufen. Um die Makroparameter herum ist eine zusätzliche Klammer erforderlich, wie bei der Erstellung von C-Makros üblich.

Dann gibt es die üblichen Probleme mit impliziten Typ-Promotionen in C. Der ?:-Operator gleicht den 2. und 3. Operanden gegeneinander aus. Das Ergebnis von GENERIC_MAX(my_char1, my_char2) wäre beispielsweise eine int. Um zu verhindern, dass das Makro solche potenziell gefährlichen Typaktionen ausführt, wurde ein endgültiger Typ verwendet, der auf den beabsichtigten Typ umgewandelt wurde.

Begründung

Wir möchten, dass beide Parameter des Makros vom gleichen Typ sind. Wenn einer von ihnen einen anderen Typ hat, ist das Makro nicht mehr typsicher, da ein Operator wie ?: implizite Typumgebungen liefert. Und weil dies der Fall ist, müssen wir das Endergebnis immer wieder auf den beabsichtigten Typ zurückstellen, wie oben erläutert.

Ein Makro mit nur einem Parameter hätte viel einfacher geschrieben werden können. Bei 2 oder mehr Parametern müssen Sie jedoch einen zusätzlichen Typparameter hinzufügen. Weil so etwas leider nicht möglich ist:

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

Das Problem ist, dass das Makro, wenn es als MAX(1, 2) mit zwei int aufgerufen wird, trotzdem versucht, alle möglichen Szenarien der _Generic-Zuordnungsliste zu erweitern. Das ENSURE_float-Makro wird also auch erweitert, obwohl es für int nicht relevant ist. Und da dieses Makro absichtlich nur den Typ float enthält, wird der Code nicht kompiliert.

Um dieses Problem zu lösen, habe ich stattdessen den Makronamen in der Vorprozessorphase mit dem Operator ## erstellt, sodass kein Makro versehentlich erweitert wird.

Beispiele

#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;
}
19
Lundin

Ich glaube nicht, dass es sich um standardisierte Makros handelt. Es gibt bereits standardisierte Funktionen für Gleitkommazahlen, fmax und fmin (und fmaxf für Floats und fmaxl für lange Doubles).

Sie können sie als Makros implementieren, solange Sie die Probleme mit Nebenwirkungen/Doppelbewertung kennen.

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

In den meisten Fällen können Sie es dem Compiler überlassen, zu bestimmen, was Sie versuchen, und es so optimal wie möglich zu optimieren. Dies führt zwar zu Problemen wie MAX(i++, j++), aber ich bezweifle, dass es immer sehr wichtig ist, das Maximum der inkrementierten Werte auf einmal zu überprüfen. Zuerst erhöhen, dann überprüfen.

18
dreamlax

Dies ist eine verspätete Antwort aufgrund einer relativ jungen Entwicklung. Da das OP die Antwort akzeptierte, die auf einer nicht portablen GCC (und clang) -Erweiterung typeof - oder __typeof__ für "sauberes" ISO C beruht, gibt es eine bessere Lösung als gcc-4.9 .

#define max(x,y) ( \
    { __auto_type __x = (x); __auto_type __y = (y); \
      __x > __y ? __x : __y; })

Der offensichtliche Vorteil dieser Erweiterung besteht darin, dass jedes Makroargument im Gegensatz zur __typeof__-Lösung nur einmal erweitert wird.

__auto_type ist eine begrenzte Form von auto von C++ 11. Es kann (oder sollte nicht?) In C++ - Code verwendet werden, obwohl es keinen guten Grund gibt, die übergeordneten Inferenzfunktionen von auto nicht zu verwenden, wenn C++ 11 verwendet wird.

Das heißt, ich nehme an es gibt keine Probleme mit dieser Syntax, wenn das Makro in einem extern "C" { ... }-Bereich enthalten ist; beispielsweise von einem C-Header. AFAIK, diese Erweiterung hat ihren Weg nicht gefunden

16
Brett Hale

Ich schrieb diese Version , die für MSVC, GCC, C und C++ funktioniert.

#if defined(__cplusplus) && !defined(__GNUC__)
#   include <algorithm>
#   define MIN std::min
#   define MAX std::max
//#   define TMIN(T, a, b) std::min<T>(a, b)
//#   define TMAX(T, a, b) std::max<T>(a, b)
#else
#       define _CHOOSE2(binoper, lexpr, lvar, rexpr, rvar) \
                ({ \
                        decltype(lexpr) lvar = (lexpr); \
                        decltype(rexpr) rvar = (rexpr); \
                        lvar binoper rvar ? lvar : rvar; \
                })
#       define _CHOOSE_VAR2(prefix, unique) prefix##unique
#       define _CHOOSE_VAR(prefix, unique) _CHOOSE_VAR2(prefix, unique)
#       define _CHOOSE(binoper, lexpr, rexpr) \
                _CHOOSE2( \
                        binoper, \
                        lexpr, _CHOOSE_VAR(_left, __COUNTER__), \
                        rexpr, _CHOOSE_VAR(_right, __COUNTER__) \
                )
#       define MIN(a, b) _CHOOSE(<, a, b)
#       define MAX(a, b) _CHOOSE(>, a, b)
#endif
11
Matt Joiner

Wenn Sie min/max benötigen, um einen teuren Zweig zu vermeiden, sollten Sie den ternären Operator nicht verwenden, da dies zu einem Sprung führt. Der Link unten beschreibt eine nützliche Methode zum Implementieren einer Min/Max-Funktion ohne Verzweigung.

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

8
cib

Ich weiß, der Typ sagte "C" ..... Wenn Sie die Chance haben, verwenden Sie eine C++ - Vorlage:

template<class T> T min(T a, T b) { return a < b ? a : b; }

Geben Sie sicher und keine Probleme mit dem ++, das in anderen Kommentaren erwähnt wird.

4
Bas Kuenen

Es sei darauf hingewiesen, dass ich denke, wenn Sie min und max mit dem Tertiär definieren, z

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

um dasselbe Ergebnis für den Sonderfall fmin(-0.0,0.0) und fmax(-0.0,0.0) zu erhalten, müssen Sie die Argumente austauschen

fmax(a,b) = MAX(a,b)
fmin(a,b) = MIN(b,a)
3
Z boson

Sieht aus, als hätten Windef.h (a la #include <windows.h>) Makros max und min (Kleinbuchstaben), die ebenfalls unter der Schwierigkeit "doppelte Bewertung" leiden, aber sie sind für diejenigen da, die ihre eigenen Würfe nicht wiederholen wollen :)

2
rogerdpack

Die maximal zwei Ganzzahlen a und b sind (int)(0.5((a+b)+abs(a-b))). Dies funktioniert möglicherweise auch mit (double) und fabs(a-b) für Doubles (ähnlich für Floats)

0
NRZ

Zu Brett Hales Kommentar , clang unterstützt __auto_type um 2016 (siehe patch ).

0
Lars