Où sont MIN
et MAX
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 de
typeof
.
David Titarenco
la source
warning: expression with side-effects multiply evaluated by macro
au point d'utilisation ...decltype
mot clé MSVC ++ 2010 - mais même ainsi, Visual Studio ne peut pas faire d'instructions composées dans les macros (etdecltype
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: SMAX(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 celuiMAX
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
ety
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:
Sur FreeBSD:
Les référentiels sources sont ici:
la source
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
etstd::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 commeMais cela pose des problèmes si vous écrivez quelque chose comme
MAX(++a, ++b)
.la source
#define MIN(A, B) ((A < B) ? A : B)
n'était pas un moyen flexible, pourquoi ???#define MULT(x, y) x * y
. SeMULT(a + b, a + b)
développe ensuite ena + b * a + b
, qui est analysé ena + (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
Usage
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 deGENERIC_MAX(my_char1, my_char2)
serait unint
. 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:
Le problème est que si la macro ci-dessus est appelée comme
MAX(1, 2)
avec deuxint
, elle essaiera toujours de macro-développer tous les scénarios possibles de la_Generic
liste d'association. Ainsi, laENSURE_float
macro sera également développée, même si elle n'est pas pertinente pourint
. Et puisque cette macro ne contient intentionnellement que lefloat
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
la source
GENERIC_MAX
en passant, cette macro est une mauvaise idée, il suffit de chercherGENERIC_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
etfmin
(etfmaxf
pour les flottants et lesfmaxl
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.
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.la source
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 .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 ++ 11auto
. 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érieuresauto
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 clangla source
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 ++.
la source
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
la source
@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()
etmax()
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.
1. L'ancienne méthode macro C:
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:
2. La nouvelle et améliorée méthode gcc " expression statement ":
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!
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:
3. La manière du modèle C ++:
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()
etmin()
pourraient ressembler en C ++:Faites des lectures supplémentaires sur les modèles C ++ ici: Wikipedia: Template (C ++) .
Cependant, les deux
max()
etmin()
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 pourstd::max<>()
etstd::min<>()
, par exemple, en C ++ 14, en regardant leurs prototypes dans les liens cplusplus.com juste au-dessus, sont:Notez que le mot - clé
typename
est un aliasclass
( 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 typeT
". 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. Laconstexpr
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 siconstexpr
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 uneconstexpr
fonction, elle se fera au moment de la compilation, comme une substitution de macroMIN()
ouMAX()
ê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.Références:
Clang note de Wikipedia :
la source
Il convient de souligner que je pense que si vous définissez
min
etmax
avec le tertiaire tel quepuis pour obtenir le même résultat pour le cas spécial de
fmin(-0.0,0.0)
etfmax(-0.0,0.0)
vous devez échanger les argumentsla source
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>
) amax
etmin
(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 :)la source
Je sais que le gars a dit "C" ... Mais si vous en avez l'occasion, utilisez un modèle C ++:
Tapez safe et aucun problème avec le ++ mentionné dans d'autres commentaires.
la source
Le maximum de deux entiers
a
etb
est(int)(0.5((a+b)+abs(a-b)))
. Cela peut également fonctionner avec(double)
etfabs(a-b)
pour les doubles (similaire pour les flotteurs)la source
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.la source