Dove sono MIN
e MAX
definiti in C, se non del tutto?
Qual è il modo migliore per implementarli, nel modo più generico e digitando nel modo più sicuro possibile? (Preferibilmente estensioni / builtin del compilatore per i compilatori mainstream.)
Dove sono MIN
e MAX
definiti in C, se non del tutto?
Qual è il modo migliore per implementarli, nel modo più generico e digitando nel modo più sicuro possibile? (Preferibilmente estensioni / builtin del compilatore per i compilatori mainstream.)
Risposte:
Dove sono
MIN
eMAX
definiti in C, se non del tutto?
Non lo sono.
Qual è il modo migliore per implementarli, nel modo più generico e sicuro possibile (preferibilmente estensioni / compilatori per compilatori tradizionali).
Come funzioni. Non userei macro come #define MIN(X, Y) (((X) < (Y)) ? (X) : (Y))
, specialmente se prevedi di distribuire il tuo codice. O scrivi tu stesso, usa qualcosa di simile fmax
o fmin
, o correggi la macro usando il tipo di GCC (ottieni anche il bonus di sicurezza della tipografia) in un'espressione dell'istruzione GCC :
#define max(a,b) \
({ __typeof__ (a) _a = (a); \
__typeof__ (b) _b = (b); \
_a > _b ? _a : _b; })
Tutti dicono "oh, conosco la doppia valutazione, non è un problema" e pochi mesi dopo, eseguirai il debug dei problemi più sciocchi per ore e ore.
Nota l'uso __typeof__
invece di typeof
:
Se stai scrivendo un file di intestazione che deve funzionare quando incluso nei programmi ISO C, scrivi
__typeof__
invece ditypeof
.
decltype
parola chiave MSVC ++ 2010 - ma anche così, Visual Studio non può fare istruzioni composte nelle macro (ed decltype
è C ++), ovvero la ({ ... })
sintassi di GCC, quindi sono abbastanza sicuro che non sia possibile, comunque. Non ho esaminato nessun altro compilatore in merito a questo problema, mi dispiace Luther: S
MAX(someUpperBound, someRandomFunction())
per limitare un valore casuale a un limite superiore. Era un'idea terribile, ma non funzionava nemmeno, perché il MAX
suo utilizzo aveva il doppio problema di valutazione, quindi ha finito con un numero casuale diverso da quello inizialmente valutato.
MIN(x++, y++)
il preprocessore genererà il seguente codice (((x++) < (y++)) ? (x++) : (y++))
. Quindi, x
e y
verrà incrementato due volte.
È anche fornito nelle versioni GNU libc (Linux) e FreeBSD di sys / param.h e ha la definizione fornita da dreamlax.
Su 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.
Su 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))
I repository di origine sono qui:
openSUSE/Linux 3.1.0-1.2-desktop
/ gcc version 4.6.2 (SUSE Linux)
. :) Bad non è portatile.
C'è un std::min
e std::max
in C ++, ma AFAIK, non c'è equivalente nella libreria C standard. Puoi definirli tu stesso con macro come
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
Ma questo causa problemi se scrivi qualcosa del genere MAX(++a, ++b)
.
#define MIN(A, B) ((A < B) ? A : B)
non è un modo flessibile, perché ???
#define MULT(x, y) x * y
. Quindi si MULT(a + b, a + b)
espande a a + b * a + b
, che viene analizzato come a + (b * a) + b
dovuto alla precedenza. Non è quello che probabilmente intendeva il programmatore.
Evita le estensioni del compilatore non standard e implementale come una macro completamente sicura in standard C puro (ISO 9899: 2011).
Soluzione
#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))
uso
MAX(int, 2, 3)
Spiegazione
La macro MAX crea un'altra macro in base al type
parametro. Questa macro di controllo, se implementata per il tipo specificato, viene utilizzata per verificare che entrambi i parametri siano del tipo corretto. Se latype
non è supportato, si verificherà un errore del compilatore.
Se x o y non sono del tipo corretto, si verificherà un errore del compilatore in ENSURE_
macro. È possibile aggiungere più macro di questo tipo se sono supportati più tipi. Ho ipotizzato che verranno utilizzati solo tipi aritmetici (numeri interi, float, puntatori ecc.) E non strutture o matrici ecc.
Se tutti i tipi sono corretti, verrà chiamata la macro GENERIC_MAX. Sono necessarie parentesi extra attorno a ciascun parametro macro, come la consueta precauzione standard quando si scrivono macro C.
Poi ci sono i soliti problemi con le promozioni di tipo implicito in C. L' ?:
operatore bilancia il 2 ° e il 3 ° operando l'uno contro l'altro. Ad esempio, il risultato di GENERIC_MAX(my_char1, my_char2)
sarebbe un int
. Per impedire alla macro di fare promozioni di tipo potenzialmente pericoloso, è stato utilizzato un cast di tipo finale per il tipo previsto.
Fondamento logico
Vogliamo che entrambi i parametri della macro siano dello stesso tipo. Se uno di questi è di tipo diverso, la macro non è più sicura, perché piace a un operatore?:
produrrà promozioni di tipo implicito. E poiché lo fa, dobbiamo anche sempre riportare il risultato finale al tipo previsto, come spiegato sopra.
Una macro con un solo parametro avrebbe potuto essere scritta in un modo molto più semplice. Ma con 2 o più parametri, è necessario includere un parametro di tipo aggiuntivo. Perché qualcosa del genere è purtroppo impossibile:
// this won't work
#define MAX(x, y) \
_Generic((x), \
int: GENERIC_MAX(x, ENSURE_int(y)) \
float: GENERIC_MAX(x, ENSURE_float(y)) \
)
Il problema è che se la macro sopra viene chiamata come MAX(1, 2)
con due int
, tenterà comunque di espandere macro tutti gli scenari possibili _Generic
dell'elenco delle associazioni. Quindi anche la ENSURE_float
macro verrà espansa, anche se non è rilevante int
. E poiché quella macro contiene solo intenzionalmente il float
tipo, il codice non verrà compilato.
Per risolvere questo problema, ho creato il nome della macro durante la fase di pre-processore, con l'operatore ##, in modo che nessuna macro venga espansa accidentalmente.
Esempi
#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
proposito, quella macro è una cattiva idea, devi solo cercare GENERIC_MAX(var++, 7)
di scoprire perché :-) Al giorno d'oggi (specialmente con compilatori fortemente ottimizzati / integrati), le macro dovrebbero praticamente essere relegate solo ai semplici moduli. Quelle simili a funzioni sono migliori come funzioni e quelle di gruppi di valori migliori come enumerazioni.
Non penso che siano macro standardizzate. Esistono già funzioni standardizzate per il virgola mobile fmax
e fmin
(e fmaxf
per i galleggianti e fmaxl
per i doppi lunghi).
Puoi implementarli come macro purché tu sia a conoscenza dei problemi degli effetti collaterali / doppia valutazione.
#define MAX(a,b) ((a) > (b) ? a : b)
#define MIN(a,b) ((a) < (b) ? a : b)
Nella maggior parte dei casi, puoi lasciarlo al compilatore per determinare cosa stai cercando di fare e ottimizzarlo nel miglior modo possibile. Sebbene ciò causi problemi se usato in questo modo MAX(i++, j++)
, dubito che ci sia sempre molto bisogno di controllare il massimo dei valori incrementati in una volta sola. Incrementa prima, quindi controlla.
Questa è una risposta tardiva, a causa di uno sviluppo abbastanza recente. Poiché l'OP ha accettato la risposta che si basa su un'estensione GCC (e clang) non portatile typeof
- o __typeof__
per ISO C "pulito" - è disponibile una soluzione migliore a partire da gcc-4.9 .
#define max(x,y) ( \
{ __auto_type __x = (x); __auto_type __y = (y); \
__x > __y ? __x : __y; })
L'ovvio vantaggio di questa estensione è che ogni argomento macro viene espanso solo una volta, a differenza della __typeof__
soluzione.
__auto_type
è una forma limitata di C ++ 11 auto
. Non può (o non dovrebbe essere?) Essere usato nel codice C ++, sebbene non ci siano buoni motivi per non usare le capacità di inferenza di tipo superiore auto
quando si usa C ++ 11.
Detto questo, suppongo che non ci siano problemi nell'uso di questa sintassi quando la macro è inclusa in un extern "C" { ... }
ambito; ad esempio, da un'intestazione C. AFAIK, questa estensione non ha trovato il modo in cui il clang delle informazioni
clang
iniziato a supportare __auto_type
intorno al 2016 (vedi patch ).
c-preprocessor
tag. Non è garantito che una funzione sia integrata anche con detta parola chiave, a meno che non si usi qualcosa come l' __always_inline__
attributo gcc .
Ho scritto questa versione che funziona per MSVC, GCC, C e 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
Se hai bisogno di min / max per evitare un ramo costoso, non dovresti usare l'operatore ternario, poiché si compila in un salto. Il collegamento seguente descrive un metodo utile per implementare una funzione min / max senza diramazione.
http://graphics.stanford.edu/~seander/bithacks.html#IntegerMinOrMax
@David Titarenco l'ha inchiodato qui , ma almeno lasciami ripulirlo un po 'per farlo sembrare bello, e mostrare entrambi min()
e max()
insieme per rendere più facile la copia e l'incollaggio da qui. :)
Aggiornamento 25 aprile 2020: ho anche aggiunto una Sezione 3 per mostrare come ciò sarebbe possibile anche con i modelli C ++, come un prezioso confronto per coloro che imparano sia il C che il C ++ o che passano dall'uno all'altro. Ho fatto del mio meglio per essere accurato, fattuale e corretto per rendere questa risposta un riferimento canonico a cui posso tornare ancora e ancora, e spero che la troviate utile come me.
Questa tecnica è comunemente usata, ben rispettata da coloro che sanno come usarla correttamente, il modo "de facto" di fare le cose, e va bene se usata correttamente, ma buggy (pensate: effetto collaterale a doppia valutazione ) se mai passare espressioni tra cui assegnazione variabile per confrontare:
#define MAX(a,b) ((a) > (b) ? (a) : (b))
#define MIN(a,b) ((a) < (b) ? (a) : (b))
Questa tecnica evita gli effetti collaterali e gli errori "doppia valutazione" sopra indicati ed è quindi considerata il modo GCC C superiore, più sicuro e "più moderno" per farlo. Aspettatevi che funzioni con entrambi i compilatori gcc e clang, dal momento che clang è, per progettazione, compatibile con gcc (vedere la nota clang in fondo a questa risposta).
MA: fai attenzione agli effetti di " ombreggiatura variabile ", poiché le espressioni delle istruzioni sono apparentemente in linea e quindi NON hanno il loro ambito variabile 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; \
})
Si noti che nelle espressioni dell'istruzione gcc, l' ultima espressione nel blocco di codice è ciò che viene "restituito" dall'espressione, come se fosse restituito da una funzione. La documentazione di GCC lo dice in questo modo:
L'ultima cosa nell'istruzione composta dovrebbe essere un'espressione seguita da un punto e virgola; il valore di questa sottoespressione funge da valore dell'intero costrutto. (Se si utilizza un altro tipo di istruzione ultimo tra parentesi graffe, il costrutto ha il tipo vuoto, e quindi effettivamente nessun valore.)
Nota C ++: se si utilizza C ++, i modelli sono probabilmente consigliati per questo tipo di costrutto, ma personalmente non mi piacciono i modelli e probabilmente utilizzerei comunque uno dei costrutti sopra in C ++, dato che spesso uso e preferisco gli stili C anche nel C ++ incorporato.
Questa sezione ha aggiunto il 25 aprile 2020:
Ho fatto un sacco di C ++ negli ultimi mesi e la pressione di preferire i template alle macro, ove possibile, nella comunità C ++ è piuttosto forte. Di conseguenza, sto migliorando nell'uso dei modelli e desidero inserire qui le versioni dei modelli C ++ per completezza e rendere questa una risposta più canonica e completa.
Ecco quali versioni di base del modello di funzionemax()
e min()
potrebbero apparire in 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;
}
Fai ulteriori letture sui modelli C ++ qui: Wikipedia: Template (C ++) .
Tuttavia, entrambi max()
e min()
fanno già parte della libreria standard C ++, <algorithm>
nell'intestazione ( #include <algorithm>
). Nella libreria standard C ++ sono definiti in modo leggermente diverso rispetto a quelli che ho sopra. I prototipi predefiniti per std::max<>()
e std::min<>()
, ad esempio, in C ++ 14, osservando i loro prototipi nei collegamenti cplusplus.com appena sopra, sono:
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);
Tieni presente che la parola chiave typename
è un alias class
(quindi il loro utilizzo è identico sia che tu lo dica <typename T>
o <class T>
), poiché è stato successivamente riconosciuto dopo l'invenzione dei modelli C ++, che il tipo di modello potrebbe essere un tipo regolare ( int
,float
, etc.) anziché soltanto un tipo di classe.
Qui puoi vedere che sono entrambi i tipi di input, così come il tipo restituito const T&
, che significa "riferimento costante al tipo T
". Ciò significa che i parametri di input e il valore restituito vengono passati per riferimento anziché passati per valore . È come passare da puntatori ed è più efficiente per tipi di grandi dimensioni, come gli oggetti di classe. La constexpr
parte della funzione modifica la funzione stessa e indica che la funzione deve essere in grado di essere valutata in fase di compilazione (almeno se vengono forniti i constexpr
parametri di input), ma se non può essere valutata in fase di compilazione, per impostazione predefinita torna a valutazione di runtime, come qualsiasi altra normale funzione.
L'aspetto in fase di compilazione di una constexpr
funzione C ++ lo rende un po 'simile a C-macro, in quanto se una valutazione in fase di compilazione è possibile per una constexpr
funzione, verrà eseguita in fase di compilazione, come potrebbe essere una sostituzione MIN()
o MAX()
macro essere valutato completamente in fase di compilazione anche in C o C ++. Per ulteriori riferimenti per queste informazioni sul modello C ++, vedere di seguito.
Nota Clang da Wikipedia :
[Clang] è progettato per fungere da sostituto drop-in per la GNU Compiler Collection (GCC), supportando la maggior parte dei suoi flag di compilazione e estensioni di linguaggio non ufficiali.
Vale la pena sottolineare che penso che se si definisce min
e max
con il terziario come
#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))
quindi per ottenere lo stesso risultato per il caso speciale di fmin(-0.0,0.0)
ed fmax(-0.0,0.0)
è necessario scambiare gli argomenti
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
Sembra che Windef.h
(a la #include <windows.h>
) abbia max
e min
(minuscole) macro, che soffrono anche della difficoltà della "doppia valutazione", ma sono lì per coloro che non vogliono ripetere il rollback :)
So che il ragazzo ha detto "C" ... Ma se ne hai la possibilità, usa un modello C ++:
template<class T> T min(T a, T b) { return a < b ? a : b; }
Digitare sicuro e nessun problema con il ++ menzionato in altri commenti.
Il massimo di due numeri interi a
ed b
è (int)(0.5((a+b)+abs(a-b)))
. Questo può funzionare anche con (double)
e fabs(a-b)
per i doppi (simile per i float)
Il modo più semplice è definirlo come una funzione globale in un .h
file e chiamarlo quando vuoi, se il tuo programma è modulare con molti file. In caso contrario, double MIN(a,b){return (a<b?a:b)}
è il modo più semplice.
warning: expression with side-effects multiply evaluated by macro
nel punto di utilizzo ...