396 Stimmen

MIN und MAX in C

Wo sind MIN y MAX in C definiert, wenn überhaupt?

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

498voto

David Titarenco Punkte 31667

Wo sind MIN y MAX in C definiert, wenn überhaupt?

Sie sind es nicht.

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

Als Funktionen. Ich würde keine Makros verwenden wie #define MIN(X, Y) (((X) < (Y)) ? (X) : (Y)) besonders wenn Sie planen, Ihren Code einzusetzen. Entweder Sie schreiben Ihren eigenen Code, oder Sie verwenden etwas wie Standard fmax o fmin oder fixieren Sie das Makro mit GCCs typeof (Sie erhalten auch einen Sicherheitsbonus) in einer GCC-Anweisungsausdruck :

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

Jeder sagt: "Oh, ich kenne mich mit der doppelten Auswertung aus, das ist kein Problem", und ein paar Monate später sind Sie stundenlang mit der Fehlersuche bei den albernsten Problemen beschäftigt.

Beachten Sie die Verwendung von __typeof__ anstelle von typeof :

Wenn Sie eine Header-Datei schreiben, die die in ISO-C-Programmen funktionieren muss Programmen funktionieren muss, schreiben Sie __typeof__ i typeof .

115voto

Mikel Punkte 23574

Sie ist auch in der GNU libc (Linux) und in den FreeBSD-Versionen von sys/param.h und hat die von dreamlax angegebene Definition.


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

Unter 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 Quellcode-Repositories finden Sie hier:

110voto

dan04 Punkte 82011

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

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

Dies führt jedoch zu Problemen, wenn Sie etwas schreiben wie MAX(++a, ++b) .

39voto

Gabriel Staples Punkte 20228

@David Titarenco hat es hier auf den Punkt gebracht aber lassen Sie es mich wenigstens ein bisschen aufräumen, damit es schön aussieht, und beides zeigen min() et max() zusammen, um das Kopieren und Einfügen von hier aus zu erleichtern :)

Update 25 Apr. 2020: Ich habe auch einen Abschnitt 3 hinzugefügt, um zu zeigen, wie dies auch mit C++-Templates gemacht werden kann, als wertvoller Vergleich für diejenigen, die sowohl C als auch C++ lernen oder von einem zum anderen übergehen wollen. Ich habe mein Bestes getan, um gründlich, sachlich und korrekt zu sein, um 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. Die alte C-Makro-Methode:

Diese Technik ist weit verbreitet, wird von denjenigen, die sie richtig anzuwenden wissen, respektiert, ist die "de facto"-Arbeitsweise und kann bei richtiger Anwendung gut eingesetzt werden, aber Buggy (denken: Nebeneffekt der doppelten Bewertung ) wenn Sie jemals an Ausdrücke einschließlich Variablenzuweisung zum Vergleich ein:

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

2. Der neue und verbesserte gcc " Anweisungsausdruck Weg":

Diese Technik vermeidet die oben genannten Nebenwirkungen und Fehler der "doppelten Bewertung" und gilt daher als die bessere, sicherere und "modernere" Methode. GCC C, um dies zu tun. Erwarten Sie, dass es sowohl mit dem gcc- als auch mit dem clang-Compiler funktioniert, da clang vom Design her gcc-kompatibel ist (siehe die clang-Notiz am Ende dieser Antwort).

ABER: Achten Sie auf " variable Beschattung "Auswirkungen, da Anweisungsausdrücke anscheinend inlined sind und daher NICHT ihren 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 das letzter Ausdruck im Codeblock ist das, was von dem Ausdruck "zurückgegeben" wird, so als ob es von einer Funktion zurückgegeben würde. Die GCC-Dokumentation sagt es auf diese Weise:

Das letzte Element in der zusammengesetzten Anweisung sollte ein Ausdruck sein, dem ein Semikolon folgt; der Wert dieses Unterausdrucks dient als Wert des gesamten Konstrukts. (Wenn Sie innerhalb der geschweiften Klammern als letztes eine andere Art von Anweisung verwenden, hat das Konstrukt den Typ void und damit effektiv keinen Wert).

3. [Nur C++] Der C++-Vorlagenweg:

C++ Hinweis: Wenn Sie C++ verwenden, werden für diese Art von Konstrukten wahrscheinlich stattdessen Templates empfohlen, aber ich persönlich mag Templates nicht und würde wahrscheinlich sowieso eines der oben genannten Konstrukte in C++ verwenden, da ich häufig C-Stile auch in eingebettetem C++ verwende und bevorzuge.

Dieser Abschnitt wurde am 25 Apr. 2020 hinzugefügt:

Ich habe mich in den letzten Monaten viel mit C++ beschäftigt, und der Druck in der C++-Gemeinschaft, Templates gegenüber Makros zu bevorzugen, wo immer dies möglich ist, ist ziemlich stark. Infolgedessen bin ich immer besser im Umgang mit Vorlagen geworden und möchte hier der Vollständigkeit halber die C++-Vorlagenversionen einfügen, damit dies eine kanonischere und gründlichere Antwort wird.

Das sind die Grundlagen Funktionsschablone Versionen von max() y min() in C++ aussehen könnte:

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++) .

Allerdings sind beide max() y min() sind bereits Teil der C++-Standardbibliothek, in der <algorithm> Kopfzeile ( #include <algorithm> ). In der C++-Standardbibliothek sind sie etwas anders definiert, als ich sie oben dargestellt habe. Die Standardprototypen für std::max<>() y std::min<>() in C++14 sind, wenn man sich ihre Prototypen in den oben genannten cplusplus.com-Links ansieht, zum Beispiel:

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 (ihre Verwendung ist also identisch, egal ob Sie sagen <typename T> o <class T> ), da später, nach der Erfindung der C++-Vorlagen, anerkannt wurde, dass der Vorlagentyp ein regulärer Typ sein kann ( int , float usw.) anstelle nur einer Klassenart.

Hier können Sie sehen, dass beide Eingabetypen sowie der Rückgabetyp const T& was soviel bedeutet wie "konstanter Verweis auf den Typ T ". Dies bedeutet, dass die Eingabeparameter und der Rückgabewert durch Referenz übergeben anstelle von nach Wert übergeben . Dies ist wie die Übergabe durch Zeiger und ist effizienter für große Typen, wie z. B. Klassenobjekte. Die constexpr Teil der Funktion modifiziert die Funktion selbst und zeigt an, dass die Funktion muss fähig sein die zur Kompilierzeit ausgewertet werden (zumindest wenn sie constexpr Eingabeparameter), aber wenn sie nicht zur Kompilierzeit ausgewertet werden kann, wird sie wie jede andere normale Funktion wieder zur Laufzeit ausgewertet.

Der Kompilierzeitaspekt einer constexpr C++-Funktion ist eine Art C-Makro-ähnliche Funktion, d. h., wenn die Auswertung zur Kompilierzeit für eine constexpr Funktion, wird sie zur Kompilierzeit ausgeführt, genauso wie eine MIN() o MAX() Die Makro-Substitution könnte möglicherweise auch in C oder C++ zur Kompilierzeit vollständig ausgewertet werden. Weitere Hinweise zu dieser C++-Vorlage finden Sie weiter unten.

4. [nur C++] C++ std::max()

Wenn Sie C++ verwenden, möchte ich hinzufügen, dass die eingebaute std::max() Funktion in der <algorithm> Header-Datei hat eine Vielzahl von Formen. Siehe den Abschnitt "Mögliche Implementierung" auf der Dokumentationsseite im cppreference.com Community-Wiki ( https://en.cppreference.com/w/cpp/algorithm/max ) für 4 mögliche Implementierungen für die 4 Formen von std::max() .

Normale Verwendungszwecke sind:

std::max(100, 200);

...aber wenn Sie viele Zahlen auf einmal vergleichen möchten, können Sie die 4. Klasse , die eine std::initializer_list<T> etwa so:

Funktionserklärung:

template< class T, class Compare >
constexpr T max( std::initializer_list<T> ilist, Compare comp );

Uso:

// Compare **3 or more numbers** by passing a curly-brace-initialized
// `std::initializer_list<>` to `std::max()`!:

std::max({100, 200, 300});                  // result is 300
std::max({100, 200, 300, 400});             // result is 400
std::max({100, 200, 300, 400, 500});        // result is 500
std::max({100, 200, 300, 400, 500, 600});   // result is 600
// etc.

Referenzen:

  1. https://gcc.gnu.org/onlinedocs/gcc/Typeof.html#Typeof
  2. https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html#Statement-Exprs
  3. MIN und MAX in C
  4. Zusätzliche C++ Vorlage Referenzen hinzugefügt Apr. 2020:
  5. ***** Wikipedia: Vorlage (C++) <-- Tolle zusätzliche Informationen über C++-Vorlagen!
  6. (Meine eigene Frage und Antwort): Warum ist `constexpr` Teil des C++14 Template-Prototyps für `std::max()`?
  7. Unterschied zwischen `constexpr` und `const`

Klingelzeichen aus Wikipedia :

[Clang] ist als Ersatz für die GNU Compiler Collection (GCC) gedacht und unterstützt die meisten ihrer Kompilierflags und inoffiziellen Spracherweiterungen.

Verwandt:

  1. (meine Antwort) Rundung der Ganzzahldivision (statt Abschneiden) - Ich verwende hier auch Makros, gcc/clang-Anweisungsausdrücke und C++-Vorlagen.

27voto

Lundin Punkte 171916

Vermeiden Sie nicht standardisierte Compiler-Erweiterungen und implementieren Sie es als vollständig typsicheres Makro in reinem Standard-C (ISO 9899:2011).

Solución

#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 ein weiteres Makro, das auf dem type Parameter. Dieses Kontrollmakro wird, wenn es für den gegebenen Typ implementiert ist, verwendet, um zu prüfen, ob beide Parameter vom richtigen Typ sind. Wenn der type nicht unterstützt wird, kommt es zu einem Compilerfehler.

Wenn entweder x oder y nicht vom richtigen Typ ist, kommt es zu einem Compilerfehler in der ENSURE_ Makros. Weitere solche Makros können hinzugefügt werden, wenn mehr Typen unterstützt werden. Ich bin 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 Makro GENERIC_MAX aufgerufen. Als übliche Vorsichtsmaßnahme beim Schreiben von C-Makros sind zusätzliche Klammern um jeden Makroparameter erforderlich.

Dann gibt es die üblichen Probleme mit impliziten Typ-Promotionen in C. Die ?: Operator gleicht den 2. und 3. Operanden gegeneinander aus. Zum Beispiel ist das Ergebnis von GENERIC_MAX(my_char1, my_char2) wäre ein int . Um zu verhindern, dass das Makro solche potenziell gefährlichen Typ-Promotionen durchführt, wurde ein abschließender Typ-Cast auf den beabsichtigten Typ verwendet.

Begründung

Wir wollen, dass beide Parameter des Makros vom gleichen Typ sind. Wenn einer von ihnen von einem anderen Typ ist, ist das Makro nicht mehr typsicher, weil ein Operator wie ?: führt zu impliziten Typ-Promotionen. Und da dies der Fall ist, müssen wir das Endergebnis immer in den beabsichtigten Typ zurückführen, wie oben erläutert.

Ein Makro mit nur einem Parameter hätte viel einfacher geschrieben werden können. Aber bei 2 oder mehr Parametern ist es notwendig, einen zusätzlichen Typ-Parameter einzufügen. Denn so etwas ist leider nicht möglich:

// 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 aufgerufen wird als MAX(1, 2) mit zwei int wird es dennoch versuchen, alle möglichen Szenarien der _Generic Assoziationsliste. So wird die ENSURE_float Makro wird ebenfalls erweitert, auch wenn es nicht relevant ist für int . Und da dieses Makro absichtlich nur die float Typ, wird der Code nicht kompiliert.

Um dieses Problem zu lösen, habe ich den Makronamen stattdessen während der Präprozessorphase mit dem ##-Operator erstellt, so dass 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;
}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X