Perché questo codice, scritto al contrario, stampa "Hello World!"


261

Ecco un po 'di codice che ho trovato su Internet:

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

Questo codice viene stampato Hello World!sullo schermo; puoi vederlo correre qui . Posso vedere chiaramente public static void mainscritto, ma è al contrario. Come funziona questo codice? Come si compila anche questo?

Modifica: ho provato questo codice in IntellIJ e funziona benissimo. Tuttavia, per qualche motivo non funziona in notepad ++, insieme a cmd. Non ho ancora trovato una soluzione, quindi se qualcuno lo fa, commenta in basso.


38
Questo è divertente ... C'è qualcosa a che fare con il supporto RTL?
Eugene Sh.

12
C'è il personaggio Unicode # 8237; subito dopo Me anche dopo []a: fileformat.info/info/unicode/char/202d/index.htm Si chiama
OVERLIDE SINISTRA

45
xkcd obbligatorio: xkcd.com/1137
Pac0

4
Puoi vedere facilmente cosa sta succedendo qui semplicemente facendo selezioni nello snippet di codice usando il mouse.
Andreas Rejbrand,

14
niam diov citats cilbupsembra un proverbio latino ..
Mick Mnemonic,

Risposte:


250

Ci sono caratteri invisibili qui che alterano la modalità di visualizzazione del codice. In Intellij questi possono essere trovati copiando e incollando il codice in una stringa vuota ( ""), che li sostituisce con escape Unicode, rimuovendo i loro effetti e rivelando l'ordine che il compilatore vede.

Ecco l'output di quel copia-incolla:

"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','!'});}}   "

I caratteri del codice sorgente sono memorizzati in questo ordine e il compilatore li considera come in questo ordine, ma sono visualizzati in modo diverso.

Nota il \u202Ecarattere, che è un override da destra a sinistra, che inizia un blocco in cui tutti i personaggi sono obbligati a essere visualizzati da destra a sinistra e il \u202D, che è un override da sinistra a destra, iniziando un blocco nidificato in cui tutti i personaggi vengono forzati nell'ordine da sinistra a destra, sovrascrivendo la prima sostituzione.

Ergo, quando visualizza il codice originale, class Mviene visualizzato normalmente, ma \u202Einverte l'ordine di visualizzazione di tutto da lì a \u202D, che inverte di nuovo tutto. (Formalmente, tutto dalla \u202Dterminazione alla riga viene invertito due volte, una volta a causa della \u202De una volta con il resto del testo invertito a causa della \u202E, motivo per cui questo testo appare al centro della riga anziché alla fine.) La direzionalità della riga successiva viene gestita indipendentemente dalla prima a causa del terminatore di linea, quindi {'H','e','l','l','o',' ','W','o','r','l','d','!'});}}viene visualizzata normalmente.

Per l'algoritmo bidirezionale Unicode completo (estremamente complesso, lungo decine di pagine), consultare l' Allegato n . 9 Unicode Standard .


Non spieghi cosa fa il compilatore (al contrario della routine di visualizzazione) con quegli stessi caratteri Unicode. Potrei ignorarli apertamente (o trattarli come uno spazio bianco), o potrebbe interpretarli come effettivamente contribuendo al codice sorgente. Non conosco le regole Java qui, ma il fatto che siano collocate alla fine di identificatori altrimenti inutilizzati mi suggerisce che potrebbe essere quest'ultima e che i caratteri Unicode fanno effettivamente parte di quei nomi identificativi.
Marc van Leeuwen,

Funzionerebbe allo stesso modo in c #, per interesse?
IanF1,

14
@ IanF1 Funzionerebbe in qualsiasi lingua in cui il compilatore / interprete conteggi i caratteri RTL e LTR come spazi bianchi. Ma non farlo mai nel codice di produzione se apprezzi la sanità mentale della persona successiva per toccare il tuo codice, che potresti benissimo essere tu.
wizzwizz4,

2
O, in altre parole: "Codice sempre come se la persona che finisce per mantenere il tuo codice sia uno psicopatico violento che sa dove vivi." , @ IanF1. O forse: "Codice sempre come se la persona che finisce per mantenere il tuo codice ti nominasse e si vergogni come autore originale su Stack Overflow."
Cody Gray

43

Sembra diverso a causa dell'algoritmo bidirezionale Unicode . Esistono due caratteri invisibili di RLO e LRO che l'algoritmo bidirezionale Unicode utilizza per modificare l' aspetto visivo dei caratteri nidificati tra questi due metacaratteri.

Il risultato è che visivamente sembrano in ordine inverso, ma i caratteri effettivi in memoria non sono invertiti. Puoi analizzare i risultati qui . Il compilatore Java ignorerà RLO e LRO e li tratterà come spazi bianchi, motivo per cui il codice viene compilato.

Nota 1: questo algoritmo viene utilizzato dagli editor di testo e dai browser per visualizzare visivamente contemporaneamente i caratteri LTR (inglese) e RTL (ad esempio arabo, ebraico), quindi "bi" direzionale. Puoi leggere ulteriori informazioni sull'algoritmo bidirezionale sul sito Web di Unicode .
Nota 2: L'esatto comportamento di LRO e RLO è definito nella Sezione 2.2 dell'algoritmo.


Qual è lo scopo di tale capacità?
Eugene Sh.

6
Questi personaggi sono necessari a volte per rendere visivamente correttamente l'arabo e l'ebraico. Queste lingue sono lette e scritte da destra a sinistra (RTL), il primo carattere letto / scritto appare sul lato destro . Puoi leggere di più qui .
James Lawson,

Tuttavia, i caratteri arabi ed ebraici sono intrinsecamente RTL: appariranno RTL anche senza una sostituzione esplicita, e annulleranno anche automaticamente l'ordinamento di alcuni altri personaggi nelle vicinanze, penso principalmente punteggiatura, quindi raramente sono necessarie sostituzioni esplicite.
user2357112 supporta Monica

Questa pagina qui descrive quando sono necessarie le sostituzioni. @ user2357112 ha ragione, raramente sono necessari. In effetti quando hai punteggiatura, virgolette e numeri, questi caratteri speciali sono considerati "neutri". Per un computer che non è in grado di leggere le parole e comprendere il contesto, non è chiaro se trattarle come LTR o RTL, ma l'algoritmo bidi deve scegliere un po 'di ordinamento. A volte "sbaglia" e devi usare questi caratteri di sostituzione per "correggerlo".
James Lawson,

3
Inoltre, U + 202E e U + 202D non sono considerati spazi bianchi. Java considera solo lo spazio ASCII, la scheda orizzontale, il feed dei moduli e CR / LF / CRLF . Sono effettivamente parte lessicale degli identificatori M\u202Ee a\u202D, ma quegli identificatori sembrano essere trattati come equivalenti a Me a. (Il JLS non fa un buon lavoro nel spiegarlo.)
user2357112 supporta Monica

28

Il personaggio U+202Erispecchia il codice da destra a sinistra, tuttavia è molto intelligente. È nascosto a partire dalla M,

"class M\u202E{..."

Come ho trovato la magia dietro questo?

Bene, all'inizio quando ho visto la domanda che mi davo da fare, "è una specie di scherzo, perdere qualcun altro tempo", ma poi ho aperto il mio IDE ("IntelliJ"), ho creato una classe e superato il codice ... e compilato !!! Quindi, ho dato un'occhiata migliore e ho visto che il "vuoto statico pubblico" era all'indietro, quindi ci sono andato con il cursore e ho cancellato alcuni caratteri ... E cosa succede? I caratteri hanno iniziato a cancellare all'indietro , quindi, ho pensato mmm .... raro ... devo eseguirlo ... Quindi continuo ad eseguire il programma, ma prima ho dovuto salvarlo ... e fu allora che trovato! . Non sono riuscito a salvare il file perché il mio IDE ha detto che c'era una codifica diversa per alcuni caratteri e mi ha indicato dove fosse, Quindi inizio una ricerca su Google per caratteri speciali che potrebbero fare il lavoro, e basta :)

Un po '

l'algoritmo bidirezionale Unicode, e U+202Ecoinvolto, spiega brevemente :

Lo standard Unicode prescrive un ordine di rappresentazione della memoria noto come ordine logico. Quando il testo viene presentato in linee orizzontali, la maggior parte degli script visualizza i caratteri da sinistra a destra. Tuttavia, ci sono diversi script (come l'arabo o l'ebraico) in cui l'ordinamento naturale del testo orizzontale visualizzato è da destra a sinistra. Se tutto il testo ha una direzione orizzontale uniforme, l'ordinamento del testo visualizzato non è ambiguo.

Tuttavia, poiché questi script da destra a sinistra utilizzano cifre scritte da sinistra a destra, il testo è in realtà bidirezionale: una combinazione di testo da destra a sinistra e da sinistra a destra. Oltre alle cifre, le parole incorporate dall'inglese e altri script sono anche scritte da sinistra a destra, producendo anche testo bidirezionale. Senza una specifica chiara, possono sorgere ambiguità nel determinare l'ordinamento dei caratteri visualizzati quando la direzione orizzontale del testo non è uniforme.

Il presente allegato descrive l'algoritmo utilizzato per determinare la direzionalità del testo Unicode bidirezionale. L'algoritmo estende il modello implicito attualmente impiegato da una serie di implementazioni esistenti e aggiunge caratteri di formattazione espliciti per circostanze speciali. Nella maggior parte dei casi, non è necessario includere informazioni aggiuntive nel testo per ottenere un ordine di visualizzazione corretto.

Tuttavia, nel caso del testo bidirezionale, vi sono circostanze in cui un ordinamento bidirezionale implicito non è sufficiente per produrre un testo comprensibile. Per gestire questi casi, viene definito un set minimo di caratteri di formattazione direzionale per controllare l'ordinamento dei caratteri durante il rendering. Ciò consente il controllo esatto dell'ordine di visualizzazione per lo scambio leggibile e garantisce che il testo normale utilizzato per elementi semplici come nomi di file o etichette possa sempre essere correttamente ordinato per la visualizzazione.

Perché creare un algoritmo come questo ?

l'algoritmo bidi può eseguire il rendering di una sequenza di caratteri arabi o ebraici uno dopo l'altro da destra a sinistra.


4

Il capitolo 3 della specifica del linguaggio fornisce una spiegazione descrivendo in dettaglio come viene eseguita la traduzione lessicale per un programma Java. Ciò che conta di più per la domanda:

I programmi sono scritti in Unicode (§3.1) , ma sono fornite traduzioni lessicali (§3.2) in modo che gli escape Unicode (§3.3) possano essere usati per includere qualsiasi carattere Unicode usando solo caratteri ASCII.

Quindi un programma è scritto con caratteri Unicode e l'autore può evitarli usando \uxxxxnel caso in cui la codifica del file non supporti il ​​carattere Unicode, nel qual caso viene tradotto nel carattere appropriato. Uno dei caratteri Unicode presenti in questo caso è \u202E. Non viene visualizzato visivamente nello snippet, ma se provi a cambiare la codifica del browser, potrebbero apparire i caratteri nascosti.

Pertanto, la traduzione lessicale risulta nella dichiarazione di classe:

class M\u202E{

il che significa che l'identificatore di classe è M\u202E. La specifica lo considera un identificatore valido:

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

Una "lettera o cifra Java" è un carattere per il quale il metodo Character.isJavaIdentifierPart(int)restituisce true.


Scusate ma questo è all'indietro (gioco di parole). Non ci sono escape nel codice sorgente; stai descrivendo come avrebbe potuto essere scritto. E, si compila in una classe chiamata "M" (solo un personaggio).
Tom Blodget,

@TomBlodget In effetti, ma il punto (che in effetti ho evidenziato nella citazione delle specifiche) è che il compilatore può anche elaborare caratteri Unicode non elaborati. Questa è davvero l'intera spiegazione. La traduzione di escape è solo un'informazione aggiuntiva e non direttamente correlata a questo caso. Per quanto riguarda la classe compilata, penso sia perché il carattere switch RTL viene in qualche modo scartato dal compilatore. Proverò a vedere se questo è previsto, ma penso che accada dopo la fase di traduzione lessicale.
M Anouti,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.