Zawsze widziałem przykłady i przypadki, w których użycie makra jest lepsze niż użycie funkcji.
Czy ktoś mógłby mi wyjaśnić na przykładzie wady makra w porównaniu z funkcją?
c
function
c-preprocessor
Kyrol
źródło
źródło
Odpowiedzi:
Makra są podatne na błędy, ponieważ polegają na podstawianiu tekstu i nie sprawdzają typów. Na przykład to makro:
działa dobrze, gdy jest używany z liczbą całkowitą:
ale robi bardzo dziwne rzeczy, gdy jest używany z wyrażeniami:
Umieszczenie nawiasów wokół argumentów pomaga, ale nie eliminuje całkowicie tych problemów.
Gdy makra zawierają wiele instrukcji, możesz mieć problemy z konstrukcjami sterowania przepływem:
Zwykłą strategią rozwiązywania tego problemu jest umieszczenie instrukcji wewnątrz pętli „do {...} while (0)”.
Jeśli masz dwie struktury, które zawierają pole o tej samej nazwie, ale różnej semantyce, to samo makro może działać na obu z dziwnymi wynikami:
Wreszcie, makra mogą być trudne do debugowania, powodując dziwne błędy składniowe lub błędy w czasie wykonywania, które musisz rozwinąć, aby je zrozumieć (np. Z gcc -E), ponieważ debuggery nie mogą przechodzić przez makra, jak w tym przykładzie:
Funkcje wbudowane i stałe pomagają uniknąć wielu z tych problemów z makrami, ale nie zawsze mają zastosowanie. Gdy makra są celowo używane do określenia zachowania polimorficznego, uniknięcie niezamierzonego polimorfizmu może być trudne. C ++ ma wiele funkcji, takich jak szablony, które pomagają tworzyć złożone konstrukcje polimorficzne w bezpieczny sposób bez użycia makr; szczegółowe informacje można znaleźć w języku programowania C ++ Stroustrupa .
źródło
x++*x++
można więc powiedzieć, że wyrażenie zwiększax
dwukrotnie; w rzeczywistości wywołuje niezdefiniowane zachowanie , co oznacza, że kompilator może robić wszystko, co chce - może zwiększyćx
dwukrotnie, raz lub wcale; może zakończyć się błędem, a nawet sprawić, że demony wylecą ci z nosa .Funkcje makro :
Cechy funkcji :
źródło
Skutki uboczne są duże. Oto typowy przypadek:
zostanie rozszerzony do:
x
zostanie zwiększona dwukrotnie w tej samej instrukcji. (i niezdefiniowane zachowanie)Pisanie makr wieloliniowych jest również uciążliwe:
Wymagają znaku
\
na końcu każdego wiersza.Makra nie mogą „zwracać” niczego, chyba że uczynisz to pojedynczym wyrażeniem:
Nie możesz tego zrobić w makrze, chyba że używasz wyrażenia GCC. (EDYCJA: Możesz jednak użyć operatora przecinka ... przeoczyłem to ... Ale nadal może być mniej czytelny.)
Kolejność operacji: (dzięki uprzejmości @ouah)
zostanie rozszerzony do:
Ale
&
ma niższy priorytet niż<
. Więc0xFF < 42
jest oceniany jako pierwszy.źródło
min(a & 0xFF, 42)
Przykład 1:
natomiast:
Przykład 2:
W porównaniu do:
źródło
W razie wątpliwości użyj funkcji (lub funkcji wbudowanych).
Jednak odpowiedzi tutaj głównie wyjaśniają problemy z makrami, zamiast prostego poglądu, że makra są złe, ponieważ możliwe są głupie wypadki.
Możesz być świadomy pułapek i nauczyć się ich unikać. Następnie używaj makr tylko wtedy, gdy jest ku temu dobry powód.
Istnieją pewne wyjątkowe przypadki, w których używanie makr ma zalety, takie jak:
va_args
.np .: https://stackoverflow.com/a/24837037/432509 .
(
__FILE__
,__LINE__
,__func__
). sprawdzić warunki przed / po,assert
awarie, a nawet potwierdzenia statyczne, aby kod nie kompilował się przy niewłaściwym użyciu (głównie przydatne do debugowania kompilacji).struct
elementy członkowskie są obecne przed rzutowaniem(może być przydatne w przypadku typów polimorficznych) .
Lub sprawdź, czy tablica spełnia jakiś warunek długości.
patrz: https://stackoverflow.com/a/29926435/432509
func(FOO, "FOO");
możesz zdefiniować makro, które rozszerza ciąg za Ciebiefunc_wrapper(FOO);
(przypisania do wielu zmiennych, dla operacji na piksel, to przykład, że możesz preferować makro od funkcji ... choć nadal zależy to w dużym stopniu od kontekstu, ponieważ
inline
funkcje mogą być opcją) .Trzeba przyznać, że niektóre z nich opierają się na rozszerzeniach kompilatora, które nie są standardowe C. Oznacza to, że możesz skończyć z mniej przenośnym kodem lub mieć
ifdef
go w środku, więc są one wykorzystywane tylko wtedy, gdy kompilator obsługuje.Unikanie tworzenia wielu argumentów
Należy to zauważyć, ponieważ jest to jedna z najczęstszych przyczyn błędów w makrach (
x++
na przykład przekazywanie , gdy makro może zwiększać się wielokrotnie) .możliwe jest pisanie makr, które unikają skutków ubocznych z wieloma instancjami argumentów.
C11 Generic
Jeśli chcesz mieć
square
makro, które działa z różnymi typami i obsługuje C11, możesz to zrobić ...Wyrażenia instrukcji
To jest rozszerzenie kompilatora obsługiwane przez GCC, Clang, EKOPath i Intel C ++ (ale nie MSVC) ;
Więc wadą makr jest to, że musisz wiedzieć, jak z nich korzystać, i że nie są one tak szeroko obsługiwane.
Jedną z korzyści jest to, że w tym przypadku możesz użyć tej samej
square
funkcji dla wielu różnych typów.źródło
Żadne sprawdzanie typu parametrów i kodu nie jest powtarzane, co może prowadzić do rozdęcia kodu. Składnia makr może również prowadzić do dowolnej liczby dziwnych przypadków brzegowych, w których średniki lub kolejność pierwszeństwa mogą przeszkadzać. Oto link, który demonstruje pewne makro zło
źródło
Jedną z wad makr jest to, że debuggery czytają kod źródłowy, który nie ma rozszerzonych makr, więc uruchomienie debuggera w makrze niekoniecznie jest przydatne. Nie trzeba dodawać, że nie można ustawić punktu przerwania wewnątrz makra, tak jak w przypadku funkcji.
źródło
Funkcje sprawdzają typ. Daje to dodatkową warstwę bezpieczeństwa.
źródło
Dodaję do tej odpowiedzi ...
Makra są podstawiane bezpośrednio do programu przez preprocesor (ponieważ w zasadzie są to dyrektywy preprocesora). Dlatego nieuchronnie wykorzystują więcej miejsca w pamięci niż odpowiednia funkcja. Z drugiej strony wywołanie funkcji i zwrócenie wyników wymaga więcej czasu, a tego narzutu można uniknąć, używając makr.
Również makra mają specjalne narzędzia, które mogą pomóc w przenoszeniu programów na różne platformy.
W przeciwieństwie do funkcji makra nie muszą mieć przypisanego typu danych dla swoich argumentów.
Ogólnie są użytecznym narzędziem w programowaniu. W zależności od okoliczności można używać zarówno makroinstrukcji, jak i funkcji.
źródło
W powyższych odpowiedziach nie zauważyłem jednej przewagi funkcji nad makrami, która moim zdaniem jest bardzo ważna:
Funkcje mogą być przekazywane jako argumenty, makra nie mogą.
Konkretny przykład: Chcesz napisać alternatywną wersję standardowej funkcji `` strpbrk '', która będzie akceptować zamiast jawnej listy znaków do wyszukania w innym ciągu, funkcję (wskaźnik do a), która zwróci 0, dopóki znak nie zostanie okazało się, że przechodzi jakiś test (zdefiniowany przez użytkownika). Jednym z powodów, dla których możesz chcieć to zrobić, jest to, że możesz wykorzystać inne standardowe funkcje biblioteczne: zamiast podawać jawny ciąg znaków pełen znaków interpunkcyjnych, możesz zamiast tego przekazać „ispunct” w ctype.h itd. Jeśli „ispunct” został zaimplementowany tylko jako makro, to nie zadziała.
Istnieje wiele innych przykładów. Na przykład, jeśli porównanie jest dokonywane za pomocą makra, a nie funkcji, nie możesz przekazać go do 'qsort' standardowego lib.h.
Analogiczna sytuacja w Pythonie to „print” w wersji 2 w porównaniu z wersją 3 (nieprzekraczalna instrukcja vs. zadowalająca funkcja).
źródło
Jeśli przekażesz funkcję jako argument do makra, będzie ona oceniana za każdym razem. Na przykład, jeśli wywołasz jedno z najpopularniejszych makr:
tak
functionThatTakeLongTime zostanie ocenione 5 razy, co może znacznie obniżyć wydajność
źródło