30.10.2024
Przy wszystkich zaletach języków wysokiego poziomu, nauka języka asemblera i języków maszynowych dalej ma sporo sensu.
Przy wszystkich zaletach języków wysokiego poziomu, nauka języka asemblera i języków maszynowych dalej ma sporo sensu.
Miesiąc temu pisałem o asemblerze niskiego poziomu i językach maszynowych oraz o tym, że asembler jest mnemoniczną reprezentacją zer i jedynek języka maszynowego. Tym razem opowiem, czym są języki wysokiego poziomu, dlaczego zostały wynalezione i czemu ludzie ciągle uczą się niektórych języków asemblera/maszynowych.
W językach asemblera/niskiego poziomu (niżej określane jako „języki maszynowe”) pisze się powoli i uciążliwie. Zwykle musimy tworzyć kod dla wielu nawet najprostszych zadań. Nawet zdania typu A=B+C z języka wysokiego poziomu mogą zająć 10 czy 15 instrukcji języka maszynowego, a dodatkowo łatwo tu o pomyłkę w kwestiach takich jak przepełnienie rejestru. Dlatego potrzebujemy jeszcze dodatkowych instrukcji do testów i kontroli przepełnienia, o czym programista może łatwo zapomnieć.
Ludzie, którzy przewidywali języki wysokiego poziomu, mówili, że wierzą, iż komputery będzie można programować w „języku naturalnym”. Biorąc pod uwagę komputery z tamtych czasów, można zrozumieć, że byli też tacy, którzy uważali to za niemożliwe.
Szczęśliwie dla informatyki deweloperzy języków wysokiego poziomu się nie poddawali. Po opracowaniu prostszych języków, takich jak FLOW-MATIC, Grace Murray Hopper przewodził rozwojowi COBOL-a, a John Backus opracował FORTRAN-a. Inni kontynuowali ich prace.
Języki wysokiego poziomu nie tylko upraszczają kodowanie, ale też sprawiają, że programy stają się bardziej przenośne. Dlaczego?
Głównym powodem tego, że języki wysokiego poziomu są bardziej przenośne, jest to, że nie potrzebujemy już dokładnego odwzorowania tego, co jest zakodowane na to, co jest wykonywane na maszynie. Zamiast tego przetwarzane są zwykle słowa jak z języka naturalnego, dając pośredni stan, który nie jest w tak dużym stopniu zależny od architektury maszyny, a który jest przekształcany na konkretny język maszynowy/asemblera.
Przez lata ten pośredni stan pozwalał na dużą elastyczność, szczególnie w przypadku języków nieopierających się mocno na typach danych, co pozwoliło na przenoszenie programów na inne architektury.
Kompilatory pozwoliły też na standaryzację linkowania modułów podprogramów i funkcji, umożliwiając łatwiejsze tworzenie bibliotek dzielonych pomiędzy różnymi językami udostępnianymi przez różne firmy.
Niektóre optymalizacje mogą być wykonywane w tym pośrednim stanie, na przykład przenoszenie kodu poza pętlę, podczas gdy inne (jak optymalizacja kodu przez wizjer) wykonywane są na niższym poziomie. Tak, optymalizacja przez wizjer może być wykonana przez dobrego programistę języka maszynowego/asemblera, ale jest to uciążliwa i męcząca praca.
Regułą było kiedyś, że dobry programista języka asemblera „zawsze” napisze lepszy kod niż średni programista języka wysokiego poziomu, podczas gdy dobry programista języka wysokiego poziomu „zawsze” napisze lepszy kod niż średni programista języka asemblera.
Jednak dobry programista języka asemblera „zawsze” napisze lepszy kod niż dobry programista języka wysokiego poziomu, jeśli rozmiar programu jest ograniczony – tyle że zajmie mu to dużo więcej czasu. Zauważmy, że „zawsze” jest w cudzysłowie.
Pisałem w języku asemblera przez ponad cztery lata niemal tylko na maszyny mainframe IBM-a. Potem uczyłem przetwarzania danych przez trzy lata i korzystałem z języka asemblera na wielu kursach, aby móc wytłumaczyć, jak działają kompilatory, systemy operacyjne i silniki baz danych. Dzisiaj jestem przekonany, że dobry programista powinien rozumiem język maszynowy i ogólną architekturę komputerów, ale nie zalecałbym programowania w języku maszynowym/asemblera.
Zamiast tego polecam skorzystanie z tej wiedzy, aby móc zrozumieć, jak pisać w językach wysokiego poziomu w sposób, który ułatwi wykonanie i wykrywanie błędów wygenerowanych przez kompilator na danej maszynie.
Jestem pewien, że niemal każdy doświadczony programista znalazł błąd wygenerowany przez kompilator, mimo że kod źródłowy był prawidłowy. Taki błąd jest jeszcze bardziej podstępny, kiedy włączymy inny poziom optymalizacji już istniejącego i przetestowanego kodu – to może być wszystko, od niezainicjowanych zmiennych przeniesionych poza pętlę przez „pomocny” optymalizator, po nieprawidłowy licznik pętli.
Nie mówię, że większość kompilatorów jest słaba, ale każdy etap optymalizacji powinien być testowany – zawsze kiedy włączamy nowe optymalizacje (lub ich grupy) dla nowej architektury.
Po napisaniu początkowego kodu korzystam z profilera, aby sprawdzić, gdzie program spędza najwięcej czasu. Jeśli coś wydaje się nieracjonalne, proszę kompilator o wygenerowanie kodu pośredniego lub nawet (szczególnie w przypadku procesorów RISC) języka maszynowego/asemblera dla tego programu.
Współcześni profesorowie mówili mi, że taka analiza jest dzisiaj mniej użyteczna ze względu na użycie maszyn wirtualnych i kontenerów. Jeśli wirtualna maszyna lub kontener nie korzystają z emulatorów, większość wygenerowanego przez kompilator kodu działa bezpośrednio na sprzęcie, więc w przypadku długo działających aplikacji wydajne działanie oszczędza czas, elektryczność lub chłodzenie (lub wszystkie te trzy rzeczy). A może też pozwoli waszemu telefonowi wytrzymać cały dzień na jednym ładowaniu.
Jon „maddog” Hall jest autorem, wykładowcą, informatykiem i jednym z pionierów Wolnego Oprogramowania. Od 1994 roku, kiedy po raz pierwszy spotkał Linusa Torvaldsa i ułatwił przeniesienie jądra na systemy 64-bitowe, pozostaje orędownikiem Linuksa. Obecnie jest prezesem Linux International®.