Où MIN
et MAX
sont-ils définis en C, le cas échéant?
Quel est le meilleur moyen de les implémenter de manière aussi générique et sécurisée que possible? (Les extensions/fonctions intégrées du compilateur pour les compilateurs traditionnels sont préférées.)
Où
MIN
etMAX
sont-ils définis en C, le cas échéant?
Ils ne sont pas.
Quel est le meilleur moyen de les implémenter, de manière aussi générique et sécurisée que possible (extensions/fonctions intégrées du compilateur préférées des compilateurs traditionnels).
En tant que fonctions. Je n'utiliserais pas de macros telles que #define MIN(X, Y) (((X) < (Y)) ? (X) : (Y))
, surtout si vous envisagez de déployer votre code. Soit, écrivez le vôtre, utilisez quelque chose comme: fmax
ou fmin
, ou corrigez la macro à l'aide de typeof de GCC:
#define max(a,b) \
({ __typeof__ (a) _a = (a); \
__typeof__ (b) _b = (b); \
_a > _b ? _a : _b; })
Tout le monde dit: "Oh, je connais la double évaluation, ce n'est pas un problème" et quelques mois plus tard, vous allez résoudre les problèmes les plus stupides pendant des heures.
Notez l'utilisation de __typeof__
au lieu de typeof
:
Si vous écrivez un fichier d’en-tête que doit fonctionner lorsqu'il est inclus dans l'ISO C programmes, écrivez
__typeof__
au lieu detypeof
.
Il est également fourni dans les versions GNU libc (Linux) et FreeBSD de sys/param.h, et a la définition fournie par dreamlax.
Sur 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.
Sur 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))
Les référentiels sources sont ici:
Il y a std::min
et std::max
en C++, mais autant que je sache, il n'y a pas d'équivalent dans la bibliothèque standard C. Vous pouvez les définir vous-même avec des macros comme
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
Mais cela pose des problèmes si vous écrivez quelque chose comme MAX(++a, ++b)
.
Evitez les extensions de compilateur non standard et implémentez-le en tant que macro entièrement compatible avec le type dans la norme C pure (ISO 9899: 2011).
Solution
#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))
Utilisation
MAX(int, 2, 3)
Explication
La macro MAX crée une autre macro basée sur le paramètre type
. Cette macro de contrôle, si elle est implémentée pour le type donné, est utilisée pour vérifier que les deux paramètres sont du type correct. Si la variable type
n'est pas prise en charge, il y aura une erreur de compilation.
Si x ou y ne sont pas du type correct, il y aura une erreur de compilation dans les macros ENSURE_
. Davantage de telles macros peuvent être ajoutées si plusieurs types sont pris en charge. J'ai supposé que seuls les types arithmétiques (nombres entiers, flottants, pointeurs, etc.) seraient utilisés et non les structures, les tableaux, etc.
Si tous les types sont corrects, la macro GENERIC_MAX sera appelée. Des parenthèses supplémentaires sont nécessaires autour de chaque paramètre de macro, comme précaution habituelle lors de l’écriture de macros C.
Ensuite, il y a les problèmes habituels des promotions de type implicite dans C. L'opérateur ?:
équilibre le deuxième et le troisième opérandes l'un par rapport à l'autre. Par exemple, le résultat de GENERIC_MAX(my_char1, my_char2)
serait une int
. Pour empêcher la macro d'effectuer de telles promotions de type potentiellement dangereux, une conversion de type finale vers le type prévu a été utilisée.
Justification
Nous voulons que les deux paramètres de la macro soient du même type. Si l'un d'entre eux est d'un type différent, la macro n'est plus sécurisée au type, car un opérateur tel que ?:
générera des promotions de type implicite. Et comme c'est le cas, nous devons également toujours attribuer le résultat final au type prévu, comme expliqué ci-dessus.
Une macro avec un seul paramètre aurait pu être écrite de manière beaucoup plus simple. Mais avec 2 paramètres ou plus, il est nécessaire d'inclure un paramètre de type supplémentaire. Parce que quelque chose comme cela est malheureusement impossible:
// this won't work
#define MAX(x, y) \
_Generic((x), \
int: GENERIC_MAX(x, ENSURE_int(y)) \
float: GENERIC_MAX(x, ENSURE_float(y)) \
)
Le problème est que si la macro ci-dessus est appelée en tant que MAX(1, 2)
avec deux int
, elle tentera toujours de développer en macro tous les scénarios possibles de la liste d'associations _Generic
. Ainsi, la macro ENSURE_float
sera également développée, même si elle n’est pas pertinente pour int
. Et comme cette macro ne contient intentionnellement que le type float
, le code ne sera pas compilé.
Pour résoudre ce problème, j'ai créé le nom de la macro au cours de la phase de pré-traitement, avec l'opérateur ##, afin qu'aucune macro ne soit développée par inadvertance.
Exemples
#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;
}
Je ne pense pas que ce sont des macros standardisées. Il existe déjà des fonctions standard pour la virgule flottante, fmax
et fmin
(et fmaxf
pour les flottants et fmaxl
pour les doubles longs).
Vous pouvez les implémenter sous forme de macros tant que vous êtes conscient des problèmes liés aux effets secondaires/à la double évaluation.
#define MAX(a,b) ((a) > (b) ? a : b)
#define MIN(a,b) ((a) < (b) ? a : b)
Dans la plupart des cas, vous pouvez laisser le compilateur déterminer ce que vous essayez de faire et l’optimiser au mieux. Bien que cela pose des problèmes lorsqu’il est utilisé comme MAX(i++, j++)
, je doute qu’il soit nécessaire de vérifier le maximum de valeurs incrémentées en une fois. Incrémentez d'abord, puis vérifiez.
C'est une réponse tardive, due à un développement assez récent. Étant donné que le PO a accepté la réponse qui repose sur une extension non variable GCC (et clang) typeof
- ou __typeof__
pour "propre" ISO C, il existe une meilleure solution disponible à partir de gcc-4.9 .
#define max(x,y) ( \
{ __auto_type __x = (x); __auto_type __y = (y); \
__x > __y ? __x : __y; })
L'avantage évident de cette extension est que chaque argument de macro n'est développé qu'une seule fois, contrairement à la solution __typeof__
.
__auto_type
est une forme limitée de auto
de C++ 11. Il ne peut pas (ou ne devrait pas?) Être utilisé dans le code C++, bien qu'il n'y ait aucune bonne raison de ne pas utiliser les capacités d'inférence de type supérieures de auto
lors de l'utilisation de C++ 11.
Cela dit, je suppose qu’il n’ya pas de problème d’utilisation de cette syntaxe lorsque la macro est incluse dans une étendue extern "C" { ... }
; par exemple, à partir d'un en-tête C. Autant que je sache, cette extension n'a pas encore trouvé son chemin
J'ai écrit cette version qui fonctionne pour MSVC, GCC, C et C++.
#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
Si vous avez besoin de min/max afin d'éviter une branche coûteuse, vous ne devriez pas utiliser l'opérateur ternaire, car il compilera jusqu'à un saut. Le lien ci-dessous décrit une méthode utile pour implémenter une fonction min/max sans créer de branche.
http://graphics.stanford.edu/~seander/bithacks.html#IntegerMinOrMax
Je sais que le gars a dit "C" ... Mais si vous avez la chance, utilisez un modèle C++:
template<class T> T min(T a, T b) { return a < b ? a : b; }
Tapez safe et aucun problème avec le ++ mentionné dans d'autres commentaires.
Il vaut la peine de souligner que si vous définissez min
et max
avec le tertiaire tel que
#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))
ensuite, pour obtenir le même résultat pour le cas spécial de fmin(-0.0,0.0)
et fmax(-0.0,0.0)
, vous devez échanger les arguments
fmax(a,b) = MAX(a,b)
fmin(a,b) = MIN(b,a)
On dirait que Windef.h
(a la #include <windows.h>
) a des macros max
et min
(minuscules), qui souffrent également du problème de "double évaluation", mais ils sont là pour ceux qui ne veulent pas relancer leurs propres :)
Le maximum de deux entiers a
et b
est (int)(0.5((a+b)+abs(a-b)))
. Cela peut également fonctionner avec (double)
et fabs(a-b)
pour les doubles (similaire pour les floats)
Relatif à Le commentaire de Brett Hale , clang
a commencé à supporter __auto_type
vers 2016 (voir patch ).