Warum druckt dieser rückwärts geschriebene Code "Hallo Welt!"

261

Hier ist ein Code, den ich im Internet gefunden habe:

class M‮{public static void main(String[]a‭){System.out.print(new char[]
{'H','e','l','l','o',' ','W','o','r','l','d','!'});}}    

Dieser Code wird Hello World!auf dem Bildschirm gedruckt . Sie können es hier laufen sehen . Ich kann deutlich public static void maingeschrieben sehen, aber es ist rückwärts. Wie funktioniert dieser Code? Wie kompiliert das überhaupt?

Bearbeiten: Ich habe diesen Code in IntellIJ ausprobiert und er funktioniert einwandfrei. Aus irgendeinem Grund funktioniert es jedoch nicht in Notepad ++ zusammen mit cmd. Ich habe immer noch keine Lösung dafür gefunden. Wenn dies jemand tut, kommentieren Sie unten.

Imaginärer Kürbis
quelle
38
Dieser ist lustig ... Hast du etwas mit RTL-Unterstützung zu tun?
Eugene Sh.
12
Es gibt das Unicode-Zeichen # 8237; direkt nach dem Mund auch nach []a: fileformat.info/info/unicode/char/202d/index.htm Es heißt LEFT-TO-RIGHT OVERRIDE
Riiverside
45
obligatorisch xkcd: xkcd.com/1137
Pac0
4
Sie können sehr leicht sehen, was hier vor sich geht, indem Sie einfach mit der Maus eine Auswahl im Code-Snippet treffen.
Andreas Rejbrand
14
niam diov citats cilbupklingt wie ein lateinisches Sprichwort ..
Mick Mnemonic

Antworten:

250

Hier gibt es unsichtbare Zeichen, die die Anzeige des Codes ändern. In Intellij können Sie diese finden, indem Sie den Code in eine leere Zeichenfolge ( "") kopieren , die sie durch Unicode-Escapezeichen ersetzt, ihre Effekte entfernt und die Reihenfolge anzeigt , die der Compiler sieht.

Hier ist die Ausgabe dieses Copy-Paste:

"class M\u202E{public static void main(String[]a\u202D){System.out.print(new char[]\n"+
        "{'H','e','l','l','o',' ','W','o','r','l','d','!'});}}   "

Die Quellcodezeichen werden in dieser Reihenfolge gespeichert, und der Compiler behandelt sie als in dieser Reihenfolge, sie werden jedoch unterschiedlich angezeigt.

Beachten Sie das \u202EZeichen, bei dem es sich um eine Überschreibung von rechts nach links handelt, das einen Block startet, in dem alle Zeichen von rechts nach links angezeigt werden müssen, und das Zeichen, bei dem es sich um eine Überschreibung von \u202Dlinks nach rechts handelt, bei dem ein verschachtelter Block beginnt, bei dem alle Zeichen angezeigt werden Zeichen werden in eine Reihenfolge von links nach rechts gezwungen, wobei die erste Überschreibung überschrieben wird.

Ergo wird, wenn der ursprüngliche Code class Mangezeigt wird, normal angezeigt, aber das \u202Ekehrt die Anzeigereihenfolge von allem von dort zu dem um \u202D, wodurch alles wieder umgekehrt wird. (Formal wird alles vom \u202Dbis zum Zeilenendezeichen zweimal umgekehrt, einmal aufgrund des \u202Dund einmal, wobei der Rest des Textes aufgrund des umgekehrt \u202Ewird, weshalb dieser Text in der Mitte der Zeile statt am Ende angezeigt wird.) Die Direktionalität der nächsten Zeile wird aufgrund des Zeilenabschlusses unabhängig von der der ersten behandelt und wird daher {'H','e','l','l','o',' ','W','o','r','l','d','!'});}}normal angezeigt.

Den vollständigen (äußerst komplexen, Dutzende von Seiten langen) bidirektionalen Unicode-Algorithmus finden Sie im Unicode-Standardanhang Nr. 9 .

Davis Broda
quelle
Sie erklären nicht, was der Compiler (im Gegensatz zur Anzeigeroutine) mit diesen Unicode-Zeichen selbst macht. Ich könnte sie völlig ignorieren (oder sie als Leerraum behandeln) oder sie als tatsächlich zum Quellcode beitragend interpretieren. Ich kenne die Java-Regeln hier nicht, aber die Tatsache, dass sie am Ende ansonsten nicht verwendeter Bezeichner stehen, legt mir nahe, dass es sich möglicherweise um letztere handelt, und die Unicode-Zeichen sind tatsächlich Teil dieser Bezeichnernamen.
Marc van Leeuwen
Würde dies aus Interesse in c # genauso funktionieren?
IanF1
14
@ IanF1 Es würde in jeder Sprache funktionieren, in der der Compiler / Interpreter RTL- und LTR-Zeichen als Leerzeichen zählt. Aber tue dies nie Code in der Produktion , wenn Sie die geistige Gesundheit der nächsten Person an alle Wert Code zu berühren, was gut Sie sein könnte.
wizzwizz4
2
Oder mit anderen Worten: "Codieren Sie immer so, als ob die Person, die Ihren Code verwaltet, ein gewalttätiger Psychopath ist, der weiß, wo Sie leben." , @ IanF1. Oder vielleicht: "Codieren Sie immer so, als würde die Person, die Ihren Code verwaltet, Sie als ursprünglichen Autor von Stack Overflow benennen und beschämen."
Cody Gray
43

Aufgrund des bidirektionalen Unicode-Algorithmus sieht es anders aus . Es gibt zwei unsichtbare Zeichen von VKE und LRO, die der bidirektionale Unicode-Algorithmus verwendet, um das visuelle Erscheinungsbild der zwischen diesen beiden Metazeichen verschachtelten Zeichen zu ändern .

Das Ergebnis ist, dass sie visuell in umgekehrter Reihenfolge aussehen, die tatsächlichen Zeichen im Speicher jedoch nicht umgekehrt werden. Sie können die Ergebnisse analysieren hier . Der Java-Compiler ignoriert RLO und LRO und behandelt sie als Leerzeichen, weshalb der Code kompiliert wird.

Hinweis 1: Dieser Algorithmus wird von Texteditoren und Browsern verwendet, um Zeichen sowohl LTR-Zeichen (Englisch) als auch RTL-Zeichen (z. B. Arabisch, Hebräisch) gleichzeitig visuell anzuzeigen - daher "bi" -direktional. Weitere Informationen zum bidirektionalen Algorithmus finden Sie auf der Unicode- Website .
Anmerkung 2: Das genaue Verhalten von LRO und RLO ist in Abschnitt 2.2 des Algorithmus definiert.

James Lawson
quelle
Was ist der Zweck einer solchen Fähigkeit?
Eugene Sh.
6
Diese Zeichen werden manchmal benötigt, um Arabisch und Hebräisch visuell korrekt wiederzugeben. Diese Sprachen werden von rechts nach links (RTL) gelesen und geschrieben. Das erste Zeichen, das gelesen / geschrieben wird, wird auf der rechten Seite angezeigt . Sie können mehr lesen hier .
James Lawson
Arabische und hebräische Zeichen sind jedoch an sich RTL - sie werden auch ohne explizite Überschreibung als RTL angezeigt und sie kehren sogar automatisch die Reihenfolge bestimmter anderer Zeichen in der Nähe um, ich denke meistens Interpunktion - daher sind explizite Überschreibungen selten erforderlich.
user2357112 unterstützt Monica
Diese Seite hier beschreibt, wann die Überschreibungen erforderlich sind. @ user2357112 ist richtig, sie werden selten benötigt. In der Tat, wenn Sie Interpunktion, Zitate und Zahlen haben - diese Sonderzeichen gelten als "neutral". Für einen Computer, der die Wörter nicht lesen und den Kontext nicht verstehen kann, ist unklar, ob sie als LTR oder RTL behandelt werden sollen, aber der Bidi-Algorithmus muss eine Reihenfolge auswählen . Manchmal "macht es falsch" und Sie müssen diese Überschreibungszeichen verwenden, um "es zu korrigieren".
James Lawson
3
Außerdem werden U + 202E und U + 202D nicht als Leerzeichen betrachtet. Java berücksichtigt nur ASCII-Speicherplatz, horizontale Registerkarte, Formular-Feed und CR / LF / CRLF als Leerzeichen . Sie sind tatsächlich lexikalisch Teil der Bezeichner M\u202Eund a\u202D, aber diese Bezeichner scheinen als äquivalent zu Mund behandelt zu werden a. (Das JLS erklärt dies nicht gut.)
user2357112 unterstützt Monica
28

Der Charakter U+202Espiegelt den Code von rechts nach links, ist aber sehr clever. Ist ab dem M versteckt,

"class M\u202E{..."

Wie habe ich die Magie dahinter gefunden?

Nun, als ich zuerst die Frage sah, die mir schwerfiel: "Es ist eine Art Witz, jemand anderem Zeit zu verlieren", aber dann öffnete ich meine IDE ("IntelliJ"), erstellte eine Klasse und übergab den Code ... und es kompiliert !!! Also schaute ich genauer hin und sah, dass die "öffentliche statische Leere" rückwärts war, also ging ich mit dem Cursor dorthin und löschte ein paar Zeichen ... Und was passiert? Die Zeichen wurden rückwärts gelöscht , also dachte ich, mmm ... selten ... ich muss es ausführen ... Also führe ich das Programm aus, aber zuerst musste ich es speichern ... und dann war ich es fand es! . Ich konnte die Datei nicht speichern, da meine IDE angab, dass für ein Zeichen eine andere Codierung vorhanden war, und zeigte mir, wo sie sich befandAlso starte ich in Google eine Recherche nach speziellen Zeichen, die den Job machen könnten, und das war's :)

Ein bisschen über

Der bidirektionale Unicode-Algorithmus und die U+202Ebeteiligten erklären kurz :

Der Unicode-Standard schreibt eine Speicherrepräsentationsreihenfolge vor, die als logische Reihenfolge bezeichnet wird. Wenn Text in horizontalen Linien dargestellt wird, zeigen die meisten Skripte Zeichen von links nach rechts an. Es gibt jedoch mehrere Skripte (wie Arabisch oder Hebräisch), bei denen die natürliche Reihenfolge des angezeigten horizontalen Textes von rechts nach links erfolgt. Wenn der gesamte Text eine einheitliche horizontale Richtung hat, ist die Reihenfolge des Anzeigetextes eindeutig.

Da diese Skripte von rechts nach links jedoch Ziffern verwenden, die von links nach rechts geschrieben werden, ist der Text tatsächlich bidirektional: eine Mischung aus Text von rechts nach links und von links nach rechts. Neben Ziffern werden auch eingebettete Wörter aus dem Englischen und anderen Skripten von links nach rechts geschrieben, wodurch auch bidirektionaler Text erzeugt wird. Ohne eine klare Spezifikation können Unklarheiten bei der Bestimmung der Reihenfolge der angezeigten Zeichen auftreten, wenn die horizontale Richtung des Textes nicht einheitlich ist.

In diesem Anhang wird der Algorithmus beschrieben, mit dem die Richtung für bidirektionalen Unicode-Text bestimmt wird. Der Algorithmus erweitert das implizite Modell, das derzeit von einer Reihe vorhandener Implementierungen verwendet wird, und fügt explizite Formatierungszeichen für besondere Umstände hinzu. In den meisten Fällen müssen dem Text keine zusätzlichen Informationen hinzugefügt werden, um eine korrekte Anzeigereihenfolge zu erhalten.

Im Fall von bidirektionalem Text gibt es jedoch Umstände, in denen eine implizite bidirektionale Reihenfolge nicht ausreicht, um verständlichen Text zu erstellen. Um diese Fälle zu behandeln, wird ein minimaler Satz von Richtungsformatierungszeichen definiert, um die Reihenfolge der Zeichen beim Rendern zu steuern. Dies ermöglicht eine genaue Kontrolle der Anzeigereihenfolge für einen lesbaren Austausch und stellt sicher, dass Klartext, der für einfache Elemente wie Dateinamen oder Beschriftungen verwendet wird, für die Anzeige immer korrekt angeordnet werden kann.

Warum einige Algorithmus wie schaffen das ?

Der Bidi-Algorithmus kann eine Folge von arabischen oder hebräischen Zeichen nacheinander von rechts nach links rendern.

Damián Rafael Lattenero
quelle
4

Kapitel 3 der Sprachspezifikation enthält eine Erläuterung, indem detailliert beschrieben wird, wie die lexikalische Übersetzung für ein Java-Programm durchgeführt wird. Was für die Frage am wichtigsten ist:

Programme sind in Unicode (§3.1) geschrieben , es werden jedoch lexikalische Übersetzungen bereitgestellt (§3.2), sodass Unicode-Escapezeichen (§3.3) verwendet werden können, um jedes Unicode-Zeichen nur mit ASCII-Zeichen einzuschließen.

Ein Programm ist also in Unicode-Zeichen geschrieben, und der Autor kann sie umgehen, indem \uxxxxer verwendet, falls die Dateicodierung das Unicode-Zeichen nicht unterstützt. In diesem Fall wird es in das entsprechende Zeichen übersetzt. Eines der in diesem Fall vorhandenen Unicode-Zeichen ist \u202E. Es wird im Snippet nicht visuell angezeigt. Wenn Sie jedoch versuchen, die Codierung des Browsers zu ändern, werden möglicherweise versteckte Zeichen angezeigt.

Daher führt die lexikalische Übersetzung zur Klassendeklaration:

class M\u202E{

was bedeutet, dass die Klassenkennung ist M\u202E. Die Spezifikation betrachtet dies als gültigen Bezeichner:

Identifier:
    IdentifierChars but not a Keyword or BooleanLiteral or NullLiteral
IdentifierChars:
    JavaLetter {JavaLetterOrDigit}

Ein "Java-Buchstabe oder eine Ziffer" ist ein Zeichen, für das die Methode Character.isJavaIdentifierPart(int)true zurückgibt.

M Anouti
quelle
Sorry, aber das ist rückwärts (Wortspiel beabsichtigt). Der Quellcode enthält keine Escapezeichen. Sie beschreiben, wie es hätte geschrieben werden können. Und es wird zu einer Klasse namens "M" kompiliert (nur ein Zeichen).
Tom Blodget
@ TomBlodget In der Tat, aber der Punkt (den ich im Spezifikationszitat hervorgehoben habe) ist, dass der Compiler auch rohe Unicode-Zeichen verarbeiten kann. Das ist wirklich die ganze Erklärung. Die Escape-Übersetzung ist nur eine zusätzliche Information und steht nicht in direktem Zusammenhang mit diesem Fall. Was die kompilierte Klasse betrifft, denke ich, dass das RTL-Switch-Zeichen vom Compiler irgendwie verworfen wird. Ich werde versuchen zu sehen, ob dies erwartet wird, aber ich denke, dass dies nach der lexikalischen Übersetzungsphase geschieht.
M Anouti