Jaki jest pożytek z konwersji kodu źródłowego na kod bajtowy Java?


37

Jeśli potrzebujemy różnych maszyn JVM dla różnych architektur, nie mogę zrozumieć, jaka jest logika wprowadzenia tej koncepcji. W innych językach potrzebujemy różnych kompilatorów dla różnych maszyn, ale w Javie potrzebujemy różnych JVM, więc jaka jest logika wprowadzenia koncepcji JVM lub tego dodatkowego kroku?



12
@gnat: Właściwie to nie jest duplikat. Jest to „kod źródłowy a bajtowy”, tj. Tylko pierwsza transformacja. Pod względem językowym jest to JavaScript kontra Java; Twój link to C ++ w porównaniu z Javą.
MSalters

2
Czy wolisz napisać prosty interpreter kodu bajtowego dla tych 50 modeli urządzeń, do których dodajesz kodowanie cyfrowe w celu aktualizacji lub 50 kompilatorów dla 50 różnych urządzeń. Java została pierwotnie opracowana dla urządzeń i maszyn. To był jego mocny kolor. Pamiętaj o tym, czytając te odpowiedzi, ponieważ Java nie ma obecnie prawdziwej przewagi (z powodu nieefektywności procesu tłumaczenia). To tylko model, którego nadal używamy.
The Great Duck

1
Zdajesz się nie rozumieć, co maszyna wirtualna jest . To maszyna. Można go zaimplementować w sprzęcie z kompilatorami kodu natywnego (a tak było w przypadku JVM). Ważna jest tutaj część „wirtualna”: zasadniczo emulujesz tę architekturę na innej. Powiedzmy, że napisałem emulator 8088 do uruchomienia na x86. Nie zamierzasz przenosić starego kodu 8088 na x86, po prostu uruchomisz go na emulowanej platformie. JVM jest maszyną, na którą celujesz jak każda inna, z tą różnicą, że działa na innych platformach.
Jared Smith

7
@TheGreatDuck Tłumaczenie ustne? Obecnie większość maszyn JVM dokonuje kompilacji just-in-time do kodu maszynowego. Nie wspominając już o tym, że „interpretacja” jest obecnie dość szerokim pojęciem. Sam procesor po prostu „interpretuje” kod x86 na swój wewnętrzny mikrokod i jest wykorzystywany do poprawy wydajności. Najnowsze procesory Intel są również wyjątkowo odpowiednie dla tłumaczy w ogóle (choć oczywiście znajdziesz testy porównawcze, które pozwolą Ci udowodnić, co chcesz).
Luaan,

Odpowiedzi:


79

Logika jest taka, że ​​kod bajtowy JVM jest znacznie prostszy niż kod źródłowy Java.

Na wysoce abstrakcyjnym poziomie można uznać, że kompilatory składają się z trzech podstawowych części: parsowania, analizy semantycznej i generowania kodu.

Parsowanie polega na odczytaniu kodu i przekształceniu go w reprezentację drzewa w pamięci kompilatora. Analiza semantyczna to część, w której analizuje to drzewo, odkrywa, co to znaczy, i upraszcza wszystkie konstrukcje wysokiego poziomu do niższych. Generowanie kodu pobiera uproszczone drzewo i zapisuje je w postaci płaskiego wyniku.

W przypadku pliku kodu bajtowego faza analizy jest znacznie uproszczona, ponieważ jest zapisany w tym samym formacie strumienia bajtów, którego używa JIT, a nie w rekursywnym (ustrukturyzowanym na drzewie) języku źródłowym. Ponadto wiele ciężkich analiz semantycznych zostało już przeprowadzonych przez kompilator Java (lub inny język). Wystarczy więc odczytać kod, wykonać minimalną analizę i analizę semantyczną, a następnie wygenerować kod.

To sprawia, że ​​zadanie JIT musi być dużo prostsze, a zatem znacznie szybsze do wykonania, przy jednoczesnym zachowaniu wysokopoziomowych metadanych i informacji semantycznych, które umożliwiają teoretyczne pisanie kodu z jednego źródła na wiele platform.


7
Niektóre inne wczesne próby dystrybucji apletów, takie jak SafeTCL, faktycznie rozpowszechniały kod źródłowy. Zastosowanie przez Javę prostego i ściśle określonego kodu bajtowego sprawia, że weryfikacja programu jest znacznie łatwiejsza w obsłudze, i to był trudny problem do rozwiązania. Kody bajtowe, takie jak kod p, były już znane jako część rozwiązania problemu z przenośnością (a ANDF prawdopodobnie był wtedy w fazie rozwoju).
Toby Speight,

9
Dokładnie. Czasy uruchamiania Java są już pewnym problemem ze względu na kod bajtowy -> krok kodu maszynowego. Uruchom javac na swoim (nietrywialnym) projekcie, a następnie wyobraź sobie, że wykonujesz cały kod Java -> przy każdym uruchomieniu.
Paul Draper,

24
Ma jeszcze jedną ogromną zaletę: jeśli któregoś dnia wszyscy chcemy przejść do hipotetycznego nowego języka - nazwijmy go „Scala” - wystarczy napisać tylko jeden Scala -> kompilator kodu bajtowego, a nie dziesiątki Scala -> kod maszynowy kompilatory. Jako bonus otrzymujemy wszystkie optymalizacje specyficzne dla platformy JVM za darmo.
BlueRaja - Danny Pflughoeft

8
Niektóre rzeczy nadal nie są możliwe w kodzie bajtów JVM, takie jak optymalizacja wywołania ogonowego. Pamiętam, że to bardzo kompromituje funkcjonalny język, który kompiluje się do JVM.
JDługosz

8
@ JDługosz racja: JVM niestety nakłada pewne ograniczenia / idiomy projektowe, które choć mogą być całkowicie naturalne, jeśli pochodzisz z języka imperatywnego, mogą stać się dość sztuczną przeszkodą, jeśli chcesz napisać kompilator dla języka, który działa zasadniczo różne. Uważam zatem, że LLVM jest lepszym celem, jeśli chodzi o ponowne wykorzystywanie pracy w języku przyszłym - ma również ograniczenia, ale mniej więcej odpowiadają ograniczeniom, jakie mają obecne (i prawdopodobnie w przyszłości) procesory.
lewo około

27

Różne reprezentacje pośrednie są coraz bardziej powszechne w projektowaniu kompilatora / środowiska wykonawczego, z kilku powodów.

W przypadku Javy pierwszym powodem prawdopodobnie była przenośność : Java była początkowo szeroko rozpowszechniona jako „Napisz raz, uruchom gdziekolwiek”. Możesz to osiągnąć, dystrybuując kod źródłowy i używając różnych kompilatorów do kierowania na różne platformy, ma to jednak kilka wad:

  • kompilatory są złożonymi narzędziami, które muszą zrozumieć wszystkie wygodne składnie języka; kod bajtowy może być prostszym językiem, ponieważ jest bliżej kodu wykonywalnego maszynowo niż źródła czytelnego dla człowieka; to znaczy:
    • kompilacja może być powolna w porównaniu do wykonania kodu bajtowego
    • kompilatory ukierunkowane na różne platformy mogą w końcu powodować różne zachowania lub nie nadążać za zmianami językowymi
    • tworzenie kompilatora dla nowej platformy jest znacznie trudniejsze niż tworzenie maszyny wirtualnej (lub kompilatora bajt-kod-natywny) dla tej platformy
  • rozpowszechnianie kodu źródłowego nie zawsze jest pożądane; bytecode zapewnia pewną ochronę przed inżynierią wsteczną (choć nadal dość łatwo dekompilować, chyba że celowo zaciemniono)

Inne zalety reprezentacji pośredniej obejmują:

  • optymalizacja , w której wzorce można wykryć w kodzie bajtowym i skompilować do szybszych odpowiedników, a nawet zoptymalizować pod kątem szczególnych przypadków w trakcie działania programu (przy użyciu kompilatora „JIT” lub „Just In Time”)
  • interoperacyjność między wieloma językami w tej samej maszynie wirtualnej; stało się to popularne w JVM (np. Scala) i jest wyraźnym celem frameworka .net

1
Java była również zorientowana na systemy wbudowane. W takich systemach sprzęt miał kilka ograniczeń pamięci i procesora.
Laiv

Czy kompilatory można opracować w taki sposób, aby najpierw kompilowały kod źródłowy Java w kod bajtowy, a następnie kompilowały kod bajtowy w kod maszynowy? Czy wyeliminowałby większość wspomnianych wad?
Sher10ck,

@ Sher10ck Tak, AFAIK może napisać kompilator, który statycznie konwertuje kod bajtowy JVM na instrukcje maszynowe dla konkretnej architektury. Ale miałoby to sens tylko wtedy, gdyby poprawiło wydajność na tyle, aby przeważyć dodatkowy wysiłek dystrybutora lub dodatkowy czas pierwszego użycia dla użytkownika. Korzyścią może być wbudowany system o niskiej mocy; nowoczesny komputer pobierający i uruchamiający wiele różnych programów byłby prawdopodobnie lepiej z dobrze dostrojonym JIT. Myślę, że Android idzie gdzieś w tym kierunku, ale nie znam szczegółów.
IMSoP,

8

Wygląda na to, że zastanawiasz się, dlaczego nie rozpowszechniamy tylko kodu źródłowego. Pozwól, że odwrócę to pytanie: dlaczego po prostu nie rozpowszechniamy kodu maszynowego?

Najwyraźniej odpowiedź brzmi: Java z założenia nie zakłada, że ​​wie, na której maszynie będzie działał twój kod; może to być komputer stacjonarny, superkomputer, telefon lub cokolwiek pomiędzy i poza nim. Java pozostawia miejsce dla lokalnego kompilatora JVM. Oprócz zwiększenia przenośności kodu ma to tę zaletę, że pozwala kompilatorowi wykonywać takie czynności, jak optymalizacje specyficzne dla komputera, jeśli takie istnieją, lub nadal produkować przynajmniej działający kod, jeśli nie istnieje. Rzeczy takie jak instrukcje SSE lub przyspieszenie sprzętowe mogą być używane tylko na komputerach, które je obsługują.

W tym świetle uzasadnienie użycia kodu bajtowego zamiast surowego kodu źródłowego jest jaśniejsze. Zbliżenie się do surowego języka maszynowego, jak to możliwe, pozwala nam uświadomić sobie lub częściowo zrealizować niektóre zalety kodu maszynowego, takie jak:

  • Krótszy czas uruchamiania, ponieważ niektóre kompilacje i analizy są już wykonane.
  • Bezpieczeństwo, ponieważ format bajt-kod ma wbudowany mechanizm do podpisywania plików dystrybucyjnych (źródło może to zrobić zgodnie z konwencją, ale mechanizm do osiągnięcia tego nie jest wbudowany tak jak w przypadku kodu bajtowego).

Pamiętaj, że nie wspominam o szybszym wykonaniu. Zarówno kod źródłowy, jak i bajtowy są lub mogą (teoretycznie) zostać w pełni skompilowane do tego samego kodu maszynowego w celu faktycznego wykonania.

Ponadto kod bajtowy pozwala na pewne ulepszenia w stosunku do kodu maszynowego. Oczywiście istnieją niezależności od platformy i optymalizacje specyficzne dla sprzętu, o których wspomniałem wcześniej, ale są też takie rzeczy, jak obsługa kompilatora JVM w celu tworzenia nowych ścieżek wykonywania ze starego kodu. Może to być załatanie problemów związanych z bezpieczeństwem lub wykrycie nowych optymalizacji lub skorzystanie z nowych instrukcji sprzętowych. W praktyce rzadko spotyka się w ten sposób duże zmiany, ponieważ może to ujawniać błędy, ale jest to możliwe i dzieje się tak przez cały czas w niewielkim stopniu.


8

Wydaje się, że istnieją co najmniej dwa różne możliwe pytania. Jeden tak naprawdę dotyczy generalnie kompilatorów, a Java jest po prostu tylko przykładem tego gatunku. Drugi jest bardziej specyficzny dla Javy, którego używa określone kody bajtów.

Kompilatory w ogóle

Rozważmy najpierw ogólne pytanie: dlaczego kompilator miałby używać pośredniej reprezentacji w procesie kompilowania kodu źródłowego, aby działał na określonym procesorze?

Redukcja złożoności

Jedna odpowiedź na to pytanie jest dość prosta: przekształca problem O (N * M) w problem O (N + M).

Jeśli otrzymamy N języków źródłowych i M celów, a każdy kompilator jest całkowicie niezależny, potrzebujemy kompilatorów N * M do przetłumaczenia wszystkich tych języków źródłowych na wszystkie te cele (gdzie „cel” jest czymś w rodzaju kombinacji procesor i system operacyjny).

Jeśli jednak wszystkie te kompilatory zgadzają się na wspólną reprezentację pośrednią, wówczas możemy mieć N frontonów kompilatora, które tłumaczą języki źródłowe na reprezentację pośrednią, i M back endy kompilatora, które tłumaczą reprezentację pośrednią na coś odpowiedniego dla konkretnego celu.

Segmentacja problemu

Co więcej, dzieli problem na dwie mniej lub bardziej ekskluzywne domeny. Ludzie, którzy znają / troszczą się o projektowanie języka, parsowanie i tego typu rzeczy, mogą skoncentrować się na interfejsach kompilatora, podczas gdy ludzie, którzy wiedzą o zestawach instrukcji, projektowaniu procesorów i podobnych rzeczach mogą skoncentrować się na zapleczu.

Na przykład, biorąc pod uwagę coś takiego jak LLVM, mamy wiele interfejsów dla różnych języków. Posiadamy również zaplecze dla wielu różnych procesorów. Język facet może napisać nowy interfejs dla swojego języka i szybko wspierać wiele celów. Facet zajmujący się procesorem może napisać nowy back-end dla swojego obiektu docelowego bez zajmowania się projektowaniem języka, parsowaniem itp.

Rozdzielanie kompilatorów na front i back end z pośrednią reprezentacją do komunikacji między nimi nie jest oryginalne w Javie. Od dawna jest to dość powszechna praktyka (zresztą na długo przed pojawieniem się Java).

Modele dystrybucji

W zakresie, w jakim Java dodała coś nowego w tym względzie, było to w modelu dystrybucji. W szczególności, mimo że kompilatory były przez długi czas wewnętrznie dzielone na części frontonu i back-endu, zwykle były one dystrybuowane jako pojedynczy produkt. Na przykład, jeśli kupiłeś kompilator Microsoft C, wewnętrznie miał on „C1” i „C2”, które były odpowiednio frontonem i back-endem - ale kupiłeś tylko „Microsoft C”, który obejmował oba sztuk (z „sterownikiem kompilatora”, który koordynował operacje między nimi). Mimo że kompilator został zbudowany w dwóch częściach, dla zwykłego programisty korzystającego z kompilatora była to tylko jedna rzecz, która tłumaczyła się z kodu źródłowego na kod obiektowy, bez niczego pomiędzy nimi.

Zamiast tego Java dystrybuowała front-end w Java Development Kit, a back-end w Java Virtual Machine. Każdy użytkownik Java miał zaplecze kompilatora, aby celować w dowolny system, z którego korzystał. Programiści Java dystrybuowali kod w formacie pośrednim, więc kiedy użytkownik go załadował, JVM zrobił wszystko, co było konieczne, aby wykonać go na konkretnej maszynie.

Precedensy

Zauważ, że ten model dystrybucji również nie był zupełnie nowy. Na przykład system P UCSD działał podobnie: interfejsy kompilatora produkowały kod P, a każda kopia systemu P zawierała maszynę wirtualną, która zrobiła wszystko, co było konieczne do wykonania kodu P na tym konkretnym celu 1 .

Kod bajtowy Java

Kod bajtu Java jest dość podobny do kodu P. To w zasadzie instrukcje dla dość prostej maszyny. Ta maszyna ma być abstrakcją istniejących maszyn, więc dość szybko można ją szybko przełożyć na niemal każdy konkretny cel. Łatwość tłumaczenia była ważna na początku, ponieważ pierwotnie zamierzano interpretować kody bajtowe, podobnie jak zrobił to P-System (i tak, dokładnie tak działały wczesne wdrożenia).

Silne strony

Kod bajtowy Java jest łatwy do wytworzenia dla kompilatora. Jeśli (na przykład) masz dość typowe drzewo reprezentujące wyrażenie, zazwyczaj dość łatwo jest przejść przez drzewo i wygenerować kod dość bezpośrednio z tego, co znajdziesz w każdym węźle.

Kody bajtów Java są dość kompaktowe - w większości przypadków znacznie bardziej kompaktowe niż kod źródłowy lub kod maszynowy dla większości typowych procesorów (a zwłaszcza dla większości procesorów RISC, takich jak SPARC, które Sun sprzedał podczas projektowania Java). Było to wtedy szczególnie ważne, ponieważ jednym z głównych celów Java była obsługa apletów - kodu osadzonego na stronach internetowych, które zostaną pobrane przed wykonaniem - w czasie, gdy większość ludzi uzyskiwała dostęp do nas za pośrednictwem modemów przez linie telefoniczne około 28,8 kilobitów na sekundę (choć oczywiście sporo osób używa starszych, wolniejszych modemów).

Słabości

Główną słabością kodów bajtów Java jest to, że nie są one szczególnie ekspresyjne. Chociaż potrafią dość dobrze wyrażać koncepcje obecne w Javie, nie działają prawie tak dobrze w wyrażaniu koncepcji, które nie są częścią Java. Podobnie, chociaż na większości komputerów łatwo jest wykonać kody bajtowe, jest to o wiele trudniejsze w sposób, który w pełni wykorzystuje jakąkolwiek konkretną maszynę.

Na przykład, dość rutynową rzeczą jest, że jeśli naprawdę chcesz zoptymalizować kody bajtów Java, w zasadzie wykonujesz kilka inżynierii wstecznej, aby przetłumaczyć je wstecz z reprezentacji podobnej do kodu maszynowego i przekształcić je z powrotem w instrukcje SSA (lub coś podobnego) 2 . Następnie manipulujesz instrukcjami SSA w celu przeprowadzenia optymalizacji, a następnie przekładasz stamtąd na coś, co jest ukierunkowane na architekturę, na której naprawdę Ci zależy. Jednak nawet w przypadku tego dość złożonego procesu niektóre pojęcia obce dla Javy są wystarczająco trudne do wyrażenia, dlatego trudno jest przetłumaczyć z niektórych języków źródłowych na kod maszynowy, który działa (nawet blisko) optymalnie na większości typowych maszyn.

Podsumowanie

Jeśli zastanawiasz się, dlaczego ogólnie używać reprezentacji pośrednich, dwa główne czynniki to:

  1. Zmniejsz problem O (N * M) do problemu O (N + M) i
  2. Podziel problem na łatwiejsze do opanowania części.

Jeśli pytasz o specyfikę kodów bajtów Java i dlaczego wybrali tę konkretną reprezentację zamiast jakiejś innej, to powiedziałbym, że odpowiedź w dużej mierze wraca do ich pierwotnych zamiarów i ograniczeń sieci w tamtym czasie , co prowadzi do następujących priorytetów:

  1. Kompaktowa reprezentacja.
  2. Szybki i łatwy do odkodowania i wykonania.
  3. Szybkie i łatwe do wdrożenia na większości popularnych maszyn.

Zdolność do reprezentowania wielu języków lub wykonywania optymalnego dla wielu różnych celów była znacznie niższym priorytetem (jeśli w ogóle były one uważane za priorytety).


  1. Dlaczego więc najczęściej zapomina się o systemie P? Głównie sytuacja cenowa. System P sprzedawany całkiem przyzwoicie na Apple II, Commodore SuperPets itp. Gdy pojawił się komputer IBM, system P był obsługiwanym systemem operacyjnym, ale MS-DOS kosztował mniej (z punktu widzenia większości ludzi został wrzucony za darmo) i szybko udostępniono więcej programów, ponieważ pisali o tym Microsoft i IBM (między innymi).
  2. Na przykład tak działa Soot .

Całkiem blisko apletów internetowych: pierwotnym zamiarem było rozpowszechnianie kodu do urządzeń (dekoderów ...), w taki sam sposób, jak RPC dystrybuuje wywołania funkcji, a CORBA dystrybuuje obiekty.
ninjalj

2
To świetna odpowiedź i dobry wgląd w to, jak różne reprezentacje pośrednie powodują różne kompromisy. :)
IMSoP

@ninjalj: To był naprawdę Oak. Do czasu, gdy przekształciła się w Javę, uważam, że pomysły na dekodery (i podobne) zostały odłożone na półkę (chociaż jako pierwszy przyznam, że istnieje uzasadniony argument, że Oak i Java są tym samym).
Jerry Coffin

@TobySpeight: Tak, wyrażenie jest prawdopodobnie lepiej dopasowane. Dzięki.
Jerry Coffin

0

Oprócz zalet wskazanych przez inne osoby, kod bajtowy jest znacznie mniejszy, więc łatwiej jest dystrybuować i aktualizować oraz zajmuje mniej miejsca w środowisku docelowym. Jest to szczególnie ważne w środowiskach o ograniczonej przestrzeni.

Ułatwia także ochronę kodu źródłowego chronionego prawem autorskim.


2
Kod bajtowy Java (i .NET) jest tak łatwy do przekształcenia w racjonalnie czytelne źródło, że istnieją produkty do zmieniania nazw i czasem innych informacji, które utrudniają to - coś często robi się z JavaScriptem, aby go zmniejszyć, ponieważ jesteśmy teraz może ustawienie kodu bajtowego dla przeglądarek internetowych.
LnxPrgr3

0

Chodzi o to, że kompilacja kodu bajtowego do kodu maszynowego jest szybsza niż interpretacja oryginalnego kodu na kod maszynowy w samą porę. Potrzebujemy jednak interpretacji, aby nasza aplikacja była wieloplatformowa, ponieważ chcemy używać naszego oryginalnego kodu na każdej platformie bez zmian i bez przygotowań (kompilacji). Najpierw więc javac kompiluje nasz kod źródłowy do bajtowego, a następnie możemy uruchomić ten kod bajtowy w dowolnym miejscu i zostanie on zinterpretowany przez maszynę wirtualną Java do szybszego kodowania maszynowego. Odpowiedź: oszczędza czas.


0

Pierwotnie JVM był czystym tłumaczem . I dostajesz najlepszego tłumacza, jeśli język, który tłumaczysz, jest tak prosty, jak to możliwe. Taki był cel kodu bajtowego: Zapewnienie wydajnie interpretowalnego wejścia do środowiska wykonawczego. Ta pojedyncza decyzja zbliżyła Javę do języka skompilowanego niż do języka interpretowanego, co ocenia się na podstawie jego działania.

Dopiero później, kiedy okazało się, że wydajność JVM z interpretacji wciąż jest do dupy, ludzie zainwestowali wysiłek w stworzenie dobrze działających kompilatorów just-in-time. To nieco zamknęło lukę w stosunku do szybszych języków, takich jak C i C ++. (Pozostają jednak pewne nieodłączne problemy z prędkością Java, więc prawdopodobnie nigdy nie otrzymasz środowiska Java, które działa tak dobrze, jak dobrze napisany kod C.)

Oczywiście z technikami just-in-time zbierające pod ręką, my mogliśmy wrócić do rzeczywistości dystrybucją kodu źródłowego, i just-in-time kompilowanie go do kodu maszynowego. Spowodowałoby to jednak znaczne obniżenie wydajności uruchamiania do momentu skompilowania wszystkich odpowiednich części kodu. Kod bajtowy nadal jest tutaj znaczącą pomocą, ponieważ jest o wiele prostszy w analizie niż równoważny kod Java.


Czy downvoter mógłby wyjaśnić dlaczego ?
cmaster

-5

Tekstowy kod źródłowy to struktura, która ma być łatwa do odczytania i modyfikacji przez człowieka.

Kod bajtowy to struktura, która ma być łatwa do odczytania i wykonania przez maszynę.

Ponieważ wszystko, co JVM robi z kodem, jest odczytywane i wykonywane, kod bajtów lepiej nadaje się do wykorzystania przez JVM.

Zauważam, że nie było jeszcze żadnych przykładów. Głupie pseudo przykłady:

//Source code
i += 1 + 5 * 2 + x;

// Byte code
i += 11, i += x
____

//Source code
i = sin(1);

// Byte code
i = 0.8414709848
_____

//Source code
i = sin(x)^2+cos(x)^2;

// Byte code (actually that one isn't true)
i = 1

Oczywiście bajtowy kod to nie tylko optymalizacje. Duża część polega na możliwości wykonywania kodu bez konieczności dbania o skomplikowane reguły, takie jak sprawdzanie, czy klasa zawiera element zwany „foo” gdzieś w dalszej części pliku, gdy metoda odnosi się do „foo”.


2
Te „przykłady” kodu bajtowego są czytelne dla człowieka. To wcale nie jest bajtowy kod. Jest to mylące i również nie odnosi się do zadanego pytania.
Wildcard,

@Wildcard Być może przegapiłeś to forum czytane przez ludzi. Dlatego umieszczam treść w formie czytelnej dla człowieka. Biorąc pod uwagę, że forum dotyczy inżynierii oprogramowania, proszenie czytelników o zrozumienie pojęcia prostej abstrakcji nie wymaga wiele.
Peter,

Formą czytelną dla człowieka jest kod źródłowy, a nie kod bajtowy. Ilustrujesz kod źródłowy wyrażeniami wstępnie obliczonymi, a NIE bajtowymi. I nie umknęło mi, że jest to forum czytelne dla człowieka: to ty skrytykowałeś inne osoby odpowiadające za to, że nie podawały żadnych przykładów kodu bajtowego, nie ja. Więc mówisz: „Zauważyłem, że nie było jeszcze żadnych przykładów”, a następnie kontynuujesz dawanie nie- przykładów, które w ogóle nie ilustrują kodu bajtowego. I to wciąż nie dotyczy tego pytania. Ponownie zadaj pytanie.
Wildcard,
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.