ti-enxame.com

MIN e MAX em C

Onde MIN e MAX são definidos em C, se é que são?

Qual é a melhor maneira de implementá-los, como genericamente e digite com segurança quanto possível? (Compilador extensões/builtins para compiladores mainstream preferido.)

238
Matt Joiner

Onde estão MIN e MAX definidos em C, se em tudo?

Eles não são.

Qual é a melhor maneira de implementá-los, como genericamente e digite o mais seguro possível (compilador extensões/builtins para compiladores mainstream preferido).

Como funções. Eu não usaria macros como #define MIN(X, Y) (((X) < (Y)) ? (X) : (Y)), especialmente se você planeja implantar seu código. Escreva o seu próprio, use algo como padrão fmax ou fmin , ou corrija a macro usando o tipo de letra do GCC (você recebe um bônus de segurança de tipos também):

 #define max(a,b) \
   ({ __typeof__ (a) _a = (a); \
       __typeof__ (b) _b = (b); \
     _a > _b ? _a : _b; })

Todo mundo diz "ah, eu sei sobre avaliação dupla, não há problema" e, daqui a alguns meses, você estará debugando os problemas mais idiotas por horas a fio.

Observe o uso de __typeof__ em vez de typeof:

Se você estiver escrevendo um arquivo de cabeçalho que Deve funcionar quando incluído em programas ISO C , Escreva __typeof__ em vez de typeof.

318
David Titarenco

Ele também é fornecido nas versões GNU libc (Linux) e FreeBSD do sys/param.h, e tem a definição fornecida pelo 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 origem estão aqui:

81
Mikel

Há um std::min e std::max em C++, mas AFAIK, não há equivalente na biblioteca padrão C. Você pode defini-los você mesmo 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).

67
dan04

Evite extensões de compilador não padrão e implemente-o como uma macro completamente segura para o tipo no padrão C puro (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))

uso  

MAX(int, 2, 3)

Explicação

A macro MAX cria outra macro com base no parâmetro type. Essa macro de controle, se implementada para o tipo especificado, é usada para verificar se ambos os parâmetros são do tipo correto. Se a type não for suportada, haverá um erro do compilador.

Se x ou y não for do tipo correto, haverá um erro de compilador nas macros ENSURE_. Mais macros desse tipo podem ser adicionadas se mais tipos forem suportados. Eu assumi que apenas tipos aritméticos (inteiros, flutuantes, ponteiros, etc) serão usados ​​e não estruturas ou matrizes, etc.

Se todos os tipos estiverem corretos, a macro GENERIC_MAX será chamada. Parênteses extras são necessários em torno de cada parâmetro da macro, como a precaução padrão usual ao escrever macros C.

Depois, há os problemas habituais com promoções de tipo implícito em 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 uma int. Para evitar que a macro realize promoções desse tipo potencialmente perigosas, foi usado um tipo final de conversão para o tipo desejado.

Fundamentação

Queremos que os dois parâmetros para a macro sejam do mesmo tipo. Se um deles for de um tipo diferente, a macro não será mais segura, porque um operador como ?: fornecerá promoções de tipo implícito. E como isso acontece, também sempre precisamos converter o resultado final de volta para o tipo desejado, conforme explicado acima.

Uma macro com apenas um parâmetro poderia ter sido escrita de maneira muito mais simples. Mas com 2 ou mais parâmetros, é necessário 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 for chamada como MAX(1, 2) com duas int, ela ainda tentará macroexpandir todos os possíveis cenários da lista de associações _Generic. Assim, a macro ENSURE_float também será expandida, mesmo que não seja relevante para int. E desde que a macro intencionalmente contém apenas o tipo float, o código não será compilado.

Para resolver isso, criei o nome da macro durante a fase de pré-processador, com o operador ##, para que nenhuma macro fosse expandida acidentalmente.

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;
}
19
Lundin

Não acho que sejam macros padronizadas. Existem funções padronizadas para ponto flutuante já, fmax e fmin (e fmaxf para floats e fmaxl para long doubles).

Você pode implementá-las como macros contanto que esteja ciente dos problemas de efeitos colaterais/avaliação dupla.

#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 está tentando fazer e otimizá-lo da melhor maneira possível. Embora isso cause problemas quando usado como MAX(i++, j++), duvido que haja muita necessidade de verificar o máximo de valores incrementados de uma só vez. Incremente primeiro e depois verifique.

18
dreamlax

Esta é uma resposta tardia, devido a um desenvolvimento bastante recente. Como o OP aceitou a resposta que depende de uma extensão não-portátil do GCC (e clang) typeof - ou __typeof__ para 'clean' ISO C - há uma solução melhor disponível a partir do gcc-4.9 .

#define max(x,y) ( \
    { __auto_type __x = (x); __auto_type __y = (y); \
      __x > __y ? __x : __y; })

O benefício óbvio dessa extensão é que cada argumento de macro é expandido apenas uma vez, ao contrário da solução __typeof__.

__auto_type é uma forma limitada de auto do C++ 11. Ele não pode (ou não deve?) Ser usado em código C++, embora não haja uma boa razão para não usar os recursos de inferência de tipo superior de auto ao usar o C++ 11.

Dito isto, euassume não há problemas ao usar esta sintaxe quando a macro é incluída em um escopo extern "C" { ... }; por exemplo, de um cabeçalho C. AFAIK, esta extensão não encontrou seu caminho

16
Brett Hale

Eu escrevi este version 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
11
Matt Joiner

Se você precisa de min/max para evitar uma ramificação cara, você não deve usar o operador ternário, pois ele irá compilar até um salto. O link abaixo descreve um método útil para implementar uma função min/max sem ramificação.

http://graphics.stanford.edu/~seander/bithacks.html#IntegerMinOrMax

8
cib

Eu sei que o cara disse "C" ... Mas se você tiver a chance, use um modelo C++:

template<class T> T min(T a, T b) { return a < b ? a : b; }

Digite safe e nenhum problema com o ++ mencionado em outros comentários.

4
Bas Kuenen

Vale a pena ressaltar que, se você definir 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)
3
Z boson

Parece que Windef.h (a la #include <windows.h>) tem macros max e min (minúsculas), que também sofrem com a dificuldade "double evaluation", mas estão lá para aqueles que não querem re-rolar seus próprios :)

2
rogerdpack

O máximo de dois inteiros a e b é (int)(0.5((a+b)+abs(a-b))). Isso também pode funcionar com (double) e fabs(a-b) para duplas (similar para floats)

0
NRZ

Relacionado a O comentário de Brett Hale , clang começou a suportar __auto_type por volta de 2016 (veja patch ).

0
Lars