¿Dónde están MIN
y se MAX
definen en C, en todo caso?
¿Cuál es la mejor manera de implementarlos de la forma más genérica posible? (Se prefieren las extensiones / compiladores del compilador para compiladores convencionales).
¿Dónde están MIN
y se MAX
definen en C, en todo caso?
¿Cuál es la mejor manera de implementarlos de la forma más genérica posible? (Se prefieren las extensiones / compiladores del compilador para compiladores convencionales).
Respuestas:
¿Dónde están
MIN
y seMAX
definen en C, en todo caso?
No lo son
¿Cuál es la mejor manera de implementarlos de la forma más genérica y segura posible? (Se prefieren las extensiones / compilaciones de compiladores para compiladores convencionales).
Como funciones. No usaría macros como #define MIN(X, Y) (((X) < (Y)) ? (X) : (Y))
, especialmente si planea implementar su código. Escriba el suyo, use algo como estándar fmax
o fmin
, o arregle la macro usando el tipo de GCC (también obtiene un bono de seguridad de tipo) en una expresión de declaración de GCC :
#define max(a,b) \
({ __typeof__ (a) _a = (a); \
__typeof__ (b) _b = (b); \
_a > _b ? _a : _b; })
Todo el mundo dice "oh, sé acerca de la doble evaluación, no hay problema" y en unos meses más adelante, estarás depurando los problemas más tontos durante horas y horas.
Tenga en cuenta el uso de en __typeof__
lugar de typeof
:
Si está escribiendo un archivo de encabezado que debe funcionar cuando se incluye en los programas ISO C, escriba en
__typeof__
lugar detypeof
.
decltype
palabra clave MSVC ++ 2010 , pero aun así, Visual Studio no puede hacer declaraciones compuestas en macros (y decltype
es C ++ de todos modos), es decir, la ({ ... })
sintaxis de GCC, así que estoy bastante seguro de que, de todos modos, no es posible. No he mirado ningún otro compilador con respecto a este tema, lo siento Luther: S
MAX(someUpperBound, someRandomFunction())
para limitar un valor aleatorio a un límite superior. Fue una idea terrible, pero tampoco funcionó, porque el MAX
que estaba usando tenía el doble problema de evaluación, por lo que terminó con un número aleatorio diferente al que se evaluó inicialmente.
MIN(x++, y++)
al preprocesador generará el siguiente código (((x++) < (y++)) ? (x++) : (y++))
. Entonces, x
y y
se incrementará dos veces.
También se proporciona en las versiones GNU libc (Linux) y FreeBSD de sys / param.h, y tiene la definición proporcionada por dreamlax.
En 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.
En 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))
Los repositorios de origen están aquí:
openSUSE/Linux 3.1.0-1.2-desktop
/ gcc version 4.6.2 (SUSE Linux)
también. :) Malo, no es portátil.
Hay un std::min
y std::max
en C ++, pero AFAIK, no hay equivalente en la biblioteca estándar de C. Puede definirlos usted mismo con macros como
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
Pero esto causa problemas si escribes algo así MAX(++a, ++b)
.
#define MIN(A, B) ((A < B) ? A : B)
que no es una forma flexible, ¿por qué?
#define MULT(x, y) x * y
. Luego se MULT(a + b, a + b)
expande a a + b * a + b
, que analiza como a + (b * a) + b
debido a la precedencia. Eso no es lo que el programador probablemente pretendía.
Evite las extensiones de compilador no estándar e impleméntelo como una macro completamente segura de tipo en estándar C puro (ISO 9899: 2011).
Solución
#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)
Explicación
La macro MAX crea otra macro basada en el type
parámetro. Esta macro de control, si se implementa para el tipo dado, se usa para verificar que ambos parámetros sean del tipo correcto. Si type
no se admite, habrá un error del compilador.
Si x o y no son del tipo correcto, habrá un error de compilación en las ENSURE_
macros. Se pueden agregar más macros de este tipo si se admiten más tipos. Supuse que solo se usarán tipos aritméticos (enteros, flotantes, punteros, etc.) y no estructuras o matrices, etc.
Si todos los tipos son correctos, se llamará a la macro GENERIC_MAX. Se necesitan paréntesis adicionales alrededor de cada parámetro macro, como la precaución estándar habitual al escribir macros C.
Luego están los problemas habituales con las promociones de tipo implícito en C. El ?:
operador equilibra el segundo y el tercer operando entre sí. Por ejemplo, el resultado de GENERIC_MAX(my_char1, my_char2)
sería un int
. Para evitar que la macro realice tales promociones de tipo potencialmente peligrosas, se utilizó una conversión de tipo final al tipo deseado.
Razón fundamental
Queremos que ambos parámetros de la macro sean del mismo tipo. Si uno de ellos es de un tipo diferente, la macro ya no es segura, ya que un operador similar ?:
generará promociones de tipo implícito. Y debido a que lo hace, también siempre necesitamos devolver el resultado final al tipo deseado como se explicó anteriormente.
Una macro con un solo parámetro podría haberse escrito de una manera mucho más simple. Pero con 2 o más parámetros, es necesario incluir un parámetro de tipo adicional. Porque algo como esto es lamentablemente imposible:
// this won't work
#define MAX(x, y) \
_Generic((x), \
int: GENERIC_MAX(x, ENSURE_int(y)) \
float: GENERIC_MAX(x, ENSURE_float(y)) \
)
El problema es que si la macro anterior se llama como MAX(1, 2)
con dos int
, aún intentará expandir macro todos los escenarios posibles de la _Generic
lista de asociación. Por lo tanto, la ENSURE_float
macro también se expandirá, aunque no sea relevante para int
. Y dado que esa macro intencionalmente solo contiene el float
tipo, el código no se compilará.
Para resolver esto, creé el nombre de la macro durante la fase de preprocesador, con el operador ##, para que ninguna macro se expanda accidentalmente.
Ejemplos
#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
cierto, esa macro es una mala idea, solo tiene que intentar GENERIC_MAX(var++, 7)
averiguar por qué :-) Hoy en día (especialmente con compiladores altamente optimizados / en línea), las macros deberían relegarse solo a los formularios simples. Las funciones similares son mejores como funciones y las de grupo de valores mejores como enumeraciones.
No creo que sean macros estandarizadas. Ya hay funciones estandarizadas para coma flotante fmax
y fmin
(y fmaxf
para flotantes y fmaxl
para dobles largos).
Puede implementarlos como macros siempre que conozca los problemas de los efectos secundarios / doble evaluación.
#define MAX(a,b) ((a) > (b) ? a : b)
#define MIN(a,b) ((a) < (b) ? a : b)
En la mayoría de los casos, puede dejarlo al compilador para determinar lo que está tratando de hacer y optimizarlo lo mejor que pueda. Si bien esto causa problemas cuando se usa como MAX(i++, j++)
, dudo que haya mucha necesidad de verificar el máximo de valores incrementales de una sola vez. Incremente primero, luego verifique.
Esta es una respuesta tardía, debido a un desarrollo bastante reciente. Dado que el OP aceptó la respuesta que se basa en una extensión GCC (y clang) no portátil typeof
, o __typeof__
para ISO C 'limpia', hay una mejor solución disponible a partir de gcc-4.9 .
#define max(x,y) ( \
{ __auto_type __x = (x); __auto_type __y = (y); \
__x > __y ? __x : __y; })
El beneficio obvio de esta extensión es que cada argumento macro solo se expande una vez, a diferencia de la __typeof__
solución.
__auto_type
es una forma limitada de C ++ 11's auto
. No puede (¿o no debería?) Usarse en código C ++, aunque no hay una buena razón para no usar las capacidades superiores de inferencia de tipos auto
cuando se usa C ++ 11.
Dicho esto, supongo que no hay problemas al usar esta sintaxis cuando la macro se incluye en un extern "C" { ... }
ámbito; por ejemplo, de un encabezado C. AFAIK, esta extensión no ha encontrado su camino información clang
clang
comenzó a respaldar __auto_type
alrededor de 2016 (ver parche ).
c-preprocessor
etiqueta. No se garantiza que una función esté en línea incluso con dicha palabra clave, a menos que se utilice algo como el __always_inline__
atributo de gcc .
Escribí esta versión que funciona para MSVC, GCC, C y 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 necesita min / max para evitar una rama costosa, no debe usar el operador ternario, ya que se compilará en un salto. El siguiente enlace describe un método útil para implementar una función min / max sin ramificación.
http://graphics.stanford.edu/~seander/bithacks.html#IntegerMinOrMax
@David Titarenco lo clavó aquí , pero permítame al menos limpiarlo un poco para que se vea bien, y mostrar ambosmin()
y max()
juntos para que sea más fácil copiar y pegar desde aquí. :)
Actualización 25 de abril de 2020: también agregué una Sección 3 para mostrar cómo se haría esto también con las plantillas de C ++, como una valiosa comparación para aquellos que aprenden C y C ++, o que hacen la transición de una a otra. He hecho todo lo posible para ser minucioso, objetivo y correcto para hacer de esta respuesta una referencia canónica a la que pueda volver una y otra vez, y espero que lo encuentres tan útil como yo.
Esta técnica es de uso común, muy respetada por aquellos que saben cómo usarla adecuadamente, la forma "de facto" de hacer las cosas, y está bien si se usa correctamente, pero con errores (piense: efecto secundario de doble evaluación ) si alguna vez pasa expresiones que incluyen asignación de variables para comparar:
#define MAX(a,b) ((a) > (b) ? (a) : (b))
#define MIN(a,b) ((a) < (b) ? (a) : (b))
Esta técnica evita los efectos secundarios y los errores anteriores de "doble evaluación" y, por lo tanto, se considera superior, más seguro y "más moderno". forma GCC C de hacerlo. Espere que funcione con los compiladores gcc y clang, ya que clang es, por diseño, compatible con gcc (vea la nota de clang al final de esta respuesta).
PERO: ¡Tenga cuidado con los efectos de " sombreado variable " todavía, ya que las expresiones de declaración aparentemente están en línea y, por lo tanto, NO tienen su propio alcance de variable local!
#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; \
})
Tenga en cuenta que en las expresiones de instrucción gcc, la última expresión en el bloque de código es lo que se "devuelve" de la expresión, como si se devolviera de una función. La documentación de GCC lo dice de esta manera:
Lo último en la declaración compuesta debe ser una expresión seguida de un punto y coma; El valor de esta subexpresión sirve como el valor de toda la construcción. (Si usa algún otro tipo de enunciado último dentro de las llaves, la construcción tiene un tipo nulo y, por lo tanto, efectivamente no tiene valor).
Nota de C ++: si usa C ++, probablemente se recomiendan plantillas para este tipo de construcción, pero personalmente no me gustan las plantillas y probablemente usaría una de las construcciones anteriores en C ++ de todos modos, ya que frecuentemente uso y prefiero los estilos C en C ++ incrustado también.
Esta sección agregó 25 de abril de 2020:
He estado haciendo un montón de C ++ en los últimos meses, y la presión para preferir plantillas en lugar de macros, donde sea posible, en la comunidad de C ++ es bastante fuerte. Como resultado, he mejorado en el uso de plantillas, y quiero incluir aquí las versiones de plantilla de C ++ para completar y hacer de esto una respuesta más canónica y exhaustiva.
A continuación, se muestran las versiones básicas de la plantilla de funcionesmax()
y min()
cómo se verían 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;
}
Lea más sobre las plantillas de C ++ aquí: Wikipedia: Plantilla (C ++) .
Sin embargo, ambos max()
y min()
ya forman parte de la biblioteca estándar de C ++, en el <algorithm>
encabezado ( #include <algorithm>
). En la biblioteca estándar de C ++ se definen de forma ligeramente diferente a la que tengo arriba. Los prototipos predeterminados para std::max<>()
y std::min<>()
, por ejemplo, en C ++ 14, mirando sus prototipos en los enlaces de cplusplus.com justo arriba, son:
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);
Tenga en cuenta que la palabra clave typename
es un alias para class
(por lo que su uso es idéntico si usted dice <typename T>
o <class T>
), ya que más tarde se reconoció después de la invención de las plantillas C ++, que el tipo de plantilla podría ser un tipo regular ( int
,float
, etc.) en lugar de solamente Un tipo de clase.
Aquí puede ver que ambos tipos de entrada, así como el tipo de retorno, son const T&
, lo que significa "referencia constante al tipo T
". Esto significa que los parámetros de entrada y el valor de retorno se pasan por referencia en lugar de pasar por valor . Esto es como pasar por punteros y es más eficiente para tipos grandes, como los objetos de clase. La constexpr
parte de la función modifica la función en sí misma e indica que la función debe poder evaluarse en tiempo de compilación (al menos si se proporcionan constexpr
parámetros de entrada), pero si no se puede evaluar en tiempo de compilación, vuelve a un valor predeterminado evaluación en tiempo de ejecución, como cualquier otra función normal.
El aspecto en tiempo de compilación de una constexpr
función C ++ hace que sea una especie de macro de C, ya que si la evaluación en tiempo de compilación es posible para una constexpr
función, se realizará en tiempo de compilación, igual que una MIN()
oMAX()
podría posiblemente la sustitución de macros ser evaluado completamente en tiempo de compilación en C o C ++ también. Para obtener referencias adicionales para esta información de plantilla de C ++, consulte a continuación.
Nota de Clang de Wikipedia :
[Clang] está diseñado para actuar como un reemplazo directo de GNU Compiler Collection (GCC), y admite la mayoría de sus indicadores de compilación y extensiones de lenguaje no oficiales.
Vale la pena señalar que creo que si define min
y max
con el terciario como
#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))
luego para obtener el mismo resultado para el caso especial de fmin(-0.0,0.0)
y fmax(-0.0,0.0)
necesita intercambiar los argumentos
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
Parece que Windef.h
(a la #include <windows.h>
) tiene max
y min
(minúsculas) macros, que también sufren la dificultad de "doble evaluación", pero están ahí para aquellos que no quieren volver a rodar las suyas :)
Sé que el tipo dijo "C" ... Pero si tienes la oportunidad, usa una plantilla de C ++:
template<class T> T min(T a, T b) { return a < b ? a : b; }
Escriba safe, y no hay problemas con el ++ mencionado en otros comentarios.
El máximo de dos enteros a
y b
es (int)(0.5((a+b)+abs(a-b)))
. Esto también puede funcionar con (double)
y fabs(a-b)
para dobles (similar para flotadores)
La forma más simple es definirlo como una función global en un .h
archivo y llamarlo cuando lo desee, si su programa es modular con muchos archivos. Si no, double MIN(a,b){return (a<b?a:b)}
es la forma más sencilla.
warning: expression with side-effects multiply evaluated by macro
en el punto de uso ...