Odpowiedzi:
Innym powodem, dla którego kompilatory produkują zestaw zamiast właściwego kodu maszynowego są:
add eax,2
Może być przetłumaczony na 83 c0 02
lub w 66 83 c0 02
zależności od ostatnio wydanej dyrektywy, takiej jak use16
.
Kompilator zwykle konwertuje kod wysokiego poziomu bezpośrednio na język maszynowy, ale można go zbudować modułowo, tak aby jeden back-end emitował kod maszynowy, a drugi kod asemblera (np. GCC). W fazie generowania kodu powstaje „kod”, który jest pewną wewnętrzną reprezentacją kodu maszynowego, który następnie musi zostać przekonwertowany na użyteczny format, taki jak język maszynowy lub kod asemblera.
Historycznie wiele znaczących kompilatorów wyprowadzało kod maszynowy bezpośrednio. Są jednak pewne trudności. Zasadniczo komuś, kto próbuje potwierdzić, że kompilator działa poprawnie, łatwiej będzie sprawdzić dane wyjściowe kodu asemblera niż kod maszynowy. Ponadto możliwe jest (i było to historycznie powszechne) użycie jednoprzebiegowego kompilatora C lub Pascal w celu utworzenia pliku w asemblerze, który można następnie przetworzyć za pomocą dwuprzebiegowego asemblera. Bezpośrednie generowanie kodu wymagałoby albo użycia dwuprzebiegowego kompilatora C lub Pascal, albo kompilatora jednoprzebiegowego, po którym następowałyby pewne sposoby poprawiania adresów przeskakiwania w przód [jeśli środowisko wykonawcze udostępnia rozmiar uruchomionego programu w stałe miejsce, kompilator może napisać listę poprawek na końcu kodu i pozwolić, aby kod startowy zastosował te poprawki w czasie wykonywania; takie podejście zwiększyłoby rozmiar pliku wykonywalnego o około cztery bajty na punkt łaty, ale poprawiłoby szybkość generowania programu].
Jeśli celem jest szybki kompilator, bezpośrednie generowanie kodu może działać dobrze. Jednak w przypadku większości projektów koszt wygenerowania kodu w języku asemblera i jego złożenia naprawdę nie jest obecnie poważnym problemem. Posiadanie kompilatorów do tworzenia kodu w formie, która może ładnie współdziałać z kodem produkowanym przez inne kompilatory, jest na ogół wystarczająco dużą korzyścią, aby uzasadnić wydłużenie czasu kompilacji.
Nawet platformy korzystające z tego samego zestawu instrukcji mogą mieć różne formaty plików obiektów relokowalnych. Mogę wymyślić „a.out” (wczesny UNIX), OMF, MZ (MS-DOS EXE), NE (16-bitowy system Windows), COFF (UNIX System V), Mach-O (OS X i iOS) i ELF (Linux i inne), a także ich warianty, takie jak XCOFF (AIX), ECOFF (SGI) i Portable Executable (PE) oparty na COFF w 32-bitowym systemie Windows. Kompilator, który tworzy język asemblera, nie musi wiele wiedzieć o formatach plików obiektowych, umożliwiając asemblerowi i linkerowi zgromadzenie tej wiedzy w osobnym procesie.
Zobacz także Różnica między OMF i COFF na temat przepełnienia stosu.
Zwykle kompilatory działają wewnętrznie z sekwencjami instrukcji. Każda instrukcja będzie reprezentowana przez strukturę danych reprezentującą jej nazwę operacji, operandy i tak dalej. Kiedy argumenty są adresami, adresy te będą zwykle symbolicznymi odniesieniami, a nie konkretnymi wartościami.
Wyjście asemblera jest stosunkowo proste. To w zasadzie kwestia wzięcia wewnętrznej struktury danych kompilatora i zrzucenia go do pliku tekstowego w określonym formacie. Dane wyjściowe asemblera są również stosunkowo łatwe do odczytania, co jest przydatne, gdy trzeba sprawdzić, co robi kompilator.
Wyprowadzanie plików obiektów binarnych to znacznie więcej pracy. Autor kompilatora musi wiedzieć, w jaki sposób kodowane są wszystkie instrukcje (co może być dalekie od trywialnych w przypadku niektórych CPUS), musi przekonwertować niektóre odwołania symboliczne na adresy względne licznika programu, a inne na jakąś formę metadanych w pliku obiektu binarnego . Muszą napisać wszystko w formacie ściśle zależnym od systemu.
Tak, absolutnie można stworzyć kompilator, który może wyprowadzać obiekty binarne bezpośrednio, bez zapisywania asemblera jako kroku pośredniego. Pytanie, podobnie jak wiele innych rzeczy w tworzeniu oprogramowania, brzmi: czy skrócenie czasu kompilacji jest warte dodatkowych prac rozwojowych i konserwacyjnych?
Kompilator, którego znam najlepiej (freepascal) może wyświetlać asembler na wszystkich platformach, ale może wysyłać tylko obiekty binarne bezpośrednio na podzestawie platform.
Kompilator powinien być w stanie wygenerować wyjście asemblera oprócz normalnego kodu relokowalnego dla dobra programisty.
Pewnego razu po prostu nie znalazłem błędu w programie C uruchomionym na Unix System V na maszynie LSI-11. Wydawało się, że nic nie działa. Wreszcie w desperacji kazałem protilowanemu kompilatorowi C wydalić wersję asemblera jego tłumaczenia. W końcu znalazłem błąd! Kompilator przydzielał więcej rejestrów niż istniało w maszynie! (Kompilator przypisał rejestry od R0 do R8 na maszynie z tylko rejestrami od R0 do R7.) Udało mi się obejść błąd w kompilatorze i mój program działał.
Kolejną korzyścią z posiadania wyjścia asemblera jest próba użycia „standardowych” bibliotek, które używają różnych protokołów przekazywania parametrów. Późniejsze kompilatory C pozwalają mi ustawić protokół z parametrem („pascal” spowoduje, że kompilator doda parametry w podanej kolejności, w przeciwieństwie do standardu C odwracania kolejności).
Kolejną korzyścią jest umożliwienie programistowi zobaczenia, jakie przerażające zadanie wykonuje jego kompilator. Prosta instrukcja C wymaga około 44 instrukcji maszyny. Wartości są ładowane z pamięci, a następnie szybko odrzucane. etc, etc, etc ...
Osobiście uważam, że posiadanie kompilatora zamiast relokowalnego modułu obiektowego jest naprawdę głupie. Podczas kompilacji programu kompilator zbiera wiele informacji o Twoim programie. Zazwyczaj przechowuje wszystkie te informacje w czymś zwanym tablicą symboli. Po wydaleniu kodu asemblera wyrzuca całą tę tabelę informacyjną. Asembler następnie analizuje wydalony kod i ponownie zbiera niektóre informacje, które kompilator już miał. Jednak asembler nie wie nic o instrukcjach If instrukcji For lub instrukcji While. Brakuje więc wszystkich tych informacji. Następnie asembler produkuje relokowalny moduł obiektowy, czego nie zrobił kompilator.
Dlaczego???