Де вони MIN
і MAX
визначені в C, якщо вони взагалі?
Який найкращий спосіб здійснити їх, максимально безпечно та ввести безпечно? (Розширення компілятора / вбудовані для основних компіляторів бажано.)
Де вони MIN
і MAX
визначені в C, якщо вони взагалі?
Який найкращий спосіб здійснити їх, максимально безпечно та ввести безпечно? (Розширення компілятора / вбудовані для основних компіляторів бажано.)
Відповіді:
Де вони
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
.
decltype
ключовим словом MSVC ++ 2010 - але навіть так, Visual Studio не може робити складні заяви у макросах (і decltype
це C ++ у будь-якому випадку), тобто ({ ... })
синтаксис GCC, тому я впевнений, що це все-таки неможливо. Я не дивився на жодних інших упорядників щодо цього питання, вибачте Лютер: S
MAX(someUpperBound, someRandomFunction())
для обмеження випадкового значення якоюсь верхньою межею. Це була жахлива ідея, але вона навіть не спрацювала, тому що MAX
він використовував проблему з подвійним оцінюванням, тож він опинився з іншим випадковим числом, ніж те, що було оцінено спочатку.
MIN(x++, y++)
до препроцесора, генерується наступний код (((x++) < (y++)) ? (x++) : (y++))
. Отже, x
і y
буде збільшуватися вдвічі.
Він також надається у версіях GNU libc (Linux) та FreeBSD для sys / param.h та має визначення, яке надає 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))
Вихідні сховища тут:
openSUSE/Linux 3.1.0-1.2-desktop
/ gcc version 4.6.2 (SUSE Linux)
теж. :) Погано, що це не портативно.
Є std::min
і std::max
в C ++, але AFAIK, у стандартній бібліотеці С немає еквівалента. Ви можете визначити їх самостійно за допомогою макросів
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
Але це спричиняє проблеми, якщо ви пишете щось подібне MAX(++a, ++b)
.
#define MIN(A, B) ((A < B) ? A : B)
це не гнучкий спосіб, чому ???
#define MULT(x, y) x * y
. Потім MULT(a + b, a + b)
розширюється на a + b * a + b
, який розбирає як a + (b * a) + b
за рахунок переваги. Це, мабуть, не планував програміст.
Уникайте нестандартних розширень компілятора та впроваджуйте його як повністю безпечний для макросу тип чистого стандарту 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
. Щоб запобігти макросу робити такі потенційно небезпечні рекламні типи, було використано остаточний тип передачі на призначений тип.
Обґрунтування
Ми хочемо, щоб обидва параметри макроса були одного типу. Якщо одна з них має інший тип, макрос більше не є безпечним, оскільки оператор любить?:
, дає неявні рекламні акції. А оскільки це так, нам також завжди потрібно повернути кінцевий результат до запланованого типу, як пояснено вище.
Макрос із лише одним параметром можна було записати набагато простіше. Але з 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;
}
GENERIC_MAX
макрос, до речі, погана ідея, вам потрібно лише спробувати GENERIC_MAX(var++, 7)
з’ясувати, чому :-) У наш час (особливо з сильно оптимізуючими / вбудованими компіляторами) макроси мають бути значною мірою віднесені лише до простих форм. Функціональні є кращими як функції, а групи значень - кращими як перерахування.
Я не думаю, що це стандартизовані макроси. Уже є стандартизовані функції для плаваючої точки, fmax
і fmin
(і fmaxf
для поплавців, і fmaxl
для довгих пар).
Ви можете реалізовувати їх як макроси, якщо вам відомі проблеми побічних ефектів / подвійної оцінки.
#define MAX(a,b) ((a) > (b) ? a : b)
#define MIN(a,b) ((a) < (b) ? a : b)
У більшості випадків ви можете залишити його компілятору, щоб визначити, що ви намагаєтеся зробити, та оптимізувати це якнайкраще. Хоча це спричиняє проблеми при такому використанні MAX(i++, j++)
, я сумніваюся, що коли-небудь виникає велика потреба у перевірці максимальних значень збільшення за один раз. Зростання спочатку, потім перевірка.
Це пізня відповідь, зумовлена досить недавньою подією. Оскільки ОП прийняла відповідь, що спирається на непереносне розширення GCC (та клаксони) 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" { ... }
область; наприклад, із заголовка С. AFAIK, це розширення не знайшло свого інформаційного дзвінка
clang
почав підтримуватись __auto_type
близько 2016 року (див. Виправлення ).
c-preprocessor
тег. Функція не гарантується, що вона буде окреслена навіть із вказаним ключовим словом, якщо тільки не використовується щось на зразок __always_inline__
атрибута gcc .
Я написав цю версію, яка працює для 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, щоб уникнути дорогого відділення, вам не слід використовувати потрійний оператор, оскільки він складеться до стрибка. Посилання нижче описує корисний метод для реалізації функції min / max без розгалуження.
http://graphics.stanford.edu/~seander/bithacks.html#IntegerMinOrMax
@David Titarenco прибив це сюди , але дозвольте мені принаймні трохи очистити його, щоб він виглядав красиво, і покажіть як min()
і max()
разом, щоб полегшити копіювання та вставлення звідси. :)
Оновлення 25 квітня 2020 року: Я також додав Розділ 3, щоб показати, як це можна зробити з шаблонами C ++, як цінне порівняння для тих, хто навчається як C, так і C ++, або переходить від одного до іншого. Я зробив все можливе, щоб бути ретельним, фактичним і правильним, щоб зробити цю відповідь канонічною довідкою, на яку я можу повертатися знову і знову, і сподіваюся, що ви вважаєте її такою ж корисною, як і я.
Ця методика зазвичай використовується, добре поважається тими, хто знає, як правильно її використовувати, "фактичним" способом робити речі, і прекрасно використовувати, якщо правильно користуватися, але баггі (подумайте: побічний ефект подвійної оцінки ), якщо ви коли-небудь передавати вирази, включаючи призначення змінної для порівняння:
#define MAX(a,b) ((a) > (b) ? (a) : (b))
#define MIN(a,b) ((a) < (b) ? (a) : (b))
Ця методика дозволяє уникнути вищевказаних побічних ефектів та помилок, подвійних оцінок, і тому вважається вищим, безпечнішим та «більш сучасним» способом 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 і, таким чином, фактично не має значення.)
Примітка 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 ++: Вікіпедія: Шаблон (C ++) .
Однак і те, max()
і min()
вже є частиною стандартної бібліотеки C ++, у <algorithm>
заголовку ( #include <algorithm>
). У стандартній бібліотеці С ++ вони визначаються дещо інакше, ніж я їх вище. За замовчуванням прототипи 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 ++ див. Нижче.
Нота Кланг з Вікіпедії :
[Clang] призначений для того, щоб виконувати функцію заміни колекції GNU Compiler Collection (GCC), підтримуючи більшість його прапорів компіляції та неофіційних розширень мови.
Варто зазначити, я думаю, що якщо ви визначитеся 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)
fmin(3.0,NaN)==fmin(NaN,3.0)==fmax(3.0,NaN)==fmax(NaN,3.0)==3.0
Схоже, макроси Windef.h
(а-ля #include <windows.h>
) мають max
і min
(малі регістри), які також страждають від труднощів з "подвійною оцінкою", але вони є для тих, хто не хоче повторно прокручувати свою :)
Я знаю, хлопець сказав "С" ... Але якщо у вас є шанс, використовуйте шаблон C ++:
template<class T> T min(T a, T b) { return a < b ? a : b; }
Введіть безпечний і не мати проблем із ++, згаданим в інших коментарях.
Максимум два цілих числа a
і b
є (int)(0.5((a+b)+abs(a-b)))
. Це також може працювати з (double)
і fabs(a-b)
для парних пар (схоже на поплавці)
Найпростіший спосіб - визначити його як глобальну функцію у .h
файлі та викликати її коли завгодно, якщо ваша програма є модульною з великою кількістю файлів. Якщо ні, double MIN(a,b){return (a<b?a:b)}
це найпростіший спосіб.
warning: expression with side-effects multiply evaluated by macro
на місці використання ...