Poniższy kod tworzy wynik „Hello World!” (nie, naprawdę, spróbuj).
public static void main(String... args) {
// The comment below is not a typo.
// \u000d System.out.println("Hello World!");
}
Powodem tego jest to, że kompilator Java analizuje znak Unicode \u000d
jako nową linię i zostaje przekształcony w:
public static void main(String... args) {
// The comment below is not a typo.
//
System.out.println("Hello World!");
}
W ten sposób powstaje komentarz „wykonywany”.
Skoro można tego użyć do „ukrywania” złośliwego kodu lub czegokolwiek innego złego programistę, dlaczego jest to dozwolone w komentarzach ?
Dlaczego zezwala na to specyfikacja Java?
Odpowiedzi:
Dekodowanie Unicode odbywa się przed jakimkolwiek innym tłumaczeniem leksykalnym. Kluczową zaletą tego jest to, że sprawia, że przechodzenie między ASCII a dowolnym innym kodowaniem jest banalne. Nie musisz nawet wymyślać, gdzie zaczynają się i kończą komentarze!
Jak stwierdzono w sekcji 3.3 JLS, pozwala to dowolnemu narzędziu opartemu na ASCII przetwarzać pliki źródłowe:
Daje to fundamentalną gwarancję niezależności platformy (niezależności obsługiwanych zestawów znaków), która zawsze była kluczowym celem platformy Java.
Możliwość zapisania dowolnego znaku Unicode w dowolnym miejscu pliku jest ciekawą funkcją, szczególnie ważną w komentarzach, gdy dokumentuje się kod w językach innych niż łacińskie. Fakt, że może on wpływać na semantykę w tak subtelny sposób, jest po prostu (niefortunnym) efektem ubocznym.
Istnieje wiele gotch na ten temat, a Java Puzzlers Joshua Blocha i Neala Gaftera zawierały następujący wariant:
(Ten program okazuje się być zwykłym programem „Hello World”).
W rozwiązaniu zagadki zwracają uwagę na następujące kwestie:
Źródło: Java: Wykonywanie kodu w komentarzach ?!
źródło
\u000d
a część po nim powinna zawierać podświetlenia kodu.// C:\user\...
co prowadzi do błędu kompilacji, ponieważ\user
nie jest prawidłową sekwencją specjalną Unicode.\u000d
jest częściowo podświetlony. Po naciśnięciu Ctrl + Shift + F znak zostaje zastąpiony nową linią, a reszta linii jest zawijana\u002A/
powinien zakończyć komentarz.Ponieważ nie rozwiązano jeszcze tego problemu, oto wyjaśnienie, dlaczego tłumaczenie znaków unikodowych odbywa się przed jakimkolwiek innym przetwarzaniem kodu źródłowego:
Ideą było to, że umożliwia bezstratne tłumaczenia kodu źródłowego Java między różnymi kodowaniami znaków. Obecnie istnieje szerokie wsparcie dla Unicode i nie wygląda to na problem, ale wtedy deweloperowi z zachodniego kraju nie było łatwo otrzymać kod źródłowy od jego azjatyckiego kolegi zawierającego azjatyckie znaki, wprowadzić pewne zmiany ( w tym jego kompilacja i testowanie) i odesłanie wyniku, wszystko bez szkody.
Tak więc kod źródłowy Java może być napisany w dowolnym kodowaniu i pozwala na szeroki zakres znaków w obrębie identyfikatorów, znaków i
String
literałów oraz komentarzy. Następnie, aby przenieść go bezstratnie, wszystkie znaki nieobsługiwane przez kodowanie docelowe są zastępowane znakami ucieczki Unicode.Jest to proces odwracalny, a interesującym punktem jest to, że tłumaczenie może być wykonane przez narzędzie, które nie musi nic wiedzieć o składni kodu źródłowego Java, ponieważ reguła tłumaczenia nie jest od niego zależna. Działa to, ponieważ tłumaczenie ich rzeczywistych znaków Unicode wewnątrz kompilatora odbywa się również niezależnie od składni kodu źródłowego Java. Oznacza to, że można wykonać dowolną liczbę kroków tłumaczenia w obu kierunkach bez zmiany znaczenia kodu źródłowego.
To jest powód kolejnej dziwnej funkcji, o której nawet nie wspomniano:
\uuuuuuxxxx
składnia:Gdy narzędzie do tłumaczenia ucieka przed znakami i napotyka sekwencję, która jest już sekwencją ucieczkową, powinna wstawić dodatkowy
u
do sekwencji, konwertując\ucafe
na\uucafe
. Znaczenie nie zmienia się, ale podczas konwersji w innym kierunku narzędzie powinno po prostu usunąć jedenu
i zastąpić tylko sekwencje zawierające pojedynczyu
znakami Unicode. W ten sposób, nawet znaki ucieczki Unicode są zachowywane w oryginalnej formie podczas konwersji tam i z powrotem. Chyba nikt nigdy nie używał tej funkcji…źródło
native2ascii
wydaje się , że nie używa\uu...xxxx
składni,native2ascii
miał pomóc w przygotowaniu pakietów zasobów, przekształcając je w iso-latin-1, tak jakProperties.load
naprawiono tylko do odczytu latin-1. I tam reguły są różne, bez\uuu…
składni i bez wczesnego etapu przetwarzania. W plikach właściwościproperty=multi\u000aline
jest rzeczywiście taki sam jakproperty=multi\nline
. (Sprzeczne z wyrażeniem „używanie znaków ucieczki Unicode zgodnie z definicją w sekcji 3.3 specyfikacji języka Java ™” dokumentacji)\u
ucieczkom generowania znaków w zakresie U + 0000–007F. (Wszystkie takie znaki mogą być reprezentowane natywnie przez wszystkie narodowe kodowania, które były istotne w latach 90. - no może oprócz niektórych znaków kontrolnych, ale i tak nie trzeba ich pisać w Javie.)Zamierzam całkowicie nieskutecznie dodać punkt, tylko dlatego, że nie mogę się powstrzymać i jeszcze go nie widziałem, że pytanie jest nieprawidłowe, ponieważ zawiera ukrytą przesłankę, która jest błędna, a mianowicie, że kod jest w komentarz!
W Javie kod źródłowy \ u000d jest pod każdym względem równoważny znakowi ASCII CR. Jest to linia kończąca się, prosta i prosta, gdziekolwiek się pojawi. Formatowanie w pytaniu jest mylące, czemu właściwie odpowiada ta sekwencja znaków:
IMHO najbardziej poprawną odpowiedzią jest zatem: kod jest wykonywany, ponieważ nie ma go w komentarzu; jest w następnej linii. „Wykonywanie kodu w komentarzach” jest niedozwolone w Javie, tak jak można się spodziewać.
Wiele zamieszania wynika z faktu, że wyróżniki składni i środowiska IDE nie są wystarczająco zaawansowane, aby uwzględnić tę sytuację. Albo wcale nie przetwarzają znaków ucieczki unicode, albo robią to po parsowaniu kodu, a nie przedtem, jak to
javac
robi.źródło
\u000d
Ucieczka kończy komentarz bo\u
ucieczek są równomiernie konwertowane do odpowiadających im znaków Unicode zanim program jest tokenized. Można równie używać\u0057\u0057
zamiast//
się rozpocząć komentarz.Jest to błąd w twoim IDE, który powinien podświetlić składnię linii, aby było jasne, że
\u000d
koniec komentarza.Jest to również błąd projektowy w języku. Nie można go teraz poprawić, ponieważ spowodowałoby to uszkodzenie programów od niego zależnych.
\u
znaki ucieczki powinny być albo konwertowane na odpowiedni znak Unicode przez kompilator tylko w kontekstach, w których to „ma sens” (literały łańcuchowe i identyfikatory i prawdopodobnie nigdzie indziej) lub powinny być zabronione generowanie znaków w zakresie U + 0000–007F , lub obie. Każda z tych semantyków uniemożliwiłaby zakończenie komentarza przez\u000d
ucieczkę, bez ingerencji w przypadki, w których\u
użyteczne są ucieczki - zauważ, że to obejmuje użycie\u
ucieczek wewnątrz komentarzy jako sposobu kodowania komentarzy w skrypcie innym niż łaciński, ponieważ edytor tekstów mógłby wziąć szerszy pogląd na to, gdzie\u
sekwencje specjalne są znaczące niż kompilator. (Nie znam jednak żadnego edytora ani IDE, które wyświetlają\u
znaki specjalne jako odpowiednie znaki w dowolnym kontekście).W rodzinie C występuje podobny błąd projektowy 1, w którym nowa kreska ułamkowa odwrócona jest przetwarzana przed określeniem granic komentarzy, np.
Przedstawiam to, aby zilustrować, że łatwo jest popełnić ten konkretny błąd projektowy, i nie zdaję sobie sprawy, że jest to błąd, dopóki nie jest za późno, aby go naprawić, jeśli jesteś przyzwyczajony do myślenia o tokenizacji i analizowaniu sposobu myślenia programistów kompilatora o tokenizacji i analizie. Zasadniczo, jeśli już zdefiniowałeś swoją formalną gramatykę, a następnie ktoś wymyślił specjalny składniowy przypadek - trigrafy, ukośnik-nowa linia, kodowanie dowolnych znaków Unicode w plikach źródłowych ograniczonych do ASCII, cokolwiek - które muszą być zaklinowane, łatwiej jest dodaj przepustkę transformacji wcześniej tokenizerem, aby zdefiniować tokenizator, aby zwrócić uwagę na to, gdzie warto użyć tego specjalnego przypadku.
1 Dla pedantów: Zdaję sobie sprawę, że ten aspekt C był w 100% zamierzony, a uzasadnienie - nie zmyślam tego - że pozwoli ci na mechaniczne dopasowanie kodu z dowolnie długimi liniami do perforowanych kart. To wciąż była niepoprawna decyzja projektowa.
źródło
\u
był mniej absurdalny niż decyzja o podążaniu za przykładem C w użyciu zer wiodących do notacji ósemkowej. Chociaż notacja ósemkowa jest czasem przydatna, jeszcze nie słyszałem, aby ktokolwiek wypowiedział argument, dlaczego wiodące zero jest dobrym sposobem na wskazanie tego.\u
jako transformacją przed tokenizacją, gdyby zabroniono tworzenia znaków w zakresie U + 0000..U + 007F. Jest to kombinacja „to działa wszędzie” i „aliasuje znaki ASCII o znaczeniu składniowym”, które obniżają go z niezręcznego do całkowicie błędnego.//
komentarz jednoliniowy nie istnieje . A ponieważ C ma terminator instrukcji, który nie jest nową linią, byłby głównie używany do długich łańcuchów, z wyjątkiem tego, o ile mogę ustalić, że „dosłowne łączenie łańcuchów znaków” było tam z K&R.To był celowy wybór projektu, który sięga wstecz do pierwotnego projektu Java.
Dla tych, którzy pytają „kto chce, aby Unicode ucieka w komentarzach?”, Zakładam, że są to ludzie, których język ojczysty używa zestawu znaków łacińskich. Innymi słowy, nieodłącznym elementem oryginalnego projektu Javy jest to, że ludzie mogą używać dowolnych znaków Unicode wszędzie tam, gdzie jest to dozwolone w programie Java, najczęściej w komentarzach i ciągach znaków.
Jest to prawdopodobnie niedociągnięcie w programach (takich jak IDE) używanych do przeglądania tekstu źródłowego, że takie programy nie mogą interpretować znaków ucieczki Unicode i wyświetlać odpowiedniego glifu.
źródło
Zgadzam się z @zwol, że jest to błąd projektowy; ale jestem jeszcze bardziej krytyczny.
\u
Escape jest użyteczny w literałach łańcuchowych i znakach; i to jest jedyne miejsce, w którym powinno istnieć. Powinno być traktowane tak samo jak inne ucieczki\n
; i"\u000A"
powinno oznaczać dokładnie"\n"
.W
\uxxxx
komentarzach absolutnie nie ma sensu - nikt tego nie może przeczytać.Podobnie nie ma sensu używać
\uxxxx
w innej części programu. Jedynym wyjątkiem są prawdopodobnie publiczne interfejsy API, które są zmuszane do umieszczania znaków innych niż ascii - kiedy ostatnio to widzieliśmy?Projektanci mieli swoje powody w 1995 roku, ale 20 lat później wydaje się, że to zły wybór.
(pytanie do czytelników - dlaczego to pytanie wciąż zyskuje nowe głosy? czy to pytanie jest powiązane z jakiegoś miejsca popularnego?)
źródło
int \u5431
po co pisz, kiedy możeszint 整
UTF-8
w 1995 roku nie było powszechnego wsparcia). Musisz tylko wywołać jedną metodę i nie chcesz instalować pakietu wsparcia języka azjatyckiego dla swojego systemu operacyjnego (pamiętaj, lata dziewięćdziesiąte) dla tej jednej metody…Jedynymi osobami, które potrafią odpowiedzieć na pytanie, dlaczego ucieczki Unicode zostały zaimplementowane, są ludzie, którzy napisali specyfikację.
Prawdopodobnym powodem jest chęć dopuszczenia całego BMP jako możliwego znaku kodu źródłowego Java. Stanowi to jednak problem:
Jest to niezwykle trudne, gdy ucieczki Unicode wchodzą do walki: tworzy cały ładunek nowych reguł leksykalnych.
Najłatwiejszym rozwiązaniem jest wykonanie leksykalizacji w dwóch krokach: najpierw wyszukaj i zamień wszystkie znaki zmiany znaczenia Unicode na znak, który reprezentuje, a następnie przeanalizuj wynikowy dokument tak, jakby znaki zmiany znaczenia nie istniały.
Zaletą tego jest to, że jest łatwy do określenia, więc upraszcza specyfikację i jest łatwy do wdrożenia.
Minusem jest twój przykład.
źródło