Wo sind MIN
und MAX
in C definiert, wenn überhaupt?
Was ist der beste Weg, um diese so allgemein und sicher wie möglich zu implementieren? (Compiler-Erweiterungen / Builtins für Mainstream-Compiler werden bevorzugt.)
Wo sind MIN
und MAX
in C definiert, wenn überhaupt?
Was ist der beste Weg, um diese so allgemein und sicher wie möglich zu implementieren? (Compiler-Erweiterungen / Builtins für Mainstream-Compiler werden bevorzugt.)
Antworten:
Wo sind
MIN
undMAX
in C definiert, wenn überhaupt?
Sie sind nicht.
Was ist der beste Weg, um diese so allgemein und typsicher wie möglich zu implementieren (Compiler-Erweiterungen / Builtins für Mainstream-Compiler bevorzugt).
Als Funktionen. Ich würde keine Makros wie verwenden #define MIN(X, Y) (((X) < (Y)) ? (X) : (Y))
, insbesondere wenn Sie vorhaben, Ihren Code bereitzustellen. Schreiben Sie entweder Ihre eigenen, verwenden Sie so etwas wie Standard fmax
oder fmin
oder korrigieren Sie das Makro mithilfe des GCC-Typs (Sie erhalten auch einen Typensicherheitsbonus) in einem GCC-Anweisungsausdruck :
#define max(a,b) \
({ __typeof__ (a) _a = (a); \
__typeof__ (b) _b = (b); \
_a > _b ? _a : _b; })
Alle sagen: "Oh, ich weiß über doppelte Evaluierung Bescheid, das ist kein Problem." Ein paar Monate später werden Sie stundenlang die albernsten Probleme beheben.
Beachten Sie die Verwendung von __typeof__
anstelle von typeof
:
Wenn Sie eine Header-Datei schreiben, die funktionieren muss, wenn sie in ISO C-Programmen enthalten ist, schreiben Sie
__typeof__
statttypeof
.
decltype
Schlüsselwort MSVC ++ 2010 herumzuspielen. Trotzdem kann Visual Studio keine zusammengesetzten Anweisungen in Makros ausführen (und decltype
ist sowieso C ++), dh GCCs ({ ... })
Syntax, also bin ich mir ziemlich sicher, dass es sowieso nicht möglich ist. Ich habe mir keine anderen Compiler zu diesem Thema angesehen, sorry Luther: S
MAX(someUpperBound, someRandomFunction())
einen zufälligen Wert auf eine Obergrenze beschränkt hatte. Es war eine schreckliche Idee, aber es funktionierte auch nicht einmal, da das von MAX
ihm verwendete Problem mit der doppelten Bewertung bestand und er eine andere Zufallszahl als die ursprünglich bewertete hatte.
MIN(x++, y++)
den Präprozessor aufrufen, wird der folgende Code generiert (((x++) < (y++)) ? (x++) : (y++))
. Also, x
und y
wird zweimal erhöht.
Es ist auch in den Versionen GNU libc (Linux) und FreeBSD 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.
Auf 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-Repositorys sind hier:
openSUSE/Linux 3.1.0-1.2-desktop
/ gcc version 4.6.2 (SUSE Linux)
auch. :) Schlecht, es ist nicht tragbar.
Es gibt ein std::min
und std::max
in C ++, aber AFAIK, es gibt kein Äquivalent in der C-Standardbibliothek. Sie können sie selbst 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 so etwas schreiben MAX(++a, ++b)
.
#define MIN(A, B) ((A < B) ? A : B)
sei kein flexibler Weg, warum ???
#define MULT(x, y) x * y
. Erweitert MULT(a + b, a + b)
sich dann zu a + b * a + b
, was a + (b * a) + b
nach Vorrang analysiert wird . Das hat der Programmierer wahrscheinlich nicht beabsichtigt.
Vermeiden Sie nicht standardmäßige Compiler-Erweiterungen und implementieren Sie sie als vollständig typsicheres Makro in reinem 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))
Verwendung
MAX(int, 2, 3)
Erläuterung
Das Makro MAX erstellt basierend auf dem type
Parameter ein weiteres Makro . Wenn dieses Steuermakro für den angegebenen Typ implementiert ist, wird überprüft, ob beide Parameter vom richtigen Typ sind. Wenn das type
nicht unterstützt wird, liegt ein Compilerfehler vor.
Wenn entweder x oder y nicht vom richtigen Typ ist, liegt ein Compilerfehler in den ENSURE_
Makros vor. Weitere solche Makros können hinzugefügt werden, wenn mehr Typen unterstützt werden. Ich habe angenommen, dass nur arithmetische Typen (Ganzzahlen, Gleitkommazahlen, Zeiger usw.) verwendet werden und keine Strukturen oder Arrays usw.
Wenn alle Typen korrekt sind, wird das Makro GENERIC_MAX aufgerufen. Für jeden Makroparameter sind zusätzliche Klammern erforderlich, wie beim Schreiben von C-Makros üblich.
Dann gibt es die üblichen Probleme mit impliziten Typ-Promotions in C. Der ?:
Operator gleicht den 2. und 3. Operanden gegeneinander aus. Zum Beispiel GENERIC_MAX(my_char1, my_char2)
wäre das Ergebnis von ein int
. Um zu verhindern, dass das Makro solche potenziell gefährlichen Typwerbung durchfü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 von einem anderen Typ ist, ist das Makro nicht mehr typsicher, da ein Operator wie ?:
implizite Typ-Promotions liefert. Und weil dies der Fall ist, müssen wir das Endergebnis immer wieder auf den beabsichtigten Typ zurücksetzen, wie oben erläutert.
Ein Makro mit nur einem Parameter hätte viel einfacher geschrieben werden können. Bei zwei oder mehr Parametern muss jedoch ein zusätzlicher Typparameter eingefügt werden. Weil so etwas leider unmö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, wenn das obige Makro wie MAX(1, 2)
bei zwei aufgerufen int
wird, immer noch versucht wird, alle möglichen Szenarien der _Generic
Zuordnungsliste zu erweitern. Das ENSURE_float
Makro wird also auch erweitert, obwohl es für nicht relevant ist int
. Und da dieses Makro absichtlich nur den float
Typ enthält , wird der Code nicht kompiliert.
Um dies zu lösen, habe ich den Makronamen stattdessen während der Vorprozessorphase mit dem Operator ## erstellt, damit 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;
}
GENERIC_MAX
Makro ist übrigens eine schlechte Idee, man muss nur GENERIC_MAX(var++, 7)
herausfinden, warum :-) Heutzutage (insbesondere bei stark optimierten / inlinierenden Compilern) sollten Makros so ziemlich nur auf die einfachen Formulare verwiesen werden. Die funktionsähnlichen sind besser als Funktionen und die Wertegruppen besser als Aufzählungen.
Ich denke nicht, dass es sich um standardisierte Makros handelt. Es gibt bereits standardisierte Funktionen für Gleitkommazahlen fmax
und fmin
(und für Gleitkommazahlen fmaxf
und fmaxl
für lange Doppelbilder).
Sie können sie als Makros implementieren, solange Sie sich der Probleme mit Nebenwirkungen / Doppelbewertung bewusst sind.
#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 tun möchten, und es so gut wie möglich optimieren. Während dies Probleme verursacht, wenn es wie verwendet wird MAX(i++, j++)
, bezweifle ich, dass es jemals notwendig ist, das Maximum der inkrementierten Werte auf einmal zu überprüfen. Zuerst inkrementieren, dann prüfen.
Dies ist eine späte Antwort aufgrund einer relativ jungen Entwicklung. Da das OP die Antwort akzeptiert hat, die auf einer nicht portablen GCC- (und Clang-) Erweiterung beruht typeof
- oder __typeof__
für 'sauberes' ISO C -, gibt es ab gcc-4.9 eine bessere Lösung .
#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 C ++ 11 auto
. Es kann (oder sollte nicht?) Nicht in C ++ - Code verwendet werden, obwohl es keinen guten Grund gibt, die überlegenen Typinferenzfunktionen von auto
C ++ 11 nicht zu verwenden .
Ich gehe jedoch davon aus, dass es bei Verwendung dieser Syntax keine Probleme gibt, wenn das Makro in einem extern "C" { ... }
Bereich enthalten ist. zB aus einem C-Header. AFAIK, diese Erweiterung hat ihren Weg nicht gefunden
clang
begann __auto_type
um 2016 mit der Unterstützung (siehe Patch ).
c-preprocessor
Tag hat. Es ist nicht garantiert, dass eine Funktion auch mit diesem Schlüsselwort inline ist, es sei denn, Sie verwenden so etwas wie das __always_inline__
Attribut von gcc .
Ich habe diese Version geschrieben , 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
Wenn Sie min / max benötigen, um eine teure Verzweigung zu vermeiden, sollten Sie den ternären Operator nicht verwenden, da er zu einem Sprung kompiliert wird. Der folgende Link beschreibt eine nützliche Methode zum Implementieren einer Min / Max-Funktion ohne Verzweigung.
http://graphics.stanford.edu/~seander/bithacks.html#IntegerMinOrMax
@ David Titarenco genagelt es hier , aber lassen Sie mich zumindest sauber es sich ein wenig machen es schön aussehen, und beide zeigen min()
und max()
gemeinsam zu kopieren zu machen und Einfügen von hier leichter. :) :)
Update 25. April 2020: Ich habe auch einen Abschnitt 3 hinzugefügt, um zu zeigen, wie dies auch mit C ++ - Vorlagen geschehen würde, als wertvoller Vergleich für diejenigen, die sowohl C als auch C ++ lernen oder von einem zum anderen wechseln. Ich habe mein Bestes getan, um gründlich und sachlich und korrekt zu sein und diese Antwort zu einer kanonischen Referenz zu machen, auf die ich immer wieder zurückkommen kann, und ich hoffe, Sie finden sie genauso nützlich wie ich.
Diese Technik wird häufig verwendet und von denjenigen, die wissen, wie man sie richtig anwendet, der "De-facto" -Methode, und bei sachgemäßer Anwendung gut anzuwenden, aber fehlerhaft (denken Sie: Nebeneffekt bei doppelter Bewertung ) Übergeben Sie jemals Ausdrücke einschließlich Variablenzuweisung , um zu vergleichen:
#define MAX(a,b) ((a) > (b) ? (a) : (b))
#define MIN(a,b) ((a) < (b) ? (a) : (b))
Diese Technik vermeidet die oben genannten Nebenwirkungen und Fehler bei der "doppelten Bewertung" und wird daher als die überlegene, sicherere und "modernere" GCC C-Methode angesehen, um dies zu tun. Erwarten Sie, dass es sowohl mit dem gcc- als auch mit dem clang-Compiler funktioniert, da clang von Natur aus gcc-kompatibel ist (siehe den clang-Hinweis am Ende dieser Antwort).
ABER: Achten Sie immer noch auf " Variable Shadowing " -Effekte, da Anweisungsausdrücke anscheinend inline sind und daher KEINEN eigenen lokalen Variablenbereich haben!
#define max(a,b) \
({ \
__typeof__ (a) _a = (a); \
__typeof__ (b) _b = (b); \
_a > _b ? _a : _b; \
})
#define min(a,b) \
({ \
__typeof__ (a) _a = (a); \
__typeof__ (b) _b = (b); \
_a < _b ? _a : _b; \
})
Beachten Sie, dass in gcc-Anweisungsausdrücken der letzte Ausdruck im Codeblock das ist, was vom Ausdruck "zurückgegeben" wird, als ob er von einer Funktion zurückgegeben worden wäre. In der Dokumentation von GCC heißt es so:
Das Letzte in der zusammengesetzten Anweisung sollte ein Ausdruck sein, gefolgt von einem Semikolon. Der Wert dieses Unterausdrucks dient als Wert des gesamten Konstrukts. (Wenn Sie eine andere Art von Anweisung verwenden, die zuletzt in geschweiften Klammern steht, hat das Konstrukt den Typ void und somit praktisch keinen Wert.)
C ++ Hinweis: Wenn Sie C ++ verwenden, werden Vorlagen wahrscheinlich stattdessen für diese Art von Konstrukt empfohlen, aber ich persönlich mag keine Vorlagen und würde wahrscheinlich sowieso eines der oben genannten Konstrukte in C ++ verwenden, da ich häufig C-Stile in eingebettetem C ++ verwende und bevorzuge.
Dieser Abschnitt wurde am 25. April 2020 hinzugefügt:
Ich habe in den letzten Monaten eine Menge C ++ gemacht, und der Druck, in der C ++ - Community Vorlagen gegenüber Makros zu bevorzugen, wo dies möglich ist, ist ziemlich groß. Infolgedessen kann ich Vorlagen besser verwenden und möchte der Vollständigkeit halber die C ++ - Vorlagenversionen hier einfügen, um eine kanonischere und gründlichere Antwort zu erhalten.
Hier ist, wie grundlegende Funktionsvorlagenversionen von C ++ aussehen max()
und min()
aussehen könnten:
template <typename T>
T max(T a, T b)
{
return a > b ? a : b;
}
template <typename T>
T min(T a, T b)
{
return a < b ? a : b;
}
Lesen Sie hier mehr über C ++ - Vorlagen: Wikipedia: Vorlage (C ++) .
Beide max()
und min()
sind jedoch bereits Teil der C ++ - Standardbibliothek im <algorithm>
header ( #include <algorithm>
). In der C ++ - Standardbibliothek sind sie etwas anders definiert als ich sie oben habe. Die Standardprototypen für std::max<>()
und std::min<>()
zum Beispiel in C ++ 14, die ihre Prototypen in den oben genannten Links zu cplusplus.com betrachten, sind:
template <class T>
constexpr const T& max(const T& a, const T& b);
template <class T>
constexpr const T& min(const T& a, const T& b);
Beachten Sie, dass das Schlüsselwort typename
ist ein Alias für class
(so ihre Nutzung ist identisch , ob Sie sagen , <typename T>
oder <class T>
), da sie später nach der Erfindung der C ++ Templates anerkannt wurde, dass die Template - Typ ein regulärer Typ sein könnte ( int
, float
usw.) statt nur ein Klassentyp.
Hier können Sie sehen, dass sowohl die Eingabetypen als auch der Rückgabetyp const T&
"konstante Referenz zum Typ T
" sind. Dies bedeutet, dass die Eingabeparameter und der Rückgabewert als Referenz anstatt als Wert übergeben werden . Dies ähnelt dem Übergeben von Zeigern und ist für große Typen wie Klassenobjekte effizienter. Der constexpr
Teil der Funktion ändert die Funktion selbst und gibt an, dass die Funktion zur Kompilierungszeit ausgewertet werden kann (zumindest wenn constexpr
Eingabeparameter angegeben sind). Wenn sie jedoch zur Kompilierungszeit nicht ausgewertet werden kann, wird standardmäßig a verwendet Laufzeitauswertung, wie bei jeder anderen normalen Funktion.
Der Aspekt der Kompilierungszeit einer constexpr
C ++ - Funktion macht sie zu einer Art C-Makro, da die Auswertung zur Kompilierungszeit für eine constexpr
Funktion zur Kompilierungszeit erfolgt, genau wie dies bei einer MIN()
oder einer MAX()
Makrosubstitution möglich wäre zur Kompilierungszeit auch in C oder C ++ vollständig ausgewertet werden. Weitere Referenzen zu diesen C ++ - Vorlageninformationen finden Sie unten.
Clang-Notiz aus Wikipedia :
[Clang] wurde als Ersatz für die GNU Compiler Collection (GCC) entwickelt und unterstützt die meisten Kompilierungsflags und inoffiziellen Spracherweiterungen.
Es ist erwähnenswert, dass ich denke, wenn Sie definieren min
und max
mit dem Tertiär wie
#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))
dann das gleiche Ergebnis für den Sonderfall zu bekommen fmin(-0.0,0.0)
und fmax(-0.0,0.0)
Sie müssen die Argumente tauschen
fmax(a,b) = MAX(a,b)
fmin(a,b) = MIN(b,a)
fmin(3.0,NaN)==fmin(NaN,3.0)==fmax(3.0,NaN)==fmax(NaN,3.0)==3.0
Sieht aus wie Windef.h
(a la #include <windows.h>
) hat max
und min
(Kleinbuchstaben) Makros, die auch unter der Schwierigkeit "doppelte Bewertung" leiden, aber sie sind für diejenigen da, die ihre eigenen nicht neu rollen wollen :)
Ich weiß, dass der Typ "C" gesagt hat ... Aber 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 safe ein und keine Probleme mit dem in anderen Kommentaren erwähnten ++.
Das Maximum von zwei ganzen Zahlen a
und b
ist (int)(0.5((a+b)+abs(a-b)))
. Dies kann auch mit (double)
und fabs(a-b)
für Doppel funktionieren (ähnlich für Floats)
Am einfachsten ist es, sie als globale Funktion in einer .h
Datei zu definieren und sie jederzeit aufzurufen, wenn Ihr Programm modular mit vielen Dateien ist. Wenn nicht, double MIN(a,b){return (a<b?a:b)}
ist der einfachste Weg.
warning: expression with side-effects multiply evaluated by macro
am Ort der Verwendung ...