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.)
quelle
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
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
.
warning: expression with side-effects multiply evaluated by macro
am Ort der Verwendung ...decltype
Schlüsselwort MSVC ++ 2010 herumzuspielen. Trotzdem kann Visual Studio keine zusammengesetzten Anweisungen in Makros ausführen (unddecltype
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: SMAX(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 vonMAX
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
undy
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:
Auf FreeBSD:
Die Quell-Repositorys sind hier:
quelle
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
undstd::max
in C ++, aber AFAIK, es gibt kein Äquivalent in der C-Standardbibliothek. Sie können sie selbst mit Makros wie definierenDies verursacht jedoch Probleme, wenn Sie so etwas schreiben
MAX(++a, ++b)
.quelle
#define MIN(A, B) ((A < B) ? A : B)
sei kein flexibler Weg, warum ???#define MULT(x, y) x * y
. ErweitertMULT(a + b, a + b)
sich dann zua + b * a + b
, wasa + (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
Verwendung
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 dastype
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 BeispielGENERIC_MAX(my_char1, my_char2)
wäre das Ergebnis von einint
. 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:
Das Problem ist, dass, wenn das obige Makro wie
MAX(1, 2)
bei zwei aufgerufenint
wird, immer noch versucht wird, alle möglichen Szenarien der_Generic
Zuordnungsliste zu erweitern. DasENSURE_float
Makro wird also auch erweitert, obwohl es für nicht relevant istint
. Und da dieses Makro absichtlich nur denfloat
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
quelle
GENERIC_MAX
Makro ist übrigens eine schlechte Idee, man muss nurGENERIC_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
undfmin
(und für Gleitkommazahlenfmaxf
undfmaxl
für lange Doppelbilder).Sie können sie als Makros implementieren, solange Sie sich der Probleme mit Nebenwirkungen / Doppelbewertung bewusst sind.
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.quelle
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 .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 ++ 11auto
. Es kann (oder sollte nicht?) Nicht in C ++ - Code verwendet werden, obwohl es keinen guten Grund gibt, die überlegenen Typinferenzfunktionen vonauto
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 gefundenquelle
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.
quelle
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
quelle
@ David Titarenco genagelt es hier , aber lassen Sie mich zumindest sauber es sich ein wenig machen es schön aussehen, und beide zeigen
min()
undmax()
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.
1. Der alte C-Makro-Weg:
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:
2. Der neue und verbesserte gcc " Anweisungsausdruck " Weg:
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!
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:
3. Die C ++ - Vorlagenmethode:
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()
undmin()
aussehen könnten:Lesen Sie hier mehr über C ++ - Vorlagen: Wikipedia: Vorlage (C ++) .
Beide
max()
undmin()
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ürstd::max<>()
undstd::min<>()
zum Beispiel in C ++ 14, die ihre Prototypen in den oben genannten Links zu cplusplus.com betrachten, sind:Beachten Sie, dass das Schlüsselwort
typename
ist ein Alias fürclass
(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 TypT
" 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. Derconstexpr
Teil der Funktion ändert die Funktion selbst und gibt an, dass die Funktion zur Kompilierungszeit ausgewertet werden kann (zumindest wennconstexpr
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 eineconstexpr
Funktion zur Kompilierungszeit erfolgt, genau wie dies bei einerMIN()
oder einerMAX()
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.Verweise:
Clang-Notiz aus Wikipedia :
quelle
Es ist erwähnenswert, dass ich denke, wenn Sie definieren
min
undmax
mit dem Tertiär wiedann das gleiche Ergebnis für den Sonderfall zu bekommen
fmin(-0.0,0.0)
undfmax(-0.0,0.0)
Sie müssen die Argumente tauschenquelle
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>
) hatmax
undmin
(Kleinbuchstaben) Makros, die auch unter der Schwierigkeit "doppelte Bewertung" leiden, aber sie sind für diejenigen da, die ihre eigenen nicht neu rollen wollen :)quelle
Ich weiß, dass der Typ "C" gesagt hat ... Aber wenn Sie die Chance haben, verwenden Sie eine C ++ - Vorlage:
Geben Sie safe ein und keine Probleme mit dem in anderen Kommentaren erwähnten ++.
quelle
Das Maximum von zwei ganzen Zahlen
a
undb
ist(int)(0.5((a+b)+abs(a-b)))
. Dies kann auch mit(double)
undfabs(a-b)
für Doppel funktionieren (ähnlich für Floats)quelle
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.quelle