MIN и MAX в C
Где MIN
а также MAX
определяется в C, если вообще?
Каков наилучший способ реализовать их как можно более обобщенно и безопасно? (Расширения / встроенные компиляторы для основных компиляторов предпочтительнее.)
18 ответов
Где
MIN
а такжеMAX
определяется в C, если вообще?
Это не так.
Каков наилучший способ их реализации, максимально общий и безопасный для типов (предпочтительны расширения / встроенные компиляторы для основных компиляторов).
Как функции. Я бы не стал использовать такие макросы, как #define MIN(X, Y) (((X) < (Y)) ? (X) : (Y))
особенно если вы планируете развернуть свой код. Либо напишите свой, используйте что-то вроде стандарта fmax
или же fmin
или исправьте макрос, используя typeof GCC (вы также получаете бонус безопасности типов):
#define max(a,b) \
({ __typeof__ (a) _a = (a); \
__typeof__ (b) _b = (b); \
_a > _b ? _a : _b; })
Все говорят: "О, я знаю о двойной оценке, это не проблема", и через несколько месяцев вы будете часами отлаживать самые глупые проблемы.
Обратите внимание на использование __typeof__
вместо typeof
:
Если вы пишете заголовочный файл, который должен работать при включении в программы ISO C, напишите
__typeof__
вместоtypeof
,
Он также предоставляется в версиях 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))
Исходные репозитории находятся здесь:
Есть std::min
а также std::max
в C++, но AFAIK, в стандартной библиотеке C нет аналога. Вы можете определить их самостоятельно с помощью макросов, таких как
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
Но это вызывает проблемы, если вы пишете что-то вроде MAX(++a, ++b)
,
@David Titarenco прибил это здесь, но позвольте мне хотя бы немного очистить его, чтобы он выглядел красиво, и показать обаmin()
а также max()
вместе, чтобы упростить копирование и вставку отсюда.:)
Обновление 25 апреля 2020 г.: Я также добавил раздел 3, чтобы показать, как это можно сделать и с шаблонами C++, в качестве ценного сравнения для тех, кто изучает C и C++ или переходит от одного к другому. Я приложил все усилия, чтобы быть тщательным, фактическим и правильным, чтобы сделать этот ответ каноническим справочником, к которому я могу возвращаться снова и снова, и я надеюсь, что вы найдете его таким же полезным, как и я.
1. Старый способ макроса C:
Этот метод широко используется, пользуется большим уважением среди тех, кто знает, как его правильно использовать, "де-факто" способ делать что-то, и его можно использовать правильно, но с ошибками (подумайте: побочный эффект двойной оценки), если вы когда-либо передавайте выражения, включая присвоение переменных, для сравнения:
#define MAX(a,b) ((a) > (b) ? (a) : (b))
#define MIN(a,b) ((a) < (b) ? (a) : (b))
2. Новый и улучшенный способ выражения оператора gcc:
Этот метод позволяет избежать вышеупомянутых побочных эффектов и ошибок "двойной оценки" и поэтому считается лучшим, более безопасным и "более современным" способом GCC C. Ожидайте, что он будет работать как с компиляторами gcc, так и с clang, поскольку clang по дизайну gcc-совместим (см. Примечание о clang в нижней части этого ответа).
НО: ОБЯЗАТЕЛЬНО следите за эффектами " затенения переменных", поскольку выражения операторов явно встроены и поэтому НЕ имеют собственной области локальных переменных!
#define max(a,b) \
({ \
__typeof__ (a) _a = (a); \
__typeof__ (b) _b = (b); \
_a > _b ? _a : _b; \
})
#define min(a,b) \
({ \
__typeof__ (a) _a = (a); \
__typeof__ (b) _b = (b); \
_a < _b ? _a : _b; \
})
Обратите внимание, что в выражениях операторов gcc последнее выражение в блоке кода - это то, что "возвращается" из выражения, как если бы оно было возвращено функцией. Документация GCC говорит об этом так:
Последним элементом составного оператора должно быть выражение, за которым следует точка с запятой; значение этого подвыражения служит значением всей конструкции. (Если последним в фигурных скобках вы используете какой-либо другой оператор, конструкция имеет тип void и, следовательно, фактически не имеет значения.)
3. Способ шаблона C++:
Примечание для C++: при использовании C++ для этого типа конструкции, вероятно, рекомендуется использовать шаблоны, но мне лично не нравятся шаблоны, и я, вероятно, все равно использовал бы одну из указанных выше конструкций в C++, поскольку я часто использую и предпочитаю стили C во встроенном C++.
В этот раздел добавлено 25 апреля 2020 г.:
Я много занимался C++ за последние несколько месяцев, и в сообществе C++ наблюдается довольно сильное давление, чтобы предпочесть шаблоны макросам, где это возможно. В результате я стал лучше пользоваться шаблонами и хочу добавить сюда версии шаблонов C++ для полноты и сделать этот ответ более каноническим и подробным.
Вот какие базовые версии шаблонов функцийmax()
а также min()
может выглядеть как на C++:
template <typename T>
T max(T a, T b)
{
return a > b ? a : b;
}
template <typename T>
T min(T a, T b)
{
return a < b ? a : b;
}
Дополнительную информацию о шаблонах C++ можно найти здесь: Wikipedia: Template (C++).
Однако оба max()
а также min()
уже являются частью стандартной библиотеки C++, в <algorithm>
заголовок (#include <algorithm>
). В стандартной библиотеке C++ они определены немного иначе, чем у меня выше. Прототипы по умолчанию дляstd::max<>()
а также std::min<>()
Например, в C++14 их прототипы в приведенных выше ссылках cplusplus.com выглядят так:
template <class T>
constexpr const T& max(const T& a, const T& b);
template <class T>
constexpr const T& min(const T& a, const T& b);
Обратите внимание, что ключевое слово typename
это псевдоним для class
(поэтому их использование идентично, говорите ли вы <typename T>
или <class T>
), поскольку позже после изобретения шаблонов C++ было признано, что тип шаблона может быть обычным типом (int
, float
и т. д.), а не только тип класса.
Здесь вы можете видеть, что оба типа ввода, а также возвращаемый тип являются const T&
, что означает "постоянная ссылка на тип T
". Это означает, что входные параметры и возвращаемое значение передаются по ссылке, а не по значению. Это похоже на передачу по указателям и более эффективно для больших типов, таких как объекты классов.constexpr
часть функции изменяет саму функцию и указывает, что функция должна быть способна быть оценена во время компиляции (по крайней мере, если предоставленоconstexpr
входные параметры), но если он не может быть оценен во время компиляции, он по умолчанию возвращается к оценке времени выполнения, как и любая другая обычная функция.
Аспект времени компиляции constexpr
Функция C++ делает ее чем-то вроде C-макроса, в том смысле, что, если оценка времени компиляции возможна для constexpr
функция, это будет сделано во время компиляции, так же, как MIN()
или MAX()
Подстановка макросов может быть полностью оценена во время компиляции и на C или C++. Дополнительные ссылки на эту информацию о шаблоне C++ см. Ниже.
Ссылки:
- https://gcc.gnu.org/onlinedocs/gcc/Typeof.html
- https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html
- MIN и MAX в C
- Дополнительные ссылки на шаблоны C++ добавлены в апреле 2020 г.:
- ***** Википедия: Шаблон (C++) <- БОЛЬШАЯ дополнительная информация о шаблонах C++!
- (Мой собственный вопрос и ответ): Почему constexpr является частью прототипа шаблона C++14 для std::max()?
- Разница между constexpr и const
Заметка Clang из Википедии:
[Clang] предназначен для использования в качестве замены GNU Compiler Collection (GCC), поддерживая большинство его флагов компиляции и неофициальных языковых расширений.
Избегайте нестандартных расширений компилятора и реализуйте его как полностью безопасный для типов макрос в чистом стандарте 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. ?:
оператор уравновешивает 2-й и 3-й операнды друг против друга. Например, результат GENERIC_MAX(my_char1, my_char2)
будет int
, Чтобы макрос не мог выполнять такие потенциально опасные продвижения типа, использовался финальный тип, приведенный к предполагаемому типу.
обоснование
Мы хотим, чтобы оба параметра в макросе были одного типа. Если один из них имеет другой тип, макрос больше не является безопасным типом, потому что такой оператор, как ?:
приведет к неявным акциям типа. И поскольку это так, мы также всегда должны приводить конечный результат обратно к намеченному типу, как описано выше.
Макрос с одним параметром мог бы быть написан намного проще. Но с 2 или более параметрами необходимо включить дополнительный параметр типа. Потому что что-то подобное, к сожалению, невозможно:
// 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;
}
Это поздний ответ, из-за довольно недавнего развития. Поскольку ФП принял ответ, основанный на непереносимом расширении 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, это расширение не нашло свой путь
Я не думаю, что это стандартизированные макросы. Уже есть стандартизированные функции для плавающей запятой, fmax
а также fmin
(а также fmaxf
для поплавков и fmaxl
для длинных пар).
Вы можете реализовать их как макросы, если вы знаете о побочных эффектах / двойной оценке.
#define MAX(a,b) ((a) > (b) ? a : b)
#define MIN(a,b) ((a) < (b) ? a : b)
В большинстве случаев вы можете оставить это на усмотрение компилятора, чтобы определить, что вы пытаетесь сделать, и оптимизировать его как можно лучше. Хотя это вызывает проблемы при использовании, как MAX(i++, j++)
Я сомневаюсь, что когда-либо возникает необходимость в проверке максимума приращенных значений за один раз. Сначала увеличьте, затем проверьте.
Я написал эту версию, которая работает для 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
Если вам нужно мин / макс, чтобы избежать дорогостоящей ветки, вам не следует использовать троичный оператор, так как он скомпилируется до прыжка. Ссылка ниже описывает полезный метод для реализации функции min / max без ветвления.
Похоже Windef.h
(а-ля #include <windows.h>
) имеет max
а также min
(строчные буквы) макросы, которые также страдают от сложности "двойной оценки", но они там для тех, кто не хочет перекатывать свои собственные:)
Я знаю, что парень сказал "C"... Но если у вас есть возможность, используйте шаблон C++:
template<class T> T min(T a, T b) { return a < b ? a : b; }
Тип безопасен, и никаких проблем с ++, упомянутых в других комментариях.
Стоит отметить, я думаю, что если вы определите 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)
Старое расширение GCC: операторы
<?, >?, <?=, >?=
В очень старой версии GCC были операторы
<?, >?
(см. здесь, здесь это было на C++, но я думаю, что тогда оно также применялось как расширение C). Я также видел операторы
<?=, >?=
соответствующие операторам присваивания.
Операнды оценивались один раз и даже допускали очень короткий оператор присваивания. Это очень мало по сравнению с обычными назначениями min / max. Нет ничего, что могло бы превзойти это.
Это было сокращение для следующего:
min(a, b) === a < b ? a : b === a <? b;
max(a, b) === a > b ? a : b === a >? b;
a = min(a, b); === if(b < a) a = b; === a <?= b;
a = max(a, b); === if(b > a) a = b; === a >?= b;
Найти минимум очень кратко:
int find_min(const int* ints, int num_ints)
{
assert(num_ints > 0);
int min = ints[0];
for(int i = 1; i < num_ints; ++i)
min <?= ints[i];
return min;
}
Я надеюсь, что это может быть когда-нибудь возвращено в GCC, потому что я считаю эти операторы гениальными.
Простым решением, как предполагают другие ответы, будет макрос типа:
#define max(a, b) ((a) > (b) ? (a) : (b))
Однако это может привести к неожиданным результатам и ненужным (пере)вычислениям, еслиa
иb
представляют собой сложные выражения (с побочными эффектами), а не просто переменные.
Вопрос действительно запрашивал расширения, специфичные для компилятора, поэтому, как показывают и другие ответы, более безопасным вариантом было бы использовать выражения операторов GCC и__typeof__
.
#define max(a, b) ({ __typeof__(a) _a = (a); __typeof__(b) _b = (b); _a > _b ? _a : _b; })
Это решение отлично подходит, если у вас есть GCC. Но иначе вообще не получится.
Функция шаблона будет еще лучше, если вы используете C++. Но вопрос помечен как c , поэтому ответ только на C++ здесь не применим ко всем случаям.
Я хотел бы предложить портативное решение. Поскольку в библиотеке C уже есть функции, оканчивающиеся наl
иll
для более крупных целочисленных типов мы могли бы определить свои собственныеmin
иmax
таким же образом, чтобы сопоставить функции, найденные в библиотеке, напримерabs
/absl
/absll
иdiv
/divl
/divll
.
static inline int min(const int a, const int b) {
return a < b ? a : b;
}
static inline int max(const int a, const int b) {
return a > b ? a : b;
}
static inline long minl(const long a, const long b) {
return a < b ? a : b;
}
static inline long maxl(const long a, const long b) {
return a > b ? a : b;
}
static inline long long minll(const long long a, const long long b) {
return a < b ? a : b;
}
static inline long long maxll(const long long a, const long long b) {
return a > b ? a : b;
}
Это решение является хорошей заменой расширений GCC или функций только C++ и более безопасно, чем макросы. Мы не определяем fmin
и fmax
и их варианты для типов с плавающей запятой, поскольку они уже существуют.
в gcc вы можете использовать это:
#define max(a,b) \
({ __typeof__ (a) _a = (a); \
__typeof__ (b) _b = (b); \
_a > _b ? _a : _b; })
в msvc вы можете использовать это:
#define SMART_MIN(x, y) \
([&]() -> decltype(x) { \
decltype(x) _min1 = (x); \
decltype(y) _min2 = (y); \
return _min1 < _min2 ? _min1 : _min2; \
})()
таким образом можно убедиться, что x, y не будут рассчитаны 2 раза
Связанный с комментарием Бретта Хейла, clang
начал поддерживать __auto_type
около 2016 года (см. патч).
Максимум двух целых a
а также b
является (int)(0.5((a+b)+abs(a-b)))
, Это также может работать с (double)
а также fabs(a-b)
для парных разрядов (аналогично для поплавков)
Самый простой способ - определить его как глобальную функцию в .h
файл, и вызывайте его в любое время, если ваша программа модульная с большим количеством файлов. Если не, double MIN(a,b){return (a<b?a:b)}
это самый простой способ.