2010-07-09 10 views
9

To jest bardziej teoretyczne pytanie dotyczące makr (jak sądzę). Wiem, że makra pobierają kod źródłowy i generują kod obiektowy bez jego oceny, umożliwiając programistom tworzenie bardziej wszechstronnych struktur składniowych. Gdybym musiał sklasyfikować te dwa makropolecenia, powiedziałbym, że było makro "C" i makro "stylu Lispa".W jaki sposób język z obsługą makr monitoruje kod źródłowy do debugowania?

Wygląda na to, że debugowanie makr może być nieco trudne, ponieważ w czasie wykonywania kod, który faktycznie działa, różni się od źródła.

W jaki sposób debugger śledzi wykonanie programu pod kątem wstępnie przetworzonego kodu źródłowego? Czy istnieje specjalny "tryb debugowania", który należy ustawić, aby przechwytywać dodatkowe dane o makrze?

W języku C mogę zrozumieć, że ustawiłeś przełącznik czasu kompilacji do debugowania, ale jak zrobiłby to język interpretowany, na przykład niektóre formy Lisp?

Przepraszam, że nie wypróbowywano tego, ale program narzędziowy LISP wymaga więcej czasu, niż muszę wydać, aby dowiedzieć się.

Odpowiedz

3

Nie sądzę, że istnieje zasadnicza różnica w makrach "stylu C" i "stylach Lispa" w sposobie ich kompilacji. Obie transformują źródło przed właściwym kompilatorem. Główną różnicą jest to, że makropolecenia C używają preprocesora C (słabszego języka dodatkowego, który jest głównie do prostego podstawiania ciągów), podczas gdy makra Lispa są zapisane w samym Lispie (i dlatego mogą w ogóle coś zrobić).

(Na marginesie: od jakiegoś czasu nie widziałem nieskomplikowanego Lispa ... na pewno nie od przełomu wieków, ale jeśli cokolwiek, zinterpretowanie sprawi, że łatwiej będzie rozwiązać problem z debugowaniem makro, nie jest trudniejsze, ponieważ masz więcej informacji.)

Zgadzam się z Michaelem: Nie widziałem debuggera dla C obsługującego w ogóle makra. Kod, który używa makr zostanie przekształcony, zanim cokolwiek się stanie. "debug" mode for compiling C code ogólnie oznacza po prostu, że przechowuje functions, types, variables, filenames, and such - Nie sądzę, aby któryś z nich przechowywał informacje o makrach.

  • do debugowania programów, które używają makr, Lisp jest prawie taka sama jak C tutaj: Twój debugger widzi skompilowany kod, nie makro aplikacji. Zazwyczaj makra są przechowywane w prosty sposób i debugowane niezależnie przed użyciem, aby uniknąć potrzeby tego, podobnie jak C.

  • Do debugowania makra same, przed wyjazdem i używać go gdzieś, Lisp ma możliwości które czynią to łatwiejsze niż w C, np na repl i macroexpand-1 (choć w C istnieje oczywiście sposób na makro wyrzuć cały plik, w całości, raz na ). Możesz zobaczyć przed i po makro wyodrębniania, bezpośrednio w edytorze, gdy napiszesz go.

nie pamiętam żadnego czasu natknąłem sytuacji, gdy debugowanie do makro definicji byłaby sama zostały użyteczne. Albo jest to błąd w definicji makra, w którym to przypadku macroexpand-1 natychmiast izoluje problem, albo jest to błąd poniżej, w którym to przypadku normalne funkcje debugowania działają dobrze i nie obchodzi mnie, że makro-rozrost nastąpił pomiędzy dwiema ramami mojego połączenia stos.

1

Nie wiem o makrach LISP (podejrzewam, że prawdopodobnie są one całkiem inne niż w makrach C) lub debugowaniu, ale wiele - prawdopodobnie większość - debugerów C/C++ nie radzą sobie z debugowaniem kodu źródłowego makr preprocesora w języku C .

Ogólnie rzecz biorąc, debugery C/C++ nie "wkraczają" w definicję makra. Jeśli makro zostanie rozwinięte w wiele instrukcji, debugger zwykle pozostanie na tym samym wierszu źródłowym (gdzie wywoływane jest makro) dla każdej operacji "kroku" debugera.

Może to spowodować, że makra debugowania będą trochę bardziej bolesne, niż mogłyby być w innym przypadku - kolejny powód, aby ich unikać w C/C++. Jeśli makro źle zachowuje się w tajemniczy sposób, przechodzę do trybu składania, aby debugować lub rozszerzyć makro (ręcznie lub przy użyciu przełącznika kompilatora). To dość rzadkie, że musisz iść do tego ekstremum; jeśli piszesz skomplikowane makra, prawdopodobnie podejmiesz niewłaściwe podejście.

1

Zazwyczaj w debugowaniu kodu źródłowego w języku C występuje rozdrobnienie linii (polecenie "następny") lub poziom szczegółowości na poziomie instrukcji ("krok w"). Makroprocesory wstawiają specjalne dyrektywy do przetworzonego źródła, które pozwalają kompilatorowi mapować skompilowane sekwencje instrukcji CPU do kodu źródłowego.

W Lispie nie istnieje żadna konwencja między makrami i kompilatorem, która umożliwia śledzenie kodu źródłowego do kompilowanego mapowania kodu, więc nie zawsze jest możliwe wykonanie pojedynczego kroku w kodzie źródłowym.

Oczywistą opcją jest wykonanie pojedynczego kroku w kodzie makroekspandowanym. Kompilator już widzi końcową, rozszerzoną, wersję kodu i może śledzić kod źródłowy do mapowania kodu maszynowego.

Inną opcją jest użycie faktu, że wyrażenia lisp podczas manipulacji mają tożsamość. Jeśli makro jest proste i po prostu dokonuje destrukcji i wkleja kod do szablonu, niektóre wyrażenia rozszerzonego kodu będą identyczne (w odniesieniu do porównania EQ) do wyrażeń, które zostały odczytane z kodu źródłowego. W tym przypadku kompilator może odwzorować niektóre wyrażenia z rozszerzonego kodu na kod źródłowy.

2

Naprawdę powinieneś sprawdzić, jakie wsparcie ma Racket do debugowania kodu za pomocą makr. To wsparcie ma dwa aspekty, o czym wspomina Ken. Z jednej strony istnieje kwestia debugowania makr: w Common Lisp najlepszym sposobem na to jest ręczne rozwijanie makr. Z CPP sytuacja jest podobna, ale bardziej prymitywna - uruchamiasz kod tylko poprzez rozszerzenie CPP i sprawdzasz wynik. Jednak oba te są niewystarczające dla bardziej zaangażowanych makr, i to była motywacja do posiadania Rakietki - pokazuje ona kolejno kroki rozszerzenia składni, z dodatkowymi znakami opartymi na gui dla takich rzeczy jak związane identyfikatory itp.

Po stronie przy użyciu makr, Racket zawsze był bardziej zaawansowany niż inne implementacje Scheme i Lisp. Chodzi o to, że każde wyrażenie (jako obiekt syntaktyczny) to kod plus dodatkowe dane, które zawierają jego lokalizację źródłową. W ten sposób, gdy formularz jest makrem, rozszerzony kod, który ma części pochodzące z makra, będzie miał poprawną lokalizację źródłową - z definicji makra, a nie z jego użycia (gdzie formularze nie są tak naprawdę obecne). Niektóre implementacje Scheme i Lisp wdrożą ograniczone do tego przy użyciu tożsamości podformularzy, jak wspomniano w dmitry-vk.

+0

Ale odwzorowanie skompilowanego kodu z powrotem do makra nie rozwiązuje następującego problemu: gdy makro generuje kod na podstawie deklaracji deklaratywnej (np. Makro, które generuje parser z gramatyki bezkontekstowej) byłoby bardzo przydatne podczas debugowania, aby móc znaleźć jaka część danych wejściowych jest "aktywna" (np. jaka reguła gramatyki jest dopasowywana, jeśli jest to możliwe). To wymagałoby od programu piszącego makro wyraźnego określenia, które części generowanej mapy kodu odpowiadają częściom wejściowym makra. Czy makra Racket mają taką zdolność? W przeciwnym razie jest to odpowiednik debugowania (częściowo) rozszerzonego kodu. –

+0

"W przeciwnym razie jest to odpowiednik debugowania (częściowo) rozszerzonego kodu." Nie myślę, że się mylę w tym zdaniu. Zignoruj ​​to. –

+0

dmity-vk: Right - specjalna formuła 'syntax' w Racket w zasadzie dba o połączenie fragmentów kodu od użytkownika makra z fragmentami kodu z samego makra i upewnienie się, że lokalizacja źródła na wynikowych formularzach jest poprawne we wszystkich formach. –

0

Prostą odpowiedzią jest to, że jest skomplikowana ;-) Istnieje kilka różnych rzeczy, które przyczyniają się do debugowania programu, a jeszcze więcej do śledzenia makr.

W językach C i C, preprocesor służy do rozwijania makr i zawiera rzeczywisty kod źródłowy. Pochodzące nazwy plików i numery linii są śledzone w tym rozszerzonym pliku źródłowym przy użyciu dyrektyw #line.

http://msdn.microsoft.com/en-us/library/b5w2czay(VS.80).aspx

Kiedy C lub C Program ++ jest skompilowane z debugowanie włączone, asembler generuje dodatkowe informacje w pliku obiektu, który śledzi linie źródłowe, nazw symbol, typ deskryptory itd

http://sources.redhat.com/gdb/onlinedocs/stabs.html

System operacyjny ma funkcje, które umożliwiają dołączenie debuggera do procesu i kontrolowanie wykonania procesu; pauzowanie, pojedyncze wprowadzanie, itp.

Po dołączeniu debuggera do programu, tłumaczy on stos procesu i licznik programu z powrotem do postaci symbolicznej, sprawdzając znaczenie adresów programów w informacji o debugowaniu.

Języki dynamiczne zazwyczaj są wykonywane na maszynie wirtualnej, niezależnie od tego, czy jest to interpreter, czy maszyna wirtualna kodu bajtowego. Jest to VM, która dostarcza hooki, aby umożliwić debuggerowi kontrolowanie przepływu programu i sprawdzanie stanu programu.