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.)
Wo sind
MIN
undMAX
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 vontypeof
.
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:
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.
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;
}
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.
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
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
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
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.
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)
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 :)
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)
Zu Brett Hales Kommentar , clang
unterstützt __auto_type
um 2016 (siehe patch ).