Gdzie są MIN
i MAX
zdefiniowane w C, jeśli w ogóle?
Jaki jest najlepszy sposób na ich wdrożenie, tak ogólnie i bezpiecznie, jak to możliwe? (Preferowane są rozszerzenia / wbudowane kompilatory kompilatorów głównego nurtu).
Gdzie są MIN
i MAX
zdefiniowane w C, jeśli w ogóle?
Jaki jest najlepszy sposób na ich wdrożenie, tak ogólnie i bezpiecznie, jak to możliwe? (Preferowane są rozszerzenia / wbudowane kompilatory kompilatorów głównego nurtu).
Odpowiedzi:
Gdzie są
MIN
iMAX
zdefiniowane w C, jeśli w ogóle?
Nie są.
Jaki jest najlepszy sposób na ich wdrożenie, tak ogólnie, jak to tylko możliwe i jak najbardziej bezpieczne (preferowane są rozszerzenia / wbudowane kompilatory dla głównych kompilatorów).
Jako funkcje. Nie używałbym takich makr #define MIN(X, Y) (((X) < (Y)) ? (X) : (Y))
, zwłaszcza jeśli planujesz wdrożyć swój kod. Napisz własny, użyj czegoś standardowego fmax
lub fmin
popraw makro za pomocą GFF typeof (dostajesz premię za bezpieczeństwo typów) w wyrażeniu instrukcji GCC :
#define max(a,b) \
({ __typeof__ (a) _a = (a); \
__typeof__ (b) _b = (b); \
_a > _b ? _a : _b; })
Wszyscy mówią „och, wiem o podwójnej ocenie, to nie problem”, a kilka miesięcy później będziesz debugować najgłupsze problemy przez wiele godzin.
Zwróć uwagę na użycie __typeof__
zamiast typeof
:
Jeśli piszesz plik nagłówkowy, który musi działać po włączeniu do programów ISO C, pisz
__typeof__
zamiasttypeof
.
decltype
słowem kluczowym MSVC ++ 2010 - ale mimo to Visual Studio nie może wykonywać instrukcji złożonych w makrach (i decltype
tak czy inaczej to C ++), tj. ({ ... })
składnia GCC, więc jestem prawie pewien, że i tak nie jest to możliwe. Nie patrzyłem na żadne inne kompilatory dotyczące tego problemu, przepraszam Luther: S
MAX(someUpperBound, someRandomFunction())
aby ograniczyć losową wartość do pewnego górnego limitu. To był okropny pomysł, ale też nie zadziałał, ponieważ ten, MAX
którego używał, miał problem z podwójną oceną, więc skończył z inną losową liczbą niż ta, którą początkowo oceniano.
MIN(x++, y++)
preprocesor wygeneruje następujący kod (((x++) < (y++)) ? (x++) : (y++))
. Tak, x
i y
będą zwiększane dwukrotnie.
Jest on także dostępny w wersjach sys / param.h GNU libc (Linux) i FreeBSD i ma definicję dostarczoną przez dreamlax.
W systemie 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.
Na 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))
Repozytoria źródłowe są tutaj:
openSUSE/Linux 3.1.0-1.2-desktop
/ gcc version 4.6.2 (SUSE Linux)
też. :) Źle, to nie jest przenośne.
Jest std::min
i std::max
w C ++, ale AFAIK, nie ma odpowiednika w bibliotece standardowej C. Możesz je zdefiniować samodzielnie za pomocą makr takich jak
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
Ale to powoduje problemy, jeśli napiszesz coś takiego MAX(++a, ++b)
.
#define MIN(A, B) ((A < B) ? A : B)
nie jest elastyczny sposób, dlaczego ???
#define MULT(x, y) x * y
. Następnie MULT(a + b, a + b)
rozwija się do a + b * a + b
, który analizuje jako a + (b * a) + b
ze względu na pierwszeństwo. Prawdopodobnie nie tak zamierzał programista.
Unikaj niestandardowych rozszerzeń kompilatora i zaimplementuj je jako całkowicie bezpieczne makro typu w czystym standardzie C (ISO 9899: 2011).
Rozwiązanie
#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))
Stosowanie
MAX(int, 2, 3)
Wyjaśnienie
Makro MAX tworzy kolejne makro na podstawie type
parametru. To makro sterujące, jeśli zostało zaimplementowane dla danego typu, służy do sprawdzania, czy oba parametry są poprawnego typu. Jeśli type
nie jest obsługiwany, wystąpi błąd kompilatora.
Jeśli x lub y nie jest poprawnego typu, w ENSURE_
makrach wystąpi błąd kompilatora . Więcej takich makr można dodać, jeśli obsługiwanych jest więcej typów. Założyłem, że będą używane tylko typy arytmetyczne (liczby całkowite, zmiennoprzecinkowe, wskaźniki itp.), A nie struktury i tablice itp.
Jeśli wszystkie typy są poprawne, makro GENERIC_MAX zostanie wywołane. Potrzebne są dodatkowe nawiasy wokół każdego parametru makra, jako zwykłe standardowe środki ostrożności podczas pisania makr C.
Potem są zwykłe problemy z niejawnymi promocjami typu w C. ?:
Operator równoważy drugi i trzeci operand względem siebie. Na przykład wynikiem GENERIC_MAX(my_char1, my_char2)
może być int
. Aby zapobiec wykonywaniu potencjalnie niebezpiecznych promocji przez makro, zastosowano ostateczny rzut rzutowany na zamierzony typ.
Racjonalne uzasadnienie
Chcemy, aby oba parametry makra były tego samego typu. Jeśli jedno z nich jest innego typu, makro nie jest już bezpieczne, ponieważ operator lubi?:
zapewni awans typu pośredniego. A ponieważ tak jest, zawsze musimy rzutować końcowy wynik z powrotem na zamierzony typ, jak wyjaśniono powyżej.
Makro z tylko jednym parametrem mogłoby zostać napisane w znacznie prostszy sposób. Ale przy 2 lub więcej parametrach konieczne jest dołączenie dodatkowego parametru typu. Ponieważ coś takiego jest niestety niemożliwe:
// this won't work
#define MAX(x, y) \
_Generic((x), \
int: GENERIC_MAX(x, ENSURE_int(y)) \
float: GENERIC_MAX(x, ENSURE_float(y)) \
)
Problem polega na tym, że jeśli powyższe makro zostanie wywołane tak jak MAX(1, 2)
dwaint
, nadal będzie próbowało rozwinąć makro wszystkie możliwe scenariusze z _Generic
listy skojarzeń. Więc ENSURE_float
makro będzie się zbyt rozbudowany, choć nie dotyczy int
. A ponieważ to makro celowo zawiera tylko float
typ, kod nie zostanie skompilowany.
Aby rozwiązać ten problem, utworzyłem nazwę makra podczas fazy preprocesora, za pomocą operatora ##, aby żadne makro nie zostało przypadkowo rozwinięte.
Przykłady
#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
Nawiasem mówiąc, to makro jest złym pomysłem, musisz tylko spróbować GENERIC_MAX(var++, 7)
dowiedzieć się, dlaczego :-) W dzisiejszych czasach (szczególnie przy mocno kompilujących / optymalizujących kompilatorach) makra powinny być w zasadzie sprowadzane tylko do prostych formularzy. Funkcje podobne są lepsze jako funkcje, a grupy wartości lepsze jako wyliczenia.
Nie sądzę, że są to standardowe makra. Istnieją już znormalizowane funkcje dla liczb zmiennoprzecinkowych fmax
i fmin
(oraz dla liczb zmiennoprzecinkowych fmaxf
i fmaxl
długich podwójnych).
Możesz je zaimplementować jako makra, o ile masz świadomość problemów związanych z efektami ubocznymi / podwójną oceną.
#define MAX(a,b) ((a) > (b) ? a : b)
#define MIN(a,b) ((a) < (b) ? a : b)
W większości przypadków możesz pozostawić to kompilatorowi, aby określić, co próbujesz zrobić, i zoptymalizować go najlepiej, jak to możliwe. Chociaż powoduje to problemy przy takim użyciu MAX(i++, j++)
, wątpię, aby zawsze była potrzeba sprawdzania maksymalnej wartości przyrostowej za jednym razem. Najpierw zwiększ, a następnie sprawdź.
To późna odpowiedź z powodu dość niedawnego rozwoju. Ponieważ PO zaakceptował odpowiedź, która opiera się na nieprzenośnym rozszerzeniu GCC (i clang) typeof
- lub __typeof__
na „czystym” ISO C - dostępne jest lepsze rozwiązanie od gcc-4.9 .
#define max(x,y) ( \
{ __auto_type __x = (x); __auto_type __y = (y); \
__x > __y ? __x : __y; })
Oczywistą zaletą tego rozszerzenia jest to, że każdy argument makra jest rozszerzany tylko raz, w przeciwieństwie do __typeof__
rozwiązania.
__auto_type
jest ograniczoną formą C ++ 11 auto
. Nie można go (lub nie należy?) Używać w kodzie C ++, choć nie ma żadnego dobrego powodu, aby nie korzystać z lepszych możliwości wnioskowania typuauto
przypadku używania C ++ 11.
To powiedziawszy, zakładam , że nie ma problemów z użyciem tej składni, gdy makro jest zawarte w extern "C" { ... }
zakresie; np. z nagłówka C. AFAIK, to rozszerzenie nie znalazło sposobu na kliknięcie informacji
clang
zaczął wspierać __auto_type
około 2016 roku (patrz łatka ).
c-preprocessor
tag. Nie można zagwarantować, że funkcja zostanie wstawiona nawet z tym słowem kluczowym, chyba że użyje czegoś takiego jak __always_inline__
atrybut gcc .
Napisałem tę wersję, która działa dla MSVC, GCC, C i 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
Jeśli potrzebujesz min / max, aby uniknąć kosztownej gałęzi, nie powinieneś używać operatora trójskładnikowego, ponieważ kompiluje się on do skoku. Poniższy link opisuje przydatną metodę implementacji funkcji min / max bez rozgałęziania.
http://graphics.stanford.edu/~seander/bithacks.html#IntegerMinOrMax
@David Titarenco przybity go tutaj , ale pozwól mi przynajmniej czyste go nieco, aby wyglądać dobrze i pokazać zarówno min()
i max()
razem zrobić kopiując i wklejając stąd łatwiejsze. :)
Aktualizacja 25 kwietnia 2020 r .: Dodałem również sekcję 3, aby pokazać, jak można by to zrobić z szablonami C ++, jako cenne porównanie dla osób uczących się zarówno C i C ++, lub przechodzących z jednego do drugiego. Zrobiłem co w mojej mocy, aby być dokładnym, rzeczowym i poprawnym, aby uczynić tę odpowiedź kanonicznym odniesieniem, do którego mogę powracać raz po raz, i mam nadzieję, że okaże się tak przydatna jak ja.
Ta technika jest powszechnie stosowana, szanowana przez tych, którzy wiedzą, jak ją właściwie stosować, „de facto” sposób robienia rzeczy, i dobrze jest ją stosować, jeśli jest właściwie stosowana, ale buggy (pomyśl: efekt podwójnej oceny ), jeśli kiedykolwiek przekazuj wyrażenia, w tym przypisanie zmiennych, w celu porównania:
#define MAX(a,b) ((a) > (b) ? (a) : (b))
#define MIN(a,b) ((a) < (b) ? (a) : (b))
Ta technika pozwala uniknąć powyższych skutków ubocznych i błędów „podwójnej oceny” i dlatego jest uważana za lepszy, bezpieczniejszy i „bardziej nowoczesny” sposób GCC C. Spodziewaj się, że będzie działać zarówno z kompilatorami gcc, jak i clang, ponieważ clang jest z założenia zgodny z gcc (patrz uwaga clang na dole tej odpowiedzi).
ALE: W dalszym ciągu uważaj na efekty „ cieniowania zmiennych ”, ponieważ wyrażenia instrukcji są najwyraźniej wbudowane i dlatego NIE mają własnego zasięgu zmiennej lokalnej!
#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; \
})
Zauważ, że w wyrażeniach instrukcji gcc ostatnie wyrażenie w bloku kodu jest tym, co jest „zwracane” z wyrażenia, tak jakby zostało zwrócone z funkcji. Dokumentacja GCC mówi to w ten sposób:
Ostatnią rzeczą w instrukcji złożonej powinno być wyrażenie, po którym następuje średnik; wartość tego podwyrażenia służy jako wartość całego konstruktu. (Jeśli użyjesz innego rodzaju instrukcji na końcu nawiasów klamrowych, konstrukcja ma typ void, a zatem nie ma żadnej wartości).
Uwaga C ++: jeśli używam C ++, szablony są prawdopodobnie zalecane zamiast tego typu konstrukcji, ale ja osobiście nie lubię szablonów i prawdopodobnie użyłbym jednej z powyższych konstrukcji w C ++, ponieważ często używam i preferuję style C również we wbudowanym C ++.
W tej sekcji dodano 25 kwietnia 2020 r .:
W ciągu ostatnich kilku miesięcy robiłem mnóstwo C ++, a presja, by preferować szablony nad makrami, tam gdzie jest to możliwe, w społeczności C ++ jest dość silna. W rezultacie coraz lepiej używam szablonów i chcę tutaj umieścić wersje szablonów C ++, aby uzyskać kompletność i uczynić to bardziej kanoniczną i dokładną odpowiedzią.
Oto, co podstawowe szablon funkcji wersje max()
i min()
może wyglądać jak w 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;
}
Przeczytaj dodatkowe informacje na temat szablonów C ++ tutaj: Wikipedia: Szablon (C ++) .
Jednak zarówno max()
i min()
są już częścią biblioteki standardowej C ++, w <algorithm>
nagłówku ( #include <algorithm>
). W standardowej bibliotece C ++ są one zdefiniowane nieco inaczej niż mam je powyżej. Domyślne prototypy std::max<>()
i std::min<>()
, na przykład, w C ++ 14, patrząc na ich prototypy w linkach cplusplus.com powyżej, to:
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);
Zauważ, że słowo kluczowe typename
jest aliasem class
(więc ich użycie jest identyczne bez względu na to, czy mówisz <typename T>
czy <class T>
), ponieważ po wynalezieniu szablonów C ++ potwierdzono, że typ szablonu może być typem regularnym ( int
,float
, itd.), A nie tylko typ klasy.
Tutaj możesz zobaczyć, że oba typy wejściowe, jak również typ zwracany, są const T&
, co oznacza „stałe odniesienie do typu T
”. Oznacza to, że parametry wejściowe i wartość zwracana są przekazywane przez odniesienie zamiast przez wartość . To jest jak przekazywanie wskaźników i jest bardziej wydajne w przypadku dużych typów, takich jak obiekty klasy. constexpr
Część funkcji modyfikuje samą funkcję i wskazuje, że funkcja musi być w stanie być oceniany w czasie kompilacji (przynajmniej w przypadku określonych constexpr
parametrów wejściowych), jednak jeśli nie można ocenić w czasie kompilacji, to domyślnie powrotem do ocena w czasie wykonywania, jak każda inna normalna funkcja.
Aspekt czasu kompilacji constexpr
funkcji C ++ sprawia, że jest ona trochę podobna do makropolecenia C, ponieważ jeśli ocena constexpr
funkcji kompilacji jest możliwa dla funkcji, zostanie ona wykonana w czasie kompilacji, podobnie jak podstawienie makra MIN()
lub MAX()
ewentualnie być w pełni oceniany podczas kompilacji również w C lub C ++. Aby uzyskać dodatkowe odniesienia do informacji o szablonie C ++, patrz poniżej.
Notatka Clang z Wikipedii :
[Clang] ma działać jako zastępczy element zastępczy GNU Compiler Collection (GCC), obsługując większość jego flag kompilacyjnych i nieoficjalne rozszerzenia językowe.
Warto zauważyć, że myślę, że jeśli zdefiniujesz min
i max
z trzeciorzędowym, takim jak
#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))
następnie, aby uzyskać ten sam wynik dla specjalnego przypadku fmin(-0.0,0.0)
i fmax(-0.0,0.0)
musisz zamienić argumenty
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
Wygląda na to, że Windef.h
(la #include <windows.h>
) ma max
i min
(małe litery) makra, które również cierpią z powodu trudności z „podwójną oceną”, ale są dostępne dla tych, którzy nie chcą ponownie tworzyć własnych :)
Wiem, że facet powiedział „C” ... Ale jeśli masz szansę, użyj szablonu C ++:
template<class T> T min(T a, T b) { return a < b ? a : b; }
Wpisz bezpieczny i bez problemów z ++ wspomnianym w innych komentarzach.
Maksymalnie dwie liczby całkowite a
i b
to (int)(0.5((a+b)+abs(a-b)))
. Może to również działać z podwójnymi (double)
i fabs(a-b)
dla nich (podobnie dla pływaków)
Najprostszym sposobem jest zdefiniowanie go jako funkcji globalnej w .h
pliku i wywołanie go w dowolnym momencie, jeśli twój program jest modułowy z dużą ilością plików. Jeśli nie, double MIN(a,b){return (a<b?a:b)}
to najprostszy sposób.
warning: expression with side-effects multiply evaluated by macro
w punkcie użycia ...