MIN и MAX в C

203

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

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

Теги:
max
min
c-preprocessor

12 ответов

265
Лучший ответ

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

Это не так.

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

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

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

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

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

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

  • 61
    Вы знаете, это было бы очень удобно, если бы gcc имел предупреждение в виде: warning: expression with side-effects multiply evaluated by macro в точке использования ...
  • 16
    @caf: разве для этого не требуется, чтобы препроцессор обладал более сложным знанием синтаксиса Си?
Показать ещё 14 комментариев
71

Он также представлен в версиях 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))

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

  • 0
    Куда? Можете дать ссылку?
  • 0
    Я добавил определения из систем, к которым у меня есть доступ, в своем ответе выше (насколько я могу судить, поле комментария не принимает форматирование). Постараюсь найти ссылки на репозитории FreeBSD / Linux / glibc.
Показать ещё 3 комментария
56

Там есть 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).

  • 9
    зачем ставить слишком много скобок ??? Я нашел тест, где они сказали, что #define MIN(A, B) ((A < B) ? A : B) не гибкий способ, почему ???
  • 68
    @Makouda: дополнительные скобки в макросах помогают избежать проблем с приоритетом операторов. Например, рассмотрим #define MULT(x, y) x * y . Затем MULT(a + b, a + b) расширяется до a + b * a + b , который анализируется как a + (b * a) + b силу приоритета. Это не то, что программист, вероятно, намеревался.
Показать ещё 4 комментария
14

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

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

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

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

  • 0
    Это должен быть предпочтительный ответ, поскольку в математической библиотеке явно есть функции min и max: cplusplus.com/reference/cmath/fmax
  • 0
    @imranal О чем именно ты говоришь? Код реализации этой библиотеки? Но этот код не раскрывается , то есть они не помещают его в интерфейс библиотеки, будучи потенциально небезопасными.
Показать ещё 1 комментарий
13

Это поздний ответ из-за довольно недавнего развития. Поскольку 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 является ограниченной формой С++ 11 auto. Он не может (или не должен?) Использоваться в коде С++, хотя нет веских оснований не использовать возможности вывода превосходного типа auto при использовании С++ 11.

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

12

Избегайте нестандартных расширений компилятора и реализуйте его как полностью безопасный тип макроса в чистом стандарте 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;
}
11

Я написал эту версию, которая работает для MSVC, GCC, 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
  • 0
    Я проголосовал, но идентификаторы, начинающиеся со знака подчеркивания, следующего за заглавной буквой, зарезервированы.
8

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

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

  • 1
    Если компилятор достаточно умен, он может избежать ветвления
  • 0
    Отличная ссылка! Я люблю это.
Показать ещё 2 комментария
4

Я знаю, что парень сказал "C"... Но если у вас есть такая возможность, используйте шаблон С++:

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

Введите безопасный код и никаких проблем c++, упомянутых в других комментариях.

  • 15
    Аргументы должны быть константными ссылками, вы никогда не знаете, что пользователь передаст.
  • 4
    Такая функция уже стандартизирована ( std :: min ).
Показать ещё 1 комментарий
3

Стоит отметить, что, если вы определяете 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)
  • 0
    Все равно не будет работать для NaN. fmin(3.0,NaN)==fmin(NaN,3.0)==fmax(3.0,NaN)==fmax(NaN,3.0)==3.0
  • 0
    @greggo, я дал лучший ответ здесь stackoverflow.com/a/30915238/2542702
1

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

  • 12
    Вы даже удивлены?
0

Максимум двух целых чисел a и b равен (int)(0.5((a+b)+abs(a-b))). Это может также работать с (double) и fabs(a-b) для двойников (аналогично для float)

  • 0
    Извините, если это не так, я новичок в C, но этот код работает для меня
  • 2
    Я не уверен, что это работает с не целыми числами. Математика с плавающей точкой имеет нелинейную точность.
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню