Wiem, że są one wyjątkowo niebezpiecznie implementowane w C / C ++. Czy nie można ich wdrożyć w bezpieczniejszy sposób? Czy wady makr są na tyle poważne, że przewyższają ich ogromną moc?
programming-languages
macros
Casebash
źródło
źródło
WithEvents
modyfikatorowi Visual Basic ? Potrzebujesz czegoś takiego jak makra semantyczne.Odpowiedzi:
Myślę, że głównym powodem jest to, że makra są leksykalne . Ma to kilka konsekwencji:
Kompilator nie ma możliwości sprawdzenia, czy makro jest semantycznie zamknięte, tj. Czy reprezentuje „jednostkę znaczenia”, jak funkcja. (Zastanów się
#define TWO 1+1
- co sięTWO*TWO
równa?)Makra nie są pisane tak jak funkcje. Kompilator nie może sprawdzić, czy parametry i typ zwrotu mają sens. Może sprawdzać tylko rozwinięte wyrażenie, które korzysta z makra.
Jeśli kod nie zostanie skompilowany, kompilator nie będzie wiedział, czy błąd występuje w samym makrze, czy w miejscu, w którym makro jest używane. Kompilator albo zgłosi nieprawidłowe miejsce przez połowę czasu, albo musi zgłosić oba, nawet jeśli jeden z nich jest prawdopodobnie w porządku. (Zastanów się
#define min(x,y) (((x)<(y))?(x):(y))
: co powinien zrobić kompilator, jeśli typyx
iy
nie pasują lub nie implementująoperator<
?)Zautomatyzowane narzędzia nie mogą z nimi współpracować w semantycznie użyteczny sposób. W szczególności nie możesz mieć takich makr, jak IntelliSense dla makr, które działają jak funkcje, ale rozwijają się do wyrażenia. (Ponownie
min
przykład).Skutki uboczne makra nie są tak wyraźne, jak w przypadku funkcji, powodując potencjalne zamieszanie dla programisty. (Ponownie rozważ
min
przykład: w wywołaniu funkcji wiesz, że wyrażenie dlax
jest oceniane tylko raz, ale tutaj nie możesz wiedzieć bez spojrzenia na makro.)Tak jak powiedziałem, są to wszystkie konsekwencje faktu, że makra są leksykalne. Kiedy próbujesz zamienić je w coś bardziej właściwego, otrzymujesz funkcje i stałe.
źródło
Ale tak, makra można zaprojektować i zaimplementować lepiej niż w C / C ++.
Problem z makrami polega na tym, że są one w rzeczywistości mechanizmem rozszerzenia składni języka , który przepisuje kod na coś innego.
W przypadku C / C ++ nie ma fundamentalnego sprawdzania rozsądku. Jeśli jesteś ostrożny, wszystko jest w porządku. Jeśli popełnisz błąd lub nadmiernie użyjesz makr, możesz wpaść w poważne problemy.
Dodaj do tego, że wiele prostych rzeczy, które możesz zrobić za pomocą makr (w stylu C / C ++), można wykonać na inne sposoby w innych językach.
W innych językach, takich jak różne dialekty Lisp, makra są lepiej zintegrowane ze składnią języka podstawowego, ale nadal możesz mieć problemy z deklaracjami w makrze „wyciekające”. To jest skierowana przez higienicznych makr .
Krótki kontekst historyczny
Makra (skrót od makropoleceń) pojawiły się po raz pierwszy w kontekście języka asemblera. Według Wikipedii makra były dostępne w niektórych asemblerach IBM w latach 50.
Oryginalny LISP nie miał makr, ale po raz pierwszy został wprowadzony do MacLisp w połowie lat 60. XX wieku: https://stackoverflow.com/questions/3065606/when-did-the-idea-of-macros-user-defined-code -transformacja-pojawiają się . http://www.csee.umbc.edu/courses/331/resources/papers/Evolution-of-Lisp.pdf . Wcześniej „fexprs” zapewniał funkcjonalność podobną do makra.
Pierwsze wersje C nie miały makr ( http://cm.bell-labs.com/cm/cs/who/dmr/chist.html ). Zostały one dodane około 1972–73 za pośrednictwem preprocesora. Wcześniej C obsługiwał tylko
#include
i#define
.Makroprocesor M4 powstał około 1977 r.
Nowsze języki najwyraźniej implementują makra, w których model działania jest bardziej składniowy niż tekstowy.
Kiedy więc ktoś mówi o prymacie konkretnej definicji terminu „makro”, należy zauważyć, że znaczenie to ewoluowało z czasem.
źródło
Makra mogą, jak zauważa Scott , pozwolić ukryć logikę. Oczywiście, podobnie jak funkcje, klasy, biblioteki i wiele innych popularnych urządzeń.
Potężny system makr może jednak pójść dalej, umożliwiając projektowanie i wykorzystywanie składni i struktur normalnie nie spotykanych w języku. To może być naprawdę wspaniałe narzędzie: języki specyficzne dla domeny, generatory kodów i inne, wszystko w komfortowym środowisku jednego języka ...
Można go jednak nadużywać. Może to utrudnić czytanie, zrozumienie i debugowanie kodu, wydłużyć czas potrzebny nowym programistom na zapoznanie się z bazą kodu oraz prowadzić do kosztownych błędów i opóźnień.
Tak więc w przypadku języków, które mają uprościć programowanie (takich jak Java lub Python), taki system jest anatemą.
źródło
Makra mogą być implementowane bardzo bezpiecznie w niektórych okolicznościach - na przykład w Lisp, makra to tylko funkcje, które zwracają przekształcony kod jako strukturę danych (wyrażenie-s). Oczywiście Lisp w znacznym stopniu korzysta z faktu, że jest homoiczny i że „kod to dane”.
Przykładem tego, jak łatwe mogą być makra, jest przykład Clojure, który określa domyślną wartość do zastosowania w przypadku wyjątku:
Jednak nawet w Lisps ogólna rada brzmi: „nie używaj makr, chyba że musisz”.
Jeśli nie używasz języka homoiconic, makra stają się znacznie trudniejsze, a różne inne opcje mają pewne pułapki:
Co więcej, wszystko, co makro może zrobić, może zostać ostatecznie osiągnięte w inny sposób w pełnym języku turinga (nawet jeśli oznacza to napisanie dużej liczby szablonów). W wyniku całej tej podstępności nic dziwnego, że wiele języków decyduje, że makra nie są warte tyle wysiłku, aby je wdrożyć.
źródło
Aby odpowiedzieć na pytania, zastanów się, do jakich makr używa się głównie (Ostrzeżenie: kod skompilowany z mózgiem).
#define X 100
Można to łatwo zastąpić:
const int X = 100;
#define max(X,Y) (X>Y?X:Y)
W każdym języku, który obsługuje przeciążanie funkcji, można to emulować w sposób znacznie bardziej bezpieczny dla typu, przeciążając funkcje poprawnego typu lub, w języku obsługującym funkcje ogólne, funkcją ogólną. Makro chętnie spróbuje porównać wszystko, w tym wskaźniki lub ciągi, które mogą się skompilować, ale prawie na pewno nie jest to, czego chciałeś. Z drugiej strony, jeśli makra są bezpieczne dla typu, nie oferują żadnych korzyści ani wygody w przypadku przeciążonych funkcji.
#define p printf
Można to łatwo zastąpić funkcją,
p()
która robi to samo. Jest to dość zaangażowane w C (wymagający użyciava_arg()
rodziny funkcji), ale w wielu innych językach, które obsługują zmienną liczbę argumentów funkcji, jest to o wiele prostsze.Obsługa tych funkcji w języku zamiast w specjalnym języku makr jest prostsza, mniej podatna na błędy i znacznie mniej myląca dla osób czytających kod. W rzeczywistości nie mogę wymyślić jednego przypadku użycia makr, którego nie można łatwo powielić w inny sposób. Tylko miejsce, gdzie makra są naprawdę przydatna jest, gdy są one związane z konstrukcjami warunkowego składankach takich jak
#if
(itd.).W tej kwestii nie będę się z tobą kłócił, ponieważ uważam, że niepreprocesorowe rozwiązania kompilacji warunkowej w popularnych językach są wyjątkowo kłopotliwe (jak wstrzykiwanie kodu bajtowego w Javie). Ale języki takie jak D wymyślają rozwiązania, które nie wymagają preprocesora i nie są bardziej kłopotliwe niż stosowanie warunkowych preprocesorów, a jednocześnie są znacznie mniej podatne na błędy.
źródło
In fact, I can't think of a single use-case for macros that can't easily be duplicated in another way
: przynajmniej w C nie można tworzyć identyfikatorów (nazw zmiennych) przy użyciu łączenia znaczników bez użycia makr.enum
do zdefiniowania warunków i stałej tablicy ciągów do zdefiniowania komunikatów może spowodować problemy, jeśli wyliczanie i tablica nie są zsynchronizowane. Użycie makra do zdefiniowania wszystkich par (wyliczenie, ciąg), a następnie dwukrotne włączenie tego makra z innymi właściwymi definicjami w zakresie za każdym razem pozwoliłoby na umieszczenie każdej wartości wyliczenia obok łańcucha.Największym problemem, jaki widziałem w przypadku makr, jest to, że gdy są intensywnie używane, mogą bardzo utrudniać odczytanie i utrzymanie kodu, ponieważ pozwalają ukryć logikę w makrze, które może być lub nie być łatwe do znalezienia (i może być trywialne ).
źródło
Nie zacznijmy od zauważenia, że MAKRO w C / C ++ są bardzo ograniczone, podatne na błędy i niezbyt przydatne.
MAKRO zaimplementowane w powiedzmy LISP lub asembler z / OS mogą być niezawodne i niezwykle przydatne.
Ale z powodu nadużycia ograniczonej funkcjonalności w C mają złą reputację. Więc nikt już nie implementuje makr, zamiast tego dostajesz takie szablony, jak niektóre proste makra, takie jak adnotacje Javy, które wykonują niektóre bardziej złożone makra.
źródło