MIN и MAX в C - PullRequest
       104

MIN и MAX в C

249 голосов
/ 09 августа 2010

Где в C определены MIN и MAX, если вообще определены?

Каков наилучший способ их реализации, максимально общий и безопасный для ввода?(Расширения / встроенные компиляторы для основных компиляторов предпочтительнее.)

Ответы [ 14 ]

336 голосов
/ 09 августа 2010

Где MIN и MAX определены в C, если вообще указаны?

Это не так.

Каков наилучший способ их реализации, максимально общий и безопасный для типов (предпочтительны расширения / встроенные компиляторы для основных компиляторов).

как функции. Я бы не использовал такие макросы, как #define MIN(X, Y) (((X) < (Y)) ? (X) : (Y)), особенно если вы планируете развернуть свой код. Либо напишите свой собственный, используйте что-то вроде стандартного fmax или fmin, либо исправьте макрос, используя typeof * GCC GCC (вы также получаете бонус безопасности типов):

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

Все говорят: «О, я знаю о двойной оценке, это не проблема», и через несколько месяцев вы будете отлаживать самые глупые проблемы часами подряд.

Обратите внимание на использование __typeof__ вместо typeof:

Если вы пишете заголовочный файл, который должен работать, когда включен в ISO C программы, пишите __typeof__ вместо typeof.

84 голосов
/ 09 августа 2010

Он также предоставляется в версиях sys / param.h для GNU libc (Linux) и FreeBSD и имеет определение, предоставленное dreamlax.


В 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.

На 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))

Здесь находятся исходные репозитории:

69 голосов
/ 09 августа 2010

В C ++ есть std::min и std::max, но AFAIK, в стандартной библиотеке C нет эквивалента. Вы можете определить их самостоятельно с помощью макросов, таких как

#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define MIN(x, y) (((x) < (y)) ? (x) : (y))

Но это вызывает проблемы, если вы напишите что-то вроде MAX(++a, ++b).

20 голосов
/ 18 июня 2015

Избегайте нестандартных расширений компилятора и реализуйте его как полностью безопасный для типов макрос в чистом стандарте C (ISO 9899: 2011).

Решение

#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))

Использование

MAX(int, 2, 3)

Объяснение

Макрос MAX создает другой макрос на основе параметра type. Этот управляющий макрос, если он реализован для данного типа, используется для проверки того, что оба параметра имеют правильный тип. Если type не поддерживается, будет ошибка компилятора.

Если x или y имеет неправильный тип, в макросах ENSURE_ будет ошибка компилятора. Можно добавить больше таких макросов, если поддерживается больше типов. Я предполагал, что будут использоваться только арифметические типы (целые числа, числа с плавающей точкой, указатели и т. Д.), А не структуры или массивы и т. Д.

Если все типы верны, будет вызван макрос GENERIC_MAX. Вокруг каждого макропараметра требуются дополнительные скобки, как обычная стандартная мера предосторожности при написании макросов C.

Тогда есть обычные проблемы с неявным продвижением типов в C. Оператор ?: балансирует 2-й и 3-й операнд друг против друга. Например, результатом GENERIC_MAX(my_char1, my_char2) будет int. Чтобы макрос не мог выполнять такие потенциально опасные продвижения типа, использовался финальный тип, приведенный к предполагаемому типу.

Обоснование

Мы хотим, чтобы оба параметра в макросе были одного типа. Если один из них имеет другой тип, макрос больше не является безопасным по типу, потому что такой оператор, как ?:, приведет к неявному продвижению типа. И поскольку это так, мы также всегда должны приводить конечный результат обратно к намеченному типу, как описано выше.

Макрос с одним параметром мог бы быть написан гораздо проще. Но с двумя или более параметрами необходимо включить дополнительный параметр типа. Потому что что-то подобное, к сожалению, невозможно:

// this won't work
#define MAX(x, y)                                  \
  _Generic((x),                                    \
           int: GENERIC_MAX(x, ENSURE_int(y))      \
           float: GENERIC_MAX(x, ENSURE_float(y))  \
          )

Проблема в том, что если указанный выше макрос вызывается как MAX(1, 2) с двумя int, он все равно попытается расширить все возможные сценарии списка ассоциаций _Generic. Таким образом, макрос ENSURE_float также будет расширен, даже если он не относится к int. А поскольку этот макрос намеренно содержит только тип float, код не будет компилироваться.

Чтобы решить эту проблему, я вместо этого создал имя макроса на этапе предварительной обработки с помощью оператора ##, чтобы случайно не раскрыть ни один макрос.

Примеры

#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 голосов
/ 09 августа 2010

Я не думаю, что это стандартизированные макросы. Уже есть стандартизированные функции для чисел с плавающей запятой, fmax и fminfmaxf для чисел с плавающей запятой, fmaxl для длинных двойных)

Вы можете реализовать их как макросы, если вам известны проблемы побочных эффектов / двойной оценки.

#define MAX(a,b) ((a) > (b) ? a : b)
#define MIN(a,b) ((a) < (b) ? a : b)

В большинстве случаев вы можете оставить это на усмотрение компилятора, чтобы определить, что вы пытаетесь сделать, и оптимизировать его как можно лучше. Хотя это вызывает проблемы при использовании, например MAX(i++, j++), я сомневаюсь, что когда-либо возникает необходимость в проверке максимума увеличенных значений за один раз. Сначала увеличьте, затем проверьте.

18 голосов
/ 20 августа 2015

Это поздний ответ, из-за довольно недавнего развития. Поскольку OP принял ответ, основанный на непереносимом расширении GCC (и clang) typeof - или __typeof__ для «чистого» ISO C - существует лучшее решение с gcc-4.9 .

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

Очевидное преимущество этого расширения состоит в том, что каждый аргумент макроса раскрывается только один раз, в отличие от решения __typeof__.

__auto_type - это ограниченная форма C ++ 11 auto. Он не может (или не должен?) Использоваться в коде C ++, хотя нет веской причины не использовать возможности превосходного вывода типов auto при использовании C ++ 11.

Тем не менее, я предполагаю нет проблем с использованием этого синтаксиса, когда макрос включен в область действия extern "C" { ... }; например, из заголовка C. AFAIK, это расширение не нашло свой путь info clang

11 голосов
/ 03 января 2012

Я написал эту версию , которая работает для MSVC, GCC, C и 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
8 голосов
/ 19 сентября 2011

Если вам нужно мин / макс, чтобы избежать дорогостоящей ветки, вам не следует использовать троичный оператор, так как он скомпилируется до прыжка.Ссылка ниже описывает полезный метод для реализации функции min / max без ветвления.

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

3 голосов
/ 18 июня 2015

Стоит отметить, я думаю, что если вы определите min и max с третичным, таким как

#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))

затем, чтобы получить тот же результат для особого случая fmin(-0.0,0.0) и fmax(-0.0,0.0), вам необходимо поменять местами аргументы

fmax(a,b) = MAX(a,b)
fmin(a,b) = MIN(b,a)
3 голосов
/ 24 апреля 2012

Похоже, что Windef.h (а-ля #include <windows.h>) имеет макросы max и min (строчные буквы), которые также страдают от трудности "двойной оценки", но они там для тех, кто не не хочу перекатывать свои:)

...