Czy w przypadku korzystania z tego samego JDK (tj. Tego samego javac
pliku wykonywalnego) wygenerowane pliki klas są zawsze identyczne? Czy może istnieć różnica w zależności od systemu operacyjnego lub sprzętu ? Czy poza wersją JDK mogą istnieć inne czynniki powodujące różnice? Czy są jakieś opcje kompilatora, aby uniknąć różnic? Czy różnica jest możliwa tylko w teorii, czy też Oracle javac
faktycznie tworzy różne pliki klas dla tych samych danych wejściowych i opcji kompilatora?
Update 1 Interesuje mnie generacja , czyli wyjście kompilatora, a nie to, czy plik klasy można uruchomić na różnych platformach.
Aktualizacja 2 Przez „ten sam JDK” mam na myśli również ten sam javac
plik wykonywalny.
Aktualizacja 3 Rozróżnienie między teoretyczną różnicą a praktyczną różnicą w kompilatorach Oracle.
[EDYTUJ, dodając sparafrazowane pytanie]
„W jakich okolicznościach ten sam plik wykonywalny javac, uruchomiony na innej platformie, wygeneruje inny kod bajtowy?”
źródło
Odpowiedzi:
Ujmijmy to w ten sposób:
Mogę z łatwością stworzyć całkowicie zgodny kompilator Java, który nigdy nie tworzy
.class
dwukrotnie tego samego pliku, mając ten sam.java
plik.Mógłbym to zrobić, poprawiając wszystkie rodzaje konstrukcji kodu bajtowego lub po prostu dodając zbędne atrybuty do mojej metody (co jest dozwolone).
Biorąc pod uwagę, że specyfikacja nie wymaga, aby kompilator tworzył pliki klas identyczne bajt po bajcie, unikałbym uzależnienia od takiego wyniku.
Jednak kilka razy, które sprawdzałem, kompilowanie tego samego pliku źródłowego za pomocą tego samego kompilatora z tymi samymi przełącznikami (i tymi samymi bibliotekami!) Dało w wyniku te same
.class
pliki.Aktualizacja: Niedawno natknąłem się na ten interesujący wpis na blogu dotyczący implementacji
switch
onString
w Javie 7 . W tym poście na blogu jest kilka istotnych części, które zacytuję tutaj (moje wyróżnienie):To dość wyraźnie ilustruje problem: kompilator nie musi działać w sposób deterministyczny, o ile pasuje do specyfikacji. Twórcy kompilatorów zdają sobie jednak sprawę, że generalnie dobrym pomysłem jest wypróbowanie (pod warunkiem, że prawdopodobnie nie jest to zbyt drogie).
źródło
Kompilatory nie mają obowiązku tworzenia tego samego kodu bajtowego na każdej platformie.
javac
Aby uzyskać konkretną odpowiedź, należy skonsultować się z narzędziami różnych dostawców .Pokażę praktyczny przykład tego z porządkiem plików.
Powiedzmy, że mamy 2 pliki jar:
my1.jar
iMy2.jar
. Są umieszczane wlib
katalogu obok siebie. Kompilator odczytuje je w porządku alfabetycznym (od tego jestlib
), ale kolejność jestmy1.jar
,My2.jar
gdy system plików jest sprawa niewrażliwe iMy2.jar
,my1.jar
jeśli to jest wielkość liter.my1.jar
Ma klasęA.class
z metodąpublic class A { public static void a(String s) {} }
My2.jar
Ma takie samoA.class
, ale z inną metodą podpis (przyjmujeObject
)public class A { public static void a(Object o) {} }
Oczywiste jest, że jeśli masz telefon
String s = "x"; A.a(s);
skompiluje wywołanie metody z różnym podpisem w różnych przypadkach. Tak więc, w zależności od wrażliwości systemu plików na wielkość liter, w rezultacie otrzymasz inną klasę.
źródło
javac
nie to samo, ponieważ masz różne pliki binarne na każdej platformie (np. Win7, Linux, Solaris, Mac). Dla dostawcy nie ma sensu mieć różnych implementacji, ale każdy problem związany z platformą może wpłynąć na wynik (np. Zamawianie plików w katalogu (pomyśl o swoimlib
katalogu), endianness itp.).javac
jest zaimplementowana w Javie (ijavac
jest to tylko prosty natywny program uruchamiający), więc większość różnic między platformami nie powinna mieć wpływu.Krótka odpowiedź - NIE
Długa odpowiedź
Nie
bytecode
muszą być takie same dla różnych platform. To JRE (Java Runtime Environment) wie, jak dokładnie wykonać kod bajtowy.Jeśli przejrzysz specyfikację maszyny wirtualnej Java , dowiesz się, że nie musi to być prawdą, że kod bajtowy jest taki sam dla różnych platform.
Przechodząc przez format pliku klasy , pokazuje strukturę pliku klasy jako
ClassFile { u4 magic; u2 minor_version; u2 major_version; u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; u2 fields_count; field_info fields[fields_count]; u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count]; }
Sprawdzanie wersji pomocniczej i głównej
Przeczytaj więcej w przypisach
Zatem zbadanie tego wszystkiego pokazuje, że pliki klas generowane na różnych platformach nie muszą być identyczne.
źródło
Po pierwsze, w specyfikacji nie ma absolutnie takiej gwarancji. Zgodny kompilator mógłby oznaczyć czas kompilacji w wygenerowanym pliku klasy jako dodatkowy (niestandardowy) atrybut, a plik klasy nadal byłby poprawny. Stworzyłoby to jednak inny plik na poziomie bajtów dla każdej pojedynczej kompilacji, i to w trywialny sposób.
Po drugie, nawet bez takich paskudnych sztuczek, nie ma powodu, aby oczekiwać, że kompilator zrobi dokładnie to samo dwa razy z rzędu, chyba że zarówno jego konfiguracja, jak i dane wejściowe są identyczne w obu przypadkach. Spec robi opisać nazwę pliku źródłowego jako jeden ze standardowych atrybutów i dodawanie pustych wierszy w pliku źródłowym mogłaby zmienić tabelę numer linii.
Po trzecie, nigdy nie spotkałem się z żadną różnicą w kompilacji ze względu na platformę hosta (poza tą, która wynikała z różnic w ścieżce klas). Kod, który byłby różny w zależności od platformy (tj. Natywnych bibliotek kodu) nie jest częścią pliku klasy, a faktyczne generowanie kodu natywnego z kodu bajtowego ma miejsce po załadowaniu klasy.
Po czwarte (i najważniejsze), to cuchnie nieprzyjemnym zapachem procesu (jak zapach kodu, ale z powodu tego, jak zachowujesz się na kodzie), aby chcieć to wiedzieć. Wersja źródła, jeśli to możliwe, a nie kompilacji, a jeśli musisz wersjonować kompilację, wersję na poziomie całego komponentu, a nie na poszczególnych plikach klas. W pierwszej kolejności użyj serwera CI (takiego jak Jenkins), aby zarządzać procesem przekształcania źródła w kod, który można uruchomić.
źródło
Uważam, że jeśli użyjesz tego samego JDK, wygenerowany kod bajtowy zawsze będzie taki sam, bez związku z używanym oprogramowaniem i systemem operacyjnym. Produkcja kodu bajtowego jest wykonywana przez kompilator java, który używa deterministycznego algorytmu do „transformacji” kodu źródłowego na kod bajtowy. Tak więc wynik zawsze będzie taki sam. W tych warunkach tylko aktualizacja kodu źródłowego wpłynie na dane wyjściowe.
źródło
Ogólnie rzecz biorąc, muszę powiedzieć, że nie ma gwarancji, że to samo źródło wyprodukuje ten sam kod bajtowy, gdy zostanie skompilowane przez ten sam kompilator, ale na innej platformie.
Przyjrzałbym się scenariuszom obejmującym różne języki (strony kodowe), na przykład Windows z obsługą języka japońskiego. Pomyśl o znakach wielobajtowych; chyba że kompilator zawsze zakłada, że musi obsługiwać wszystkie języki, które może zoptymalizować pod kątem 8-bitowego ASCII.
W specyfikacji języka Java znajduje się sekcja dotycząca zgodności binarnej .
źródło
Java allows you write/compile code on one platform and run on different platform.
AFAIK ; będzie to możliwe tylko wtedy, gdy plik klasy wygenerowany na innej platformie będzie taki sam lub taki sam, tj. identyczny.Edytować
Co mam na myśli przez technicznie to samo komentarz to. Nie muszą być dokładnie takie same, jeśli porównujesz bajt po bajcie.
Tak więc zgodnie ze specyfikacją plik .class klasy na różnych platformach nie musi być dopasowywany bajt po bajcie.
źródło
Na pytanie:
Przykład Cross-Compilation pokazuje, jak możemy użyć opcji Javac: -target version
Ta flaga generuje pliki klas, które są zgodne z wersją Java, którą określamy podczas wywoływania tego polecenia. W związku z tym pliki klas będą się różnić w zależności od atrybutów dostarczonych podczas obliczania przy użyciu tej opcji.
źródło
Najprawdopodobniej odpowiedź brzmi „tak”, ale aby uzyskać precyzyjną odpowiedź, podczas kompilacji trzeba szukać kluczy lub generować guid.
Nie pamiętam sytuacji, w której to się dzieje. Na przykład, aby mieć identyfikator do celów serializacji, jest on zakodowany na stałe, tj. Generowany przez programistę lub IDE.
PS Również JNI może mieć znaczenie.
PPS znalazłem, że
javac
jest napisany w Javie. Oznacza to, że jest identyczny na różnych platformach. Dlatego nie wygenerowałby innego kodu bez powodu. Tak więc może to zrobić tylko w przypadku połączeń natywnych.źródło
Są dwa pytania.
To jest pytanie teoretyczne, a odpowiedź brzmi: tak, może być. Jak powiedzieli inni, specyfikacja nie wymaga od kompilatora tworzenia plików klas identycznych bajt po bajcie.
Nawet jeśli każdy obecnie istniejący kompilator wyprodukował ten sam kod bajtowy we wszystkich okolicznościach (inny sprzęt itp.), Jutro odpowiedź może być inna. Jeśli nigdy nie planujesz aktualizacji javac lub systemu operacyjnego, możesz przetestować zachowanie tej wersji w określonych okolicznościach, ale wyniki mogą być inne, jeśli przejdziesz na przykład z Java 7 Update 11 na Java 7 Update 15.
To niepoznawalne.
Nie wiem, czy zarządzanie konfiguracją jest powodem zadawania tego pytania, ale jest to zrozumiały powód, aby się tym przejmować. Porównywanie kodów bajtów jest uzasadnioną kontrolą IT, ale tylko w celu ustalenia, czy pliki klas uległy zmianie, a nie w celu ustalenia, czy zmieniły się pliki źródłowe.
źródło
Ujmę to inaczej.
Po pierwsze, myślę, że nie chodzi o bycie deterministycznym:
Oczywiście jest to deterministyczne: losowość jest trudna do osiągnięcia w informatyce i nie ma powodu, aby kompilator wprowadzał ją tutaj z jakiegokolwiek powodu.
Po drugie, jeśli przeformułujesz go przez „jak podobne są pliki z kodem bajtowym dla tego samego pliku kodu źródłowego?”, To nie , nie możesz polegać na tym, że będą podobne .
Dobrym sposobem, aby się tego upewnić, jest pozostawienie .class (lub .pyc w moim przypadku) na etapie git. Zrozumiesz, że na różnych komputerach w Twoim zespole git zauważa zmiany między plikami .pyc, gdy żadne zmiany nie zostały wprowadzone do pliku .py (i mimo to .pyc ponownie skompilowano).
Tak przynajmniej zauważyłem. Więc umieść * .pyc i * .class w swoim .gitignore!
źródło