Czy są jakieś sprytne przypadki modyfikacji kodu w czasie wykonywania?


119

Czy potrafisz wymyślić jakieś uzasadnione (inteligentne) zastosowania do modyfikacji kodu w czasie wykonywania (program modyfikujący swój własny kod w czasie wykonywania)?

Nowoczesne systemy operacyjne wydają się nienawidzić programów, które to robią, ponieważ wirusy wykorzystują tę technikę w celu uniknięcia wykrycia.

Wszystko, co przychodzi mi do głowy, to jakiś rodzaj optymalizacji środowiska wykonawczego, który usunąłby lub dodał kod, wiedząc w czasie wykonywania coś, czego nie można poznać w czasie kompilacji.


8
W nowoczesnych architekturach bardzo przeszkadza to w buforowaniu i potoku instrukcji: samomodyfikujący się kod nie modyfikowałby pamięci podręcznej, więc potrzebne byłyby bariery, a to prawdopodobnie spowolniłoby kod. I nie możesz modyfikować kodu, który jest już w potoku instrukcji. Zatem każda optymalizacja oparta na samomodyfikującym się kodzie musi być wykonana na długo przed uruchomieniem kodu, aby mieć wpływ na wydajność lepszy niż, powiedzmy, sprawdzenie w czasie wykonywania.
Alexandre C.

7
@Alexandre: często zdarza się, że samomodyfikujący się kod powoduje, że modyfikacje zmieniają się rzadko (np. Raz, dwa razy) pomimo wykonania dowolnej liczby razy, więc jednorazowy koszt może być nieistotny.
Tony Delroy

7
Nie wiem, dlaczego jest to oznaczone jako C lub C ++, ponieważ żaden z nich nie ma do tego żadnego mechanizmu.
MSalters

4
@Alexandre: Microsoft Office jest znany z tego właśnie. W konsekwencji (?) Wszystkie procesory x86 mają doskonałe wsparcie dla samomodyfikującego się kodu. Na innych procesorach konieczna jest kosztowna synchronizacja, co czyni całość mniej atrakcyjną.
Mackie Messer

3
@ Cawas: Zazwyczaj oprogramowanie do automatycznej aktualizacji pobiera nowe zestawy i / lub pliki wykonywalne i nadpisuje istniejące. Następnie uruchomi ponownie oprogramowanie. To właśnie robią Firefox, Adobe itp. Samodzielna modyfikacja zazwyczaj oznacza, że ​​podczas wykonywania kod jest przepisywany w pamięci przez aplikację ze względu na niektóre parametry i niekoniecznie jest utrwalany z powrotem na dysk. Na przykład, może zoptymalizować ścieżki całego kodu, jeśli może inteligentnie wykryć, że ścieżki te nie będą wykonywane podczas tego konkretnego uruchomienia, aby przyspieszyć wykonanie.
NotMe

Odpowiedzi:


117

Istnieje wiele ważnych przypadków modyfikacji kodu. Generowanie kodu w czasie wykonywania może być przydatne do:

  • Niektóre maszyny wirtualne używają kompilacji JIT w celu poprawy wydajności.
  • Generowanie wyspecjalizowanych funkcji w locie jest od dawna powszechne w grafice komputerowej. Zobacz np. Rob Pike i Bart Locanthi i John Reiser Hardware Software Tradeoffs for Bitmap Graphics on the Blit (1984) lub ten post (2006) autorstwa Chrisa Lattnera na temat wykorzystania LLVM przez Apple do specjalizacji kodu środowiska uruchomieniowego w ich stosie OpenGL.
  • W niektórych przypadkach oprogramowanie ucieka się do techniki znanej jako trampolina, która polega na dynamicznym tworzeniu kodu na stosie (lub w innym miejscu). Przykładami są zagnieżdżone funkcje GCC i mechanizm sygnałowy niektórych Uniksów.

Czasami kod jest tłumaczony na kod w czasie wykonywania (nazywa się to dynamicznym tłumaczeniem binarnym ):

  • Emulatory, takie jak Rosetta firmy Apple, wykorzystują tę technikę do przyspieszenia emulacji. Innym przykładem jest oprogramowanie do morfowania kodu firmy Transmeta .
  • Wyrafinowane debuggery i profilery, takie jak Valgrind lub Pin, używają go do instrumentowania kodu podczas jego wykonywania.
  • Przed wprowadzeniem rozszerzeń do zestawu instrukcji x86 oprogramowanie do wirtualizacji, takie jak VMWare, nie mogło bezpośrednio uruchamiać uprzywilejowanego kodu x86 wewnątrz maszyn wirtualnych. Zamiast tego musiał tłumaczyć w locie wszelkie problematyczne instrukcje na bardziej odpowiedni kod niestandardowy.

Modyfikacja kodu może posłużyć do obejścia ograniczeń zestawu instrukcji:

  • Był czas (wiem, dawno temu), kiedy komputery nie miały instrukcji powrotu z podprogramu lub pośredniego adresowania pamięci. Kod samomodyfikujący się był jedynym sposobem implementacji podprogramów, wskaźników i tablic .

Więcej przypadków modyfikacji kodu:

  • Wiele debugerów zastępuje instrukcje implementacji punktów przerwania .
  • Niektóre konsolidatory dynamiczne modyfikują kod w czasie wykonywania. Ten artykuł zawiera podstawowe informacje na temat relokacji bibliotek DLL systemu Windows w czasie wykonywania, co w rzeczywistości jest formą modyfikacji kodu.

10
Ta lista wydaje się mieszać przykłady kodu, który sam się modyfikuje, i kodu, który modyfikuje inny kod, na przykład konsolidatory.
AShelly

6
@AShelly: Cóż, jeśli uważasz, że dynamiczny konsolidator / ładowacz jest częścią kodu, to sam się modyfikuje. Mieszkają w tej samej przestrzeni adresowej, więc myślę, że to ważny punkt widzenia.
Mackie Messer

1
Ok, lista rozróżnia teraz programy i oprogramowanie systemowe. Mam nadzieję, że to ma sens. Ostatecznie każda klasyfikacja jest dyskusyjna. Wszystko sprowadza się do tego, co dokładnie uwzględnisz w definicji programu (lub kodu).
Mackie Messer,

35

Dokonano tego w grafice komputerowej, w szczególności w programach renderujących do celów optymalizacji. W czasie wykonywania programu sprawdzany jest stan wielu parametrów i generowana jest zoptymalizowana wersja kodu rasteryzatora (potencjalnie eliminująca wiele warunków), co pozwala na znacznie szybsze renderowanie elementów graficznych, np. Trójkątów.


5
Ciekawą lekturą są 3-częściowe artykuły Michaela Abrasha Pixomatic na DDJ: drdobbs.com/architecture-and-design/184405765 , drdobbs.com/184405807 , drdobbs.com/184405848 . Drugi link (Część 2) mówi o spawarce kodu Pixomatic dla potoku pikseli.
typo.pl

1
Bardzo fajny artykuł na ten temat. Od 1984 roku, ale nadal dobrze się czyta: Rob Pike i Bart Locanthi i John Reiser. Kompromisy sprzętowe dla grafiki bitmapowej na Blit .
Mackie Messer

5
Charles Petzold wyjaśnia jeden taki przykład w książce zatytułowanej „Piękny kod”: amazon.com/Beautiful-Code-Leading-Programmers-Practice/dp/ ...
Nawaz

3
Ta odpowiedź mówi o generowaniu kodu, ale pytanie dotyczy modyfikacji kodu ...
Timwi

3
@Timwi - zmodyfikował kod. Zamiast obsługiwać duży łańcuch if, raz przeanalizował kształt i przepisał moduł renderujący, aby był skonfigurowany pod kątem prawidłowego typu kształtu bez konieczności sprawdzania za każdym razem. Co ciekawe, jest to teraz powszechne w przypadku kodu opencl - ponieważ jest kompilowany w locie, możesz go przepisać dla konkretnego przypadku w czasie wykonywania
Martin Beckett

23

Jednym z ważnych powodów jest to, że w zestawie instrukcji asm brakuje pewnych niezbędnych instrukcji, które można by stworzyć samodzielnie. Przykład: Na x86 nie ma możliwości utworzenia przerwania dla zmiennej w rejestrze (np. Wykonaj przerwanie z numerem przerwania w ax). Dozwolone były tylko liczby const zakodowane w opkodzie. Za pomocą samomodyfikującego się kodu można by naśladować to zachowanie.


Słusznie. Czy jest jakieś zastosowanie tej techniki? Wydaje się to niebezpieczne.
Alexandre C.

4
@Alexandre C .: Jeśli dobrze pamiętam, wiele bibliotek wykonawczych (C, Pascal, ...) musiało razy DOS wykonać funkcję, aby wykonać wywołanie przerwania. Ponieważ takie funkcje otrzymują numer przerwania jako parametr, trzeba było podać taką funkcję (oczywiście, gdyby liczba była stała, można było wygenerować właściwy kod, ale nie było to gwarantowane). I wszystkie biblioteki zaimplementowały go za pomocą samomodyfikującego się kodu.
flolo

Możesz użyć obudowy przełącznika, aby zrobić to bez modyfikacji kodu. Zmniejszenie polega na tym, że kod wyjściowy będzie większy
phuclv

17

Niektóre kompilatory używały go do inicjalizacji zmiennej statycznej, unikając kosztu warunkowego przy kolejnych dostępach. Innymi słowy, implementują „wykonaj ten kod tylko raz” przez nadpisanie tego kodu bez operacji przy pierwszym wykonaniu.


1
Bardzo fajnie, zwłaszcza jeśli chodzi o unikanie blokad / odblokowań muteksów.
Tony Delroy

2
Naprawdę? Jak to się dzieje w przypadku kodu opartego na pamięci ROM lub kodu wykonywanego w segmencie kodu chronionym przed zapisem?
Ira Baxter

1
@Ira Baxter: każdy kompilator, który emituje relokowalny kod, wie, że segment kodu jest zapisywalny, przynajmniej podczas uruchamiania. Tak więc stwierdzenie „używali go niektórzy kompilatorzy” jest nadal możliwe.
MSalters

17

Jest wiele przypadków:

  • Wirusy często wykorzystywały samomodyfikujący się kod do „deobfuskacji” swojego kodu przed wykonaniem, ale ta technika może być również przydatna w frustrującej inżynierii wstecznej, łamaniu zabezpieczeń i niechcianym hakowaniu
  • W niektórych przypadkach może wystąpić szczególny moment w trakcie działania (np. Natychmiast po przeczytaniu pliku konfiguracyjnego), kiedy wiadomo, że - przez resztę życia procesu - określona gałąź będzie zawsze lub nigdy brana: zamiast niepotrzebnie sprawdzając jakąś zmienną w celu określenia, która droga rozgałęzienia, sama instrukcja rozgałęzienia może zostać odpowiednio zmodyfikowana
    • np. może się okazać, że obsługiwany będzie tylko jeden z możliwych typów pochodnych, tak że wirtualna wysyłka może zostać zastąpiona określonym wywołaniem
    • Po wykryciu, który sprzęt jest dostępny, użycie pasującego kodu może zostać zakodowane na stałe
  • Niepotrzebny kod można zastąpić instrukcjami bez operacji lub przeskoczyć nad nim, lub przenieść następny bit kodu bezpośrednio na miejsce (łatwiej, jeśli używasz kodów operacyjnych niezależnych od pozycji)
  • Kod napisany w celu ułatwienia jego własnego debugowania może wprowadzić instrukcję pułapki / sygnału / przerwania oczekiwaną przez debugger w strategicznej lokalizacji.
  • Niektóre wyrażenia predykatów oparte na danych wejściowych użytkownika mogą być kompilowane do kodu natywnego przez bibliotekę
  • Umieszczanie prostych operacji, które nie są widoczne do czasu wykonania (np. Z dynamicznie ładowanej biblioteki) ...
  • Warunkowe dodanie kroków samodzielnej instrumentacji / profilowania
  • Pęknięcia mogą być implementowane jako biblioteki, które modyfikują kod, który je ładuje (nie modyfikuje się „samodzielnie” dokładnie, ale wymaga tych samych technik i uprawnień).
  • ...

Modele bezpieczeństwa niektórych systemów operacyjnych oznaczają, że samomodyfikujący się kod nie może działać bez uprawnień roota / administratora, co sprawia, że ​​jest niepraktyczny do użytku ogólnego.

Z Wikipedii:

Oprogramowanie działające w systemie operacyjnym ze ścisłymi zabezpieczeniami W ^ X nie może wykonywać instrukcji na stronach, na których jest dozwolone - tylko sam system operacyjny może zarówno zapisywać instrukcje w pamięci, jak i później wykonywać te instrukcje.

W takich systemach operacyjnych nawet programy takie jak maszyna wirtualna Java potrzebują uprawnień roota / administratora, aby wykonać swój kod JIT. ( Więcej informacji można znaleźć pod adresem http://en.wikipedia.org/wiki/W%5EX )


2
Nie potrzebujesz uprawnień roota do samodzielnego modyfikowania kodu. Maszyna wirtualna Java również nie.
Mackie Messer

Nie wiedziałem, że niektóre systemy operacyjne są tak surowe. Ale z pewnością ma to sens w niektórych zastosowaniach. Zastanawiam się jednak, czy wykonanie Javy z uprawnieniami roota faktycznie zwiększa bezpieczeństwo ...
Mackie Messer

@Mackie: Myślę, że musi to zmniejszyć, ale może może ustawić uprawnienia do pamięci, a następnie zmienić efektywny identyfikator z powrotem na jakieś konto użytkownika ...?
Tony Delroy

Tak, spodziewałbym się, że będą mieli drobnoziarnisty mechanizm nadawania uprawnień towarzyszących ścisłemu modelowi bezpieczeństwa.
Mackie Messer

15

System operacyjny Synthesis zasadniczo częściowo ocenił Twój program pod kątem wywołań API i zastąpił kod systemu operacyjnego wynikami. Główną korzyścią jest to, że zniknęło wiele sprawdzania błędów (ponieważ jeśli Twój program nie prosi systemu operacyjnego o zrobienie czegoś głupiego, nie musi tego sprawdzać).

Tak, to przykład optymalizacji czasu wykonywania.


Nie widzę sensu. Jeśli powiesz, że wywołanie systemowe zostanie zabronione przez system operacyjny, prawdopodobnie otrzymasz z powrotem błąd, który będziesz musiał sprawdzić w kodzie, prawda? Wydaje mi się, że modyfikowanie pliku wykonywalnego zamiast zwracania kodu błędu jest rodzajem przepracowania.
Alexandre C.

@Alexandre C.: w ten sposób możesz wyeliminować sprawdzanie wskaźnika zerowego. Często dla wywołującego jest oczywiste, że argument jest prawidłowy.
MSalters

@Alexandre: Możesz przeczytać wyniki badań pod linkiem. Myślę, że dostali dość imponujące przyspieszenia i o to właśnie chodzi: -}
Ira Baxter

2
W przypadku stosunkowo błahych wywołań systemowych niezwiązanych z operacjami we / wy oszczędności są znaczące. Na przykład, jeśli piszesz demona dla Uniksa, jest kilka wywołań systemowych typu boiler-plate, które wykonujesz, aby rozłączyć stdio, ustawić różne programy obsługi sygnału itp. Jeśli wiesz, że parametry wywołania są stałymi i wyniki będą zawsze takie same (na przykład zamykające wejście standardowe), duża część kodu wykonywanego w ogólnym przypadku jest niepotrzebna.
Mark Bessey

1
Jeśli przeczytasz tezę, rozdział 8 zawiera naprawdę imponujące liczby na temat nietrywialnych wejść / wyjść w czasie rzeczywistym do akwizycji danych. Pamiętasz, że to praca z połowy lat 80., a maszyna, na której pracował, miała 10 lat? Mhz 68000, był w stanie w oprogramowaniu przechwytywać dane audio o jakości CD (44 000 próbek na sekundę) za pomocą zwykłego starego oprogramowania. Twierdził, że stacje robocze Sun (klasyczny Unix) mogą osiągnąć tylko około 1/5 tej wartości. Jestem starym koderem asemblera z tamtych czasów i to jest dość spektakularne.
Ira Baxter,

9

Wiele lat temu spędziłem poranek próbując zdebugować jakiś samomodyfikujący się kod, jedna instrukcja zmieniła adres docelowy następnej instrukcji, tj. Obliczałem adres oddziału. Został napisany w języku asemblera i działał doskonale, kiedy przechodziłem przez program po jednej instrukcji na raz. Ale kiedy uruchomiłem program, nie udało się. W końcu zdałem sobie sprawę, że maszyna pobierała 2 instrukcje z pamięci i (ponieważ instrukcje były rozmieszczone w pamięci) instrukcja, którą modyfikowałem, została już pobrana, a zatem maszyna wykonywała niezmodyfikowaną (niepoprawną) wersję instrukcji. Oczywiście, kiedy debugowałem, wykonywałem tylko jedną instrukcję naraz.

Chodzi mi o to, że samomodyfikujący się kod może być wyjątkowo nieprzyjemny do testowania / debugowania i często ma ukryte założenia dotyczące zachowania maszyny (czy to sprzętowej, czy wirtualnej). Co więcej, system nigdy nie mógł udostępniać stron kodowych między różnymi wątkami / procesami wykonywanymi na (obecnie) maszynach wielordzeniowych. Eliminuje to wiele korzyści dla pamięci wirtualnej itp. Unieważniłoby również optymalizacje gałęzi wykonane na poziomie sprzętu.

(Uwaga - nie uwzględniam JIT w kategorii kodu samomodyfikującego się. JIT tłumaczy jedną reprezentację kodu na alternatywną reprezentację, nie modyfikuje kodu)

Podsumowując, to po prostu zły pomysł - naprawdę schludny, naprawdę niejasny, ale naprawdę zły.

oczywiście - jeśli wszystko, co masz, to 8080 i ~ 512 bajtów pamięci, być może będziesz musiał uciekać się do takich praktyk.


1
Nie wiem, dobro i zło nie wydają się być odpowiednimi kategoriami, aby o tym myśleć. Oczywiście powinieneś naprawdę wiedzieć, co robisz i dlaczego to robisz. Ale programista, który napisał ten kod, prawdopodobnie nie chciał, abyś widział, co robi program. Oczywiście to paskudne, jeśli musisz debugować kod w ten sposób. Ale ten kod najprawdopodobniej miał taki być.
Mackie Messer

Nowoczesne procesory x86 mają silniejsze wykrywanie SMC niż jest to wymagane na papierze: obserwowanie pobierania nieaktualnych instrukcji na x86 z samomodyfikującym się kodem . W przypadku większości procesorów innych niż x86 (takich jak ARM) pamięć podręczna instrukcji nie jest spójna z pamięcią podręczną danych, więc ręczne opróżnianie / synchronizacja jest wymagane, zanim nowo zapisane bajty będą mogły być niezawodnie wykonane jako instrukcje. community.arm.com/processors/b/blog/posts/… . Tak czy inaczej, wydajność SMC jest okropna na nowoczesnych procesorach, chyba że zmodyfikujesz raz i uruchomisz wiele razy.
Peter Cordes,

7

Z punktu widzenia jądra systemu operacyjnego każdy kompilator Just In Time i Linker Runtime dokonuje samodzielnej modyfikacji tekstu programu. Wybitnym przykładem może być Google V8 ECMA Script Interpreter.


5

Innym powodem samomodyfikacji kodu (właściwie kodu „samoczynnego”) jest implementacja mechanizmu kompilacji Just-In-Time w celu zwiększenia wydajności. Np. Program, który czyta wyrażenie algebiczne i oblicza je na podstawie szeregu parametrów wejściowych, może przekształcić wyrażenie w kodzie maszynowym przed podaniem obliczenia.


5

Wiesz, stary kasztan, że nie ma logicznej różnicy między sprzętem a oprogramowaniem ... można też powiedzieć, że nie ma logicznej różnicy między kodem a danymi.

Co to jest samomodyfikujący się kod? Kod, który umieszcza wartości w strumieniu wykonania, aby można je było interpretować nie jako dane, ale jako polecenie. Oczywiście, istnieje teoretyczny punkt widzenia w językach funkcjonalnych, że tak naprawdę nie ma różnicy. Mówię, że e może to zrobić w prosty sposób w językach imperatywnych i kompilatorach / interpreterach bez domniemania równego statusu.

Mam na myśli to, w praktycznym sensie, że dane mogą zmieniać ścieżki wykonywania programu (w pewnym sensie jest to niezwykle oczywiste). Myślę o czymś w rodzaju kompilatora-kompilatora, który tworzy tabelę (tablicę danych), przez którą przechodzi się podczas analizowania, przechodzenia od stanu do stanu (a także modyfikowania innych zmiennych), tak jak program przechodzi od polecenia do polecenia , modyfikowanie zmiennych w procesie.

Więc nawet w zwykłym przypadku, gdy kompilator tworzy przestrzeń kodu i odwołuje się do w pełni oddzielnej przestrzeni danych (sterty), nadal można zmodyfikować dane, aby jawnie zmienić ścieżkę wykonywania.


4
Bez logicznej różnicy, prawda. Nie widziałem jednak zbyt wielu samomodyfikujących się układów scalonych.
Ira Baxter

@Mitch, IMO zmiana ścieżki exec nie ma nic wspólnego z (samo-) modyfikacją kodu. Poza tym mylisz dane z info. Nie mogę odpowiedzieć na komentarz z roku na moją odpowiedź w LSE b / c. Zostałem tam wyrzucony od lutego na 3 lata (1000 dni) za wyrażanie w meta-LSE mojego pov, że Amerykanie i Brytyjczycy nie posiadają angielskiego.
Gennady Vanin Геннадий Ванин

4

Zaimplementowałem program wykorzystujący ewolucję, aby stworzyć najlepszy algorytm. Wykorzystał samomodyfikujący się kod, aby zmodyfikować schemat DNA.


2

Jednym z przypadków użycia jest plik testowy EICAR, który jest legalnym plikiem wykonywalnym COM systemu DOS do testowania programów antywirusowych.

X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*

Musi używać modyfikacji kodu własnego, ponieważ plik wykonywalny musi zawierać tylko drukowalne / typowalne znaki ASCII z zakresu [21h-60h, 7Bh-7Dh], co znacznie ogranicza liczbę zakodowanych instrukcji

Szczegóły wyjaśniono tutaj


Jest również używany do wysyłania operacji zmiennoprzecinkowych w DOS

Niektóre kompilatory będą emitować CD xxxx z zakresu od 0x34-0x3B w miejsce instrukcji zmiennoprzecinkowych x87. Ponieważ CDjest to kod intinstrukcji, przeskoczy do przerwania 34h-3Bh i emuluje tę instrukcję w oprogramowaniu, jeśli koprocesor x87 nie jest dostępny. W przeciwnym razie program obsługi przerwań zastąpi te 2 bajty 9B Dx, aby późniejsze wykonania były obsługiwane bezpośrednio przez x87 bez emulacji.

Jaki jest protokół emulacji zmiennoprzecinkowej x87 w systemie MS-DOS?


1

Linux Kernel ma ładowalnych modułów jądra, które nie tylko to.

Emacs również ma tę zdolność i cały czas z niej korzystam.

Wszystko, co obsługuje dynamiczną architekturę wtyczki, zasadniczo modyfikuje jej kod w czasie wykonywania.


4
ledwie. posiadanie dynamicznie ładowanej biblioteki, która nie zawsze jest rezydentna, ma niewiele wspólnego z samomodyfikującym się kodem.
Dow

1

Przeprowadzam analizy statystyczne na podstawie stale aktualizowanej bazy danych. Mój model statystyczny jest zapisywany i przepisywany za każdym razem, gdy kod jest wykonywany, aby uwzględnić nowe dane, które stają się dostępne.


0

Scenariusz, w którym można to wykorzystać, to program nauczania. W odpowiedzi na dane wejściowe użytkownika program uczy się nowego algorytmu:

  1. wyszukuje istniejącą bazę kodu dla podobnego algorytmu
  2. jeśli w bazie kodu nie ma podobnego algorytmu, program po prostu dodaje nowy algorytm
  3. jeśli istnieje podobny algorytm, program (być może z pomocą użytkownika) modyfikuje istniejący algorytm, aby mógł służyć zarówno staremu, jak i nowemu celowi

Powstaje pytanie, jak to zrobić w Javie: Jakie są możliwości samodzielnej modyfikacji kodu Java?


-1

Najlepszą wersją mogą być makra Lisp. W przeciwieństwie do makr C, które są tylko preprocesorem, Lisp zapewnia dostęp do całego języka programowania przez cały czas. Jest to najpotężniejsza funkcja seplenienia i nie istnieje w żadnym innym języku.

W żadnym wypadku nie jestem ekspertem, ale jeden z facetów sepleniących mówi o tym! Jest powód, dla którego mówią, że Lisp jest najpotężniejszym językiem, a mądrzy ludzie nie, że prawdopodobnie mają rację.


2
Czy to faktycznie tworzy samomodyfikujący się kod, czy jest to po prostu potężniejszy preprocesor (taki, który będzie generował funkcje)?
Brendan Long

@Brendan: rzeczywiście, ale jest to właściwy sposób na przetwarzanie wstępne . Nie ma tu żadnej modyfikacji kodu uruchomieniowego.
Alexandre C.,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.