Чому цей код, написаний назад, друкує "Hello World!"


261

Ось код, який я знайшов в Інтернеті:

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

Цей код друкується Hello World!на екрані; ви можете побачити, як він працює тут . Я чітко бачу public static void mainнаписане, але це назад. Як працює цей код? Як це навіть компілюється?

Редагувати: Я спробував цей код в IntellIJ, і він прекрасно працює. Однак чомусь він не працює в блокноті ++ разом із cmd. Я досі не знайшов рішення для цього, тому, якщо хто це зробив, прокоментуйте нижче.


38
Це смішно ... Щось із підтримкою RTL?
Євген Ш.

12
Там є символ Unicode # 8237; одразу після, Mа також після []a: fileformat.info/info/unicode/char/202d/index.htm Це називається
НАЛЯГО-ПРАВИЙ НАДНІЙ

45
обов'язкові xkcd: xkcd.com/1137
Pac0

4
Ви можете легко побачити, що відбувається тут, просто зробивши виділення у фрагменті коду за допомогою миші.
Андреас Рейбранд

14
niam diov citats cilbupзвучить як латинське прислів’я ..
Мік Мнемонік

Відповіді:


250

Тут є невидимі символи, які змінюють спосіб відображення коду. У Intellij їх можна знайти, скопіювавши код у порожній рядок ( ""), який замінює їх уникненнями Unicode, видаляючи їх ефекти та розкриваючи порядок, який бачить компілятор.

Ось вихід цієї копії-пасти:

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

Символи вихідного коду зберігаються в цьому порядку, і компілятор розглядає їх як такі, що в цьому порядку, але вони відображаються інакше.

Зверніть увагу на \u202Eсимвол, який є переоправленням справа наліво, починаючи блок, де всі символи змушені відображатися праворуч ліворуч, а той \u202D, що є переоправленням зліва направо, починаючи вкладений блок, де всі символи змушені переходити вліво-вправо, переосмислюючи перше заміщення.

Ergo, коли він відображає оригінальний код, class Mвідображається нормально, але \u202Eповертає порядок відображення всього звідти до пункту \u202D, який повертає все знову. (Формально все від \u202Dтермінатора до рядка повертається двічі, один раз за рахунок \u202Dі один раз, коли решта тексту перевернута через \u202E, тому цей текст відображається посередині рядка замість кінця.) Спрямованість наступного рядка керується незалежно від першого через термінатор лінії, тому {'H','e','l','l','o',' ','W','o','r','l','d','!'});}}відображається нормально.

Про повний (надзвичайно складний, десятки сторінок) двонаправлений алгоритм Unicode див. У стандартному додатку Unicode №9 .


Ви не пояснюєте, що компілятор (на відміну від режиму відображення) робить із самими цими символами Unicode. Я можу проігнорувати їх прямо (або трактувати їх як пробіли), або можу трактувати їх як фактично внесок у вихідний код. Я не знаю тут правил Java, але той факт, що вони розміщуються в кінці не використовуваних ідентифікаторів, підказує мені, що це може бути останній, і символи Unicode насправді є частиною цих імен ідентифікаторів.
Марк ван Левен

Чи буде це працювати так само в c #, з інтересу?
IanF1

14
@ IanF1 Він буде працювати будь-якою мовою, де компілятор / інтерпретатор вважає символи RTL та LTR як пробіли. Але ніколи цього не робіть у виробничому коді, якщо ви взагалі цінуєте розум наступної людини доторкатися до вашого коду, який цілком може бути вам.
wizzwizz4

2
Або, іншими словами: "Завжди кодуйте так, ніби людина, яка закінчує підтримувати ваш код, - це жорстокий психопат, який знає, де ви живете". , @ IanF1. Або, можливо: "Завжди кодуйте так, ніби людина, яка закінчує підтримувати ваш код, назве вас і соромить вас як оригінального автора на" Переповнення стека ".
Коді Грей

43

Це виглядає інакше через двонаправлений алгоритм Unicode . Існує два невидимих ​​символи RLO та LRO, які двонаправлений алгоритм Unicode використовує для зміни візуального вигляду символів, вкладених між цими двома метахарактеристиками.

Результат полягає в тому, що візуально вони виглядають у зворотному порядку, але фактичні символи в пам'яті не повертаються. Ви можете проаналізувати результати тут . Компілятор Java проігнорує RLO та LRO і розцінить їх як пробіли, тому код компілюється.

Примітка 1: Цей алгоритм використовується текстовими редакторами та браузерами для візуального відображення символів як символів LTR (англійською), так і RTL-символів (наприклад, арабською, івритом) разом. Більше про двонаправлений алгоритм ви можете прочитати на веб-сайті Unicode .
Примітка 2: Точна поведінка LRO та RLO визначена у розділі 2.2 алгоритму.


Яка мета такої спроможності?
Євген Ш.

6
Ці символи потрібні іноді, щоб візуально візуалізувати арабську та іврит правильно. Ці мови читаються та пишуться справа наліво (RTL), перший символ, який читається / записується, з’являється праворуч . Більше ви можете прочитати тут .
Джеймс Лоусон

Арабські та івритські символи є власне RTL, хоча - вони з'являться RTL навіть без явного переопределення, і вони навіть автоматично змінюватимуть впорядкування певних інших символів поблизу, я думаю, що в основному розділові знаки - тому чіткі відміни рідко потрібні.
user2357112 підтримує Моніку

Ця сторінка тут описує , коли перевизначення необхідно. @ user2357112 вірно, вони рідко потрібні. Дійсно, коли у вас є розділові знаки, цитати та цифри - ці спеціальні символи вважаються "нейтральними". Для комп'ютера, який не вміє читати слова і не розуміє контексту, незрозуміло, чи слід ставитися до них як до LTR або RTL, але алгоритм bidi повинен вибрати певне впорядкування. Іноді це «стає неправильним», і вам потрібно використовувати ці перекреслені символи, щоб «виправити це».
Джеймс Лоусон

3
Також U + 202E і U + 202D не вважаються пробілами. Java розглядає лише простір ASCII, горизонтальну вкладку, подачу форми та CR / LF / CRLF як пробіл . Вони на самому ділі лексичний частина ідентифікаторів M\u202Eі a\u202D, але ці ідентифікатори по всій видимості, розглядаються як еквівалентні Mі a. (JLS не дуже добре пояснює це.)
user2357112 підтримує Моніку

28

Персонаж U+202Eвідображає код справа наліво, хоча це дуже розумно. Приховано, починаючи з М,

"class M\u202E{..."

Як я знайшов магію за цим?

Ну, спочатку, коли я побачив важке запитання, "це якась жарт, втратити комусь інший час", але потім я відкрив свій IDE ("IntelliJ"), створив клас і минув код ... і складено !!! Отже, я краще подивився і побачив, що "публічна статична порожнеча" відстала, тому я пішов туди курсором і стер кілька символів ... І що трапляється? Знаки почали стиратися назад , тож, я думав, ммм .... рідко ... мені доводиться його виконувати ... Тож я приступаю до виконання програми, але спочатку мені потрібно було її зберегти ... і це було тоді, коли я знайшов це! . Я не зміг зберегти файл, тому що мій IDE сказав, що існує інше кодування для деяких знаків, і вкажіть мені, де це було, Тож я розпочинаю дослідження в Google щодо спеціальних завдань, які могли б виконати цю роботу, і це все :)

Трохи про

двонаправлений алгоритм Unicode і U+202Eкоротко пояснимо :

Стандарт Unicode прописує порядок представлення пам'яті, відомий як логічний порядок. Коли текст представлений горизонтальними рядками, більшість сценаріїв відображають символи зліва направо. Однак є кілька сценаріїв (таких як арабська чи іврит), де природне впорядкування горизонтального тексту відображається справа наліво. Якщо весь текст має рівномірний горизонтальний напрямок, то впорядкованість відображуваного тексту однозначна.

Однак, оскільки ці сценарії справа наліво використовують цифри, які записуються зліва направо, текст насправді є двонаправленим: суміш тексту справа наліво та зліва направо. Крім цифр, вбудовані слова з англійської та інших сценаріїв також пишуться зліва направо, також створюючи двонаправлений текст. Без чітких специфікацій можуть виникати неоднозначності при визначенні впорядкованості відображуваних символів, коли горизонтальний напрямок тексту не є рівномірним.

Цей додаток описує алгоритм, який використовується для визначення спрямованості двонаправленого тексту Unicode. Алгоритм розширює неявну модель, яка в даний час використовується рядом існуючих реалізацій, і додає явні символи форматування для особливих обставин. У більшості випадків не потрібно включати додаткову інформацію до тексту, щоб отримати правильне впорядкування дисплея.

Однак у випадку двонаправленого тексту є обставини, коли неявне двостороннє впорядкування недостатньо для отримання зрозумілого тексту. Для вирішення цих випадків визначається мінімальний набір символів спрямованого форматування для контролю впорядкованості символів під час надання. Це дозволяє точно контролювати впорядкованість дисплея для розбірливого обміну та гарантує, що звичайний текст, який використовується для простих елементів, таких як назви файлів або міток, завжди може бути правильно впорядкований для відображення.

Навіщо створювати деякий алгоритм , як це ?

алгоритм bidi може відображати послідовність арабських або єврейських символів один за одним справа наліво.


4

Розділ 3 специфікації мови надає пояснення, докладно описуючи, як лексичний переклад робиться для програми Java. Найважливіше питання:

Програми написані в Unicode (§3.1) , але надаються лексичні переклади (§3.2), щоб уникнути Unicode (§3.3) можна використовувати для включення будь-якого символу Unicode, використовуючи лише символи ASCII.

Таким чином, програма написана символами Unicode, і автор може уникнути їх, використовуючи, \uxxxxякщо кодування файлу не підтримує символ Unicode, і в цьому випадку він переводиться на відповідний символ. Один з символів Unicode, присутній у цьому випадку, - це \u202E. Це не візуально показано у фрагменті, але якщо ви спробуєте переключити кодування браузера, можуть з’явитися приховані символи.

Тому лексичний переклад призводить до декларування класу:

class M\u202E{

що означає, що ідентифікатор класу M\u202E. Специфікація розглядає це як дійсні ідентіфіктор:

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

"Буква чи цифра Java" - символ, для якого метод Character.isJavaIdentifierPart(int)повертає значення true.


Вибачте, але це зворотно (призначено каламбур). У вихідному коді немає виходу; ви описуєте, як це могло бути написано. І, вона компілюється в клас під назвою "M" (лише один символ).
Том Блоджет

@TomBlodget Дійсно, але суть (яку насправді я підкреслив у цитаті специфікацій) полягає в тому, що компілятор також може обробляти необроблені символи Unicode. Це справді все пояснення. Переклад евакуації - це лише додаткова інформація, не пов’язана безпосередньо з цією справою. Що стосується компільованого класу, я думаю, що це тому, що компілятор якось відкидає символ RTL-комутатора. Я спробую побачити, чи очікується це, але, думаю, це відбудеться після фази лексичного перекладу.
M Anouti
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.