Mín e máx em C
Onde estão MIN
e MAX
definidos em C, Se é que estão?
Qual é a melhor maneira de implementar estes, o mais genericamente e tipo de segurança possível? (Compiler extensions/builtins for mainstream compilers preferred.)
13 answers
Não são.Onde estão
MIN
eMAX
definidos em C, Se é que estão?
Qual é a melhor maneira de implementar estes, o mais genericamente e digitar seguro possível (extensões de compilador/builtins para compiladores mainstream preferidos).
Como funções. Eu não usaria macros como #define MIN(X, Y) (((X) < (Y)) ? (X) : (Y))
, especialmente se você planeja implantar o seu código. Ou escreves o teu, usas algo como padrão.fmax
ou fmin
, ou corrigir a macro usando o tipo de letra do GCC (Você também recebe bónus de segurança do tipo de letra):
#define max(a,b) \
({ __typeof__ (a) _a = (a); \
__typeof__ (b) _b = (b); \
_a > _b ? _a : _b; })
Todos dizem: "sei da dupla avaliação, não há problema" e, daqui a alguns meses, vais acabar por debugar os problemas mais idiotas durante horas a fio.
Note a utilização de __typeof__
em vez de typeof
:
Se estiver a escrever um ficheiro de cabeçalho que deve funcionar quando incluído na norma ISO C programas, escrever
__typeof__
em vez detypeof
.
Também é fornecido nas versões GNU libc (Linux) e FreeBSD do sys/param.h, E tem a definição fornecida por dreamlax.
No 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.
No 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))
Os repositórios de código estão aqui:
Há um std::min
e std::max
Em C++, mas AFAIK, não há equivalente na Biblioteca Padrão de C. Você pode defini-los com macros como
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
Mas isso causa problemas se você escrever algo como MAX(++a, ++b)
.
Evitar extensões de compilador não-padrão e implementá-lo como uma macro completamente segura em puro padrão C (ISO 9899:2011).
Solução
#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))
Utilização
MAX(int, 2, 3)
Anotações
A macro MAX cria outra macro baseada no parâmetro type
. Esta macro de controle, se implementada para o tipo dado, é usada para verificar se ambos os parâmetros são do tipo correto. Se o type
não for suportado, existe será um erro de compilador.
Se x ou y não for do tipo correcto, haverá um erro de compilador nas macros ENSURE_
. Mais macros podem ser adicionados se mais tipos forem suportados. Eu assumi que somente tipos aritméticos (inteiros, flutuadores, ponteiros, etc) serão usados e não estruturas ou arrays, etc.
Se todos os tipos estiverem correctos, a macro GENERIC_MAX será chamada. Parêntesis Extra são necessários em torno de cada parâmetro de macro, como a precaução padrão habitual ao escrever C macro.
Depois há os problemas habituais com promoções implícitas de tipo C. o operador ?:
equilibra o segundo e o terceiro operando um contra o outro. Por exemplo, o resultado de GENERIC_MAX(my_char1, my_char2)
seria um int
. Para evitar que a macro fizesse tais promoções de tipo potencialmente perigosas, foi utilizado um molde final para o tipo pretendido.
Fundamentação
Queremos que ambos os parâmetros da macro sejam do mesmo tipo. Se um deles é de um tipo diferente, a macro é não mais Digite seguro, porque um operador como?:
irá produzir promoções implícitas de tipo. E porque isso acontece, nós também precisamos sempre lançar o resultado final de volta para o tipo pretendido, como explicado acima.
Uma macro com apenas um parâmetro poderia ter sido escrita de uma forma muito mais simples. Mas com 2 ou mais parâmetros, há uma necessidade de incluir um parâmetro de tipo extra. Porque algo assim é infelizmente impossível.
// this won't work
#define MAX(x, y) \
_Generic((x), \
int: GENERIC_MAX(x, ENSURE_int(y)) \
float: GENERIC_MAX(x, ENSURE_float(y)) \
)
O problema é que se a macro acima é chamado como MAX(1, 2)
com dois int
, ele ainda vai tentar macro-expandir todos os cenários possíveis da lista de associação _Generic
. Assim, a macro ENSURE_float
também será expandida, mesmo que não seja relevante para int
. E como essa macro intencionalmente só contém o tipo float
, o código não será compilado.
Exemplos
#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;
}
fmax
e fmin
(e fmaxf
para flutuadores e fmaxl
para duplos longos).
Pode implementá-los como macros, desde que esteja ciente das questões dos efeitos secundários/dupla avaliação.
#define MAX(a,b) ((a) > (b) ? a : b)
#define MIN(a,b) ((a) < (b) ? a : b)
Na maioria dos casos, você pode deixar para o compilador determinar o que você está tentando fazer e otimizá-lo o melhor que pode. Embora isso cause problemas quando usado como MAX(i++, j++)
, Eu duvido que haja sempre muita necessidade em verificar o máximo de valores incrementados de uma só vez. Incrementem primeiro, depois verifiquem.
typeof
-ou __typeof__
para ISO "limpo" C - existe uma solução melhor disponível a partir de gcc - 4.9.
#define max(x,y) ( \
{ __auto_type __x = (x); __auto_type __y = (y); \
__x > __y ? __x : __y; })
O benefício óbvio desta extensão é que cada argumento macro só é expandido uma vez, ao contrário da solução __typeof__
.
__auto_type
é uma forma limitada de C++11's auto
. Não pode (ou não deve?) ser usado no código C++ , embora não haja uma boa razão para não usar as capacidades de inferência do tipo superior de auto
ao usar o C++11.
Dito isto, eu suponha que não existem problemas usando esta sintaxe quando a macro está incluída num âmbito extern "C" { ... }
; por exemplo, a partir de um cabeçalho C. AFAIK, esta extensão não encontrou o seu caminho info clang
Eu escrevi esta versão que funciona para 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 você precisa de min / max para evitar um ramo caro, você não deve usar o operador ternário, pois ele irá compilar até um salto. A ligação abaixo descreve um método útil para implementar uma função min/max sem ramificar.
Http://graphics.stanford.edu/~seander/bithacks.html#IntegerMinOrMax
template<class T> T min(T a, T b) { return a < b ? a : b; }
Type safe, and no problems with the ++ mentioned in other comments.
Vale a pena salientar que acho que se definirmos min
e max
com o terciário como
#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))
Então para obter o mesmo resultado para o caso especial de fmin(-0.0,0.0)
e fmax(-0.0,0.0)
você precisa trocar os argumentos
fmax(a,b) = MAX(a,b)
fmin(a,b) = MIN(b,a)
Parece que {[[0]} (a la #include <windows.h>
) tem max
e min
(minúsculas) macros, que também sofrem da dificuldade de "dupla avaliação", mas eles estão lá para aqueles que não querem voltar a rolar os seus próprios:)
O máximo de dois inteiros a
e b
é (int)(0.5((a+b)+abs(a-b)))
. Isto também pode funcionar com (double)
e fabs(a-b)
para duplos (semelhantes para flutuadores)
A maneira mais simples é defini-lo como uma função global num ficheiro .h
, e chamá-lo sempre que quiser, se o seu programa for modular com muitos ficheiros. Se não, double MIN(a,b){return (a<b?a:b)}
é a maneira mais simples.