Où sont MIN
et MAX
définis en C, le cas échéant?
Quelle est la meilleure façon de les implémenter, de manière aussi générique et sécurisée que possible? (Les extensions / buildins du compilateur pour les compilateurs traditionnels sont préférés.)
Où sont MIN
et MAX
définis en C, le cas échéant?
Quelle est la meilleure façon de les implémenter, de manière aussi générique et sécurisée que possible? (Les extensions / buildins du compilateur pour les compilateurs traditionnels sont préférés.)
Réponses:
Où sont
MIN
etMAX
définis en C, le cas échéant?
Ils ne le sont pas.
Quelle est la meilleure façon de les implémenter, de manière aussi générique et sécurisée que possible (extensions / compilations de compilateur pour les compilateurs traditionnels préférés).
Comme fonctions. Je n'utiliserais pas de macros comme #define MIN(X, Y) (((X) < (Y)) ? (X) : (Y))
, surtout si vous prévoyez de déployer votre code. Soit écrivez le vôtre, utilisez quelque chose comme standard fmax
ou fmin
, ou corrigez la macro en utilisant typeof de GCC (vous obtenez également un bonus de sécurité de type) dans une expression d'instruction 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 déboguerez 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 qui doit fonctionner lorsqu'il est inclus dans les programmes ISO C, écrivez à la
__typeof__
place detypeof
.
decltype
mot clé MSVC ++ 2010 - mais même ainsi, Visual Studio ne peut pas faire d'instructions composées dans les macros (et decltype
est de toute façon C ++), c'est-à-dire la ({ ... })
syntaxe de GCC, donc je suis sûr que ce n'est pas possible, de toute façon. Je n'ai pas regardé d'autres compilateurs concernant ce problème, désolé Luther: S
MAX(someUpperBound, someRandomFunction())
pour limiter une valeur aléatoire à une limite supérieure. C'était une idée terrible, mais cela n'a pas fonctionné non plus, car celui MAX
qu'il utilisait avait le problème de la double évaluation, alors il s'est retrouvé avec un nombre aléatoire différent de celui qui avait été initialement évalué.
MIN(x++, y++)
le préprocesseur, le code suivant sera généré (((x++) < (y++)) ? (x++) : (y++))
. Donc, x
et y
sera incrémenté deux fois.
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:
openSUSE/Linux 3.1.0-1.2-desktop
/ gcc version 4.6.2 (SUSE Linux)
aussi. :) Dommage que ce ne soit pas portable.
Il y a un std::min
et std::max
en C ++, mais AFAIK, 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)
.
#define MIN(A, B) ((A < B) ? A : B)
n'était pas un moyen flexible, pourquoi ???
#define MULT(x, y) x * y
. Se MULT(a + b, a + b)
développe ensuite en a + b * a + b
, qui est analysé en a + (b * a) + b
raison de la priorité. Ce n'est pas ce que le programmeur voulait probablement.
Évitez les extensions de compilateur non standard et implémentez-la en tant que macro de type complètement sécurisé 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))
Usage
MAX(int, 2, 3)
Explication
La macro MAX crée une autre macro basée sur le type
paramètre. 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 latype
n'est pas pris en charge, il y aura une erreur de compilation.
Si x ou y n'est pas du type correct, il y aura une erreur de compilation dans le ENSURE_
macros. D'autres macros de ce type peuvent être ajoutées si plusieurs types sont pris en charge. J'ai supposé que seuls les types arithmétiques (entiers, flottants, pointeurs, etc.) seront utilisés et non les structures ou 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 la précaution standard habituelle lors de l'écriture des macros C.
Ensuite, il y a les problèmes habituels avec les promotions de type implicites en C. L' ?:
opérateur équilibre les 2e et 3e opérandes l'un par rapport à l'autre. Par exemple, le résultat de GENERIC_MAX(my_char1, my_char2)
serait un int
. Pour empêcher la macro d'effectuer de telles promotions de type potentiellement dangereuses, une conversion de type final vers le type prévu a été utilisée.
Raisonnement
Nous voulons que les deux paramètres de la macro soient du même type. Si l'un d'eux est d'un type différent, la macro n'est plus de type sécurisé, car un opérateur comme?:
ci produira des promotions de type implicites. Et parce que c'est le cas, nous devons également toujours restituer 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 ça 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 comme MAX(1, 2)
avec deux int
, elle essaiera toujours de macro-développer tous les scénarios possibles de la _Generic
liste d'association. Ainsi, la ENSURE_float
macro sera également développée, même si elle n'est pas pertinente pour int
. Et puisque cette macro ne contient intentionnellement que le float
type, le code ne sera pas compilé.
Pour résoudre ce problème, j'ai plutôt créé le nom de la macro lors de la phase de pré-processeur, avec l'opérateur ##, afin qu'aucune macro ne soit accidentellement développée.
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;
}
GENERIC_MAX
en passant, cette macro est une mauvaise idée, il suffit de chercher GENERIC_MAX(var++, 7)
pourquoi :-) De nos jours (en particulier avec les compilateurs fortement optimisés / en ligne), les macros devraient être reléguées aux formulaires simples uniquement. Les fonctions de type sont meilleures en tant que fonctions et celles de groupes de valeurs en tant qu'énumérations.
Je ne pense pas que ce soient des macros standardisées. Il existe déjà des fonctions standardisées pour les virgules flottantes fmax
et fmin
(et fmaxf
pour les flottants et les fmaxl
longs doubles).
Vous pouvez les implémenter sous forme de macros tant que vous êtes conscient des problèmes d'effets secondaires / de 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 au compilateur le soin de déterminer ce que vous essayez de faire et de l'optimiser du mieux qu'il peut. Bien que cela pose des problèmes lorsqu'il est utilisé comme MAX(i++, j++)
, je doute qu'il soit toujours nécessaire de vérifier le maximum de valeurs incrémentées en une seule fois. Incrémentez d'abord, puis vérifiez.
Il s'agit d'une réponse tardive, en raison d'un développement assez récent. Étant donné que l'OP a accepté la réponse qui repose sur une extension GCC (et clang) non portable typeof
- ou __typeof__
pour ISO C «propre» - il existe une meilleure solution à 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 __typeof__
solution.
__auto_type
est une forme limitée de C ++ 11 auto
. 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 auto
lorsque vous utilisez C ++ 11.
Cela dit, je suppose qu'il n'y a aucun problème à utiliser cette syntaxe lorsque la macro est incluse dans une extern "C" { ... }
portée; par exemple, à partir d'un en-tête C. AFAIK, cette extension n'a pas trouvé son chemin info clang
clang
commencé à prendre en charge __auto_type
vers 2016 (voir patch ).
c-preprocessor
balise. Une fonction n'est pas garantie d'être alignée même avec ledit mot clé, à moins d'utiliser quelque chose comme l' __always_inline__
attribut de gcc .
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 pour éviter une branche coûteuse, vous ne devez pas utiliser l'opérateur ternaire, car il se compilera en un saut. Le lien ci-dessous décrit une méthode utile pour implémenter une fonction min / max sans branchement.
http://graphics.stanford.edu/~seander/bithacks.html#IntegerMinOrMax
@David Titarenco l'a cloué ici , mais permettez-moi au moins de le nettoyer un peu pour le rendre joli, et de montrer à la fois min()
et max()
ensemble pour faciliter le copier-coller à partir d'ici. :)
Mise à jour du 25 avril 2020: j'ai également ajouté une section 3 pour montrer comment cela serait également fait avec les modèles C ++, comme une comparaison utile pour ceux qui apprennent à la fois le C et le C ++, ou qui passent de l'un à l'autre. J'ai fait de mon mieux pour être complet et factuel et correct pour faire de cette réponse une référence canonique à laquelle je pourrai revenir encore et encore, et j'espère que vous la trouverez aussi utile que moi.
Cette technique est couramment utilisée, bien respectée par ceux qui savent l'utiliser correctement, la façon "de facto" de faire les choses, et très bien à utiliser si elle est utilisée correctement, mais buggy (pensez: effet secondaire à double évaluation ) si vous jamais passer des expressions, y compris l'affectation des variables pour comparer:
#define MAX(a,b) ((a) > (b) ? (a) : (b))
#define MIN(a,b) ((a) < (b) ? (a) : (b))
Cette technique évite les effets secondaires et les bogues "à double évaluation" ci-dessus et est donc considérée comme la manière GCC C supérieure, plus sûre et "plus moderne" de le faire. Attendez-vous à ce qu'il fonctionne avec les compilateurs gcc et clang, car clang est, par conception, compatible gcc (voir la note de clang au bas de cette réponse).
MAIS: Méfiez-vous toujours des effets "d' observation des variables ", car les expressions d'instructions sont apparemment alignées et n'ont donc PAS leur propre portée de variable locale!
#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; \
})
Notez que dans les expressions d'instruction gcc, la dernière expression du bloc de code est ce qui est "renvoyé" à partir de l'expression, comme si elle avait été renvoyée par une fonction. La documentation de GCC le dit ainsi:
La dernière chose dans l'instruction composée doit être une expression suivie d'un point-virgule; la valeur de cette sous-expression sert de valeur à la construction entière. (Si vous utilisez un autre type d'instruction en dernier entre les accolades, la construction a le type void, et donc aucune valeur.)
C ++ Remarque: si vous utilisez C ++, les modèles sont probablement recommandés pour ce type de construction à la place, mais personnellement je n'aime pas les modèles et utiliserais probablement l'une des constructions ci-dessus en C ++, car j'utilise et préfère souvent les styles C en C ++ incorporé également.
Cette section a ajouté le 25 avril 2020:
J'ai fait une tonne de C ++ ces derniers mois, et la pression pour préférer les modèles aux macros, si possible, dans la communauté C ++ est assez forte. En conséquence, je me suis amélioré dans l'utilisation des modèles et je souhaite insérer ici les versions des modèles C ++ pour être complet et en faire une réponse plus canonique et approfondie.
Voici ce que base modèle de la fonction versions max()
et min()
pourraient ressembler en C ++:
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;
}
Faites des lectures supplémentaires sur les modèles C ++ ici: Wikipedia: Template (C ++) .
Cependant, les deux max()
et min()
font déjà partie de la bibliothèque standard C ++, dans l'en- <algorithm>
tête ( #include <algorithm>
). Dans la bibliothèque standard C ++, ils sont définis légèrement différemment que je les ai ci-dessus. Les prototypes par défaut pour std::max<>()
et std::min<>()
, par exemple, en C ++ 14, en regardant leurs prototypes dans les liens cplusplus.com juste au-dessus, sont:
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);
Notez que le mot - clé typename
est un alias class
( de sorte que leur utilisation est identique si vous dites <typename T>
ou <class T>
), car il a été reconnu plus tard après l'invention de C ++ modèles, que le type de modèle peut être un type régulier ( int
, float
, etc.) au lieu de seulement un type de classe.
Ici, vous pouvez voir que les deux types d'entrée, ainsi que le type de retour, le sont const T&
, ce qui signifie "référence constante au type T
". Cela signifie que les paramètres d'entrée et la valeur de retour sont transmis par référence au lieu d'être transmis par valeur . Cela revient à passer par des pointeurs et est plus efficace pour les grands types, tels que les objets de classe. La constexpr
partie de la fonction modifie la fonction elle - même et indique que la fonction doit pouvoir être évaluée au moment de la compilation (au moins si constexpr
les paramètres d'entrée sont fournis ), mais si elle ne peut pas être évaluée au moment de la compilation, elle revient par défaut à un évaluation à l'exécution, comme toute autre fonction normale.
L'aspect au moment de la compilation d'une constexpr
fonction C ++ en fait une sorte de macro-C, dans la mesure où si une évaluation au moment de la compilation est possible pour une constexpr
fonction, elle se fera au moment de la compilation, comme une substitution de macro MIN()
ou MAX()
être entièrement évalué au moment de la compilation en C ou C ++ aussi. Pour des références supplémentaires pour ces informations de modèle C ++, voir ci-dessous.
Clang note de Wikipedia :
[Clang] est conçu pour agir en remplacement de la collection de compilateurs GNU (GCC), prenant en charge la plupart de ses indicateurs de compilation et extensions de langage non officielles.
Il convient de souligner que je pense 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))
puis 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)
fmin(3.0,NaN)==fmin(NaN,3.0)==fmax(3.0,NaN)==fmax(NaN,3.0)==3.0
On dirait que Windef.h
(a la #include <windows.h>
) a max
et min
(en minuscules) les macros, qui souffrent également de la difficulté de "double évaluation", mais elles sont là pour ceux qui ne veulent pas relancer les leurs :)
Je sais que le gars a dit "C" ... Mais si vous en avez l'occasion, 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.
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 flotteurs)
Le moyen le plus simple est de le définir comme une fonction globale dans un .h
fichier, et de l'appeler quand vous le souhaitez, si votre programme est modulaire avec beaucoup de fichiers. Sinon, double MIN(a,b){return (a<b?a:b)}
c'est le moyen le plus simple.
warning: expression with side-effects multiply evaluated by macro
au point d'utilisation ...