¿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).
fuente
¿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 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
.
warning: expression with side-effects multiply evaluated by macro
en el punto de uso ...decltype
palabra clave MSVC ++ 2010 , pero aun así, Visual Studio no puede hacer declaraciones compuestas en macros (ydecltype
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: SMAX(someUpperBound, someRandomFunction())
para limitar un valor aleatorio a un límite superior. Fue una idea terrible, pero tampoco funcionó, porque elMAX
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
yy
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:
En FreeBSD:
Los repositorios de origen están aquí:
fuente
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
ystd::max
en C ++, pero AFAIK, no hay equivalente en la biblioteca estándar de C. Puede definirlos usted mismo con macros comoPero esto causa problemas si escribes algo así
MAX(++a, ++b)
.fuente
#define MIN(A, B) ((A < B) ? A : B)
que no es una forma flexible, ¿por qué?#define MULT(x, y) x * y
. Luego seMULT(a + b, a + b)
expande aa + b * a + b
, que analiza comoa + (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
Uso
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. Sitype
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 deGENERIC_MAX(my_char1, my_char2)
sería unint
. 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:
El problema es que si la macro anterior se llama como
MAX(1, 2)
con dosint
, aún intentará expandir macro todos los escenarios posibles de la_Generic
lista de asociación. Por lo tanto, laENSURE_float
macro también se expandirá, aunque no sea relevante paraint
. Y dado que esa macro intencionalmente solo contiene elfloat
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
fuente
GENERIC_MAX
cierto, esa macro es una mala idea, solo tiene que intentarGENERIC_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
yfmin
(yfmaxf
para flotantes yfmaxl
para dobles largos).Puede implementarlos como macros siempre que conozca los problemas de los efectos secundarios / doble evaluación.
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.fuente
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 .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'sauto
. 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 tiposauto
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 clangfuente
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 ++.
fuente
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
fuente
@David Titarenco lo clavó aquí , pero permítame al menos limpiarlo un poco para que se vea bien, y mostrar ambos
min()
ymax()
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.
1. La antigua forma macro C:
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:
2. El nuevo y mejorado gcc " expresión de declaración :
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!
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:
3. La forma de la plantilla C ++:
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 funciones
max()
ymin()
cómo se verían en C ++:Lea más sobre las plantillas de C ++ aquí: Wikipedia: Plantilla (C ++) .
Sin embargo, ambos
max()
ymin()
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 parastd::max<>()
ystd::min<>()
, por ejemplo, en C ++ 14, mirando sus prototipos en los enlaces de cplusplus.com justo arriba, son:Tenga en cuenta que la palabra clave
typename
es un alias paraclass
(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 tipoT
". 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. Laconstexpr
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 proporcionanconstexpr
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 unaconstexpr
función, se realizará en tiempo de compilación, igual que unaMIN()
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.Referencias
Nota de Clang de Wikipedia :
fuente
Vale la pena señalar que creo que si define
min
ymax
con el terciario comoluego para obtener el mismo resultado para el caso especial de
fmin(-0.0,0.0)
yfmax(-0.0,0.0)
necesita intercambiar los argumentosfuente
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>
) tienemax
ymin
(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 :)fuente
Sé que el tipo dijo "C" ... Pero si tienes la oportunidad, usa una plantilla de C ++:
Escriba safe, y no hay problemas con el ++ mencionado en otros comentarios.
fuente
El máximo de dos enteros
a
yb
es(int)(0.5((a+b)+abs(a-b)))
. Esto también puede funcionar con(double)
yfabs(a-b)
para dobles (similar para flotadores)fuente
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.fuente