Запись компилятора на его собственном языке

В Java все переменные, которые вы объявляете, на самом деле являются «ссылками» на объекты (или примитивы), а не самими объектами.

При попытке выполнить один метод объекта , ссылка просит живой объект выполнить этот метод. Но если ссылка ссылается на NULL (ничего, нуль, void, nada), то нет способа, которым метод будет выполнен. Тогда runtime сообщит вам об этом, выбросив исключение NullPointerException.

Ваша ссылка «указывает» на нуль, таким образом, «Null -> Pointer».

Объект живет в памяти виртуальной машины пространство и единственный способ доступа к нему - использовать ссылки this. Возьмем этот пример:

public class Some {
    private int id;
    public int getId(){
        return this.id;
    }
    public setId( int newId ) {
        this.id = newId;
    }
}

И в другом месте вашего кода:

Some reference = new Some();    // Point to a new object of type Some()
Some otherReference = null;     // Initiallly this points to NULL

reference.setId( 1 );           // Execute setId method, now private var id is 1

System.out.println( reference.getId() ); // Prints 1 to the console

otherReference = reference      // Now they both point to the only object.

reference = null;               // "reference" now point to null.

// But "otherReference" still point to the "real" object so this print 1 too...
System.out.println( otherReference.getId() );

// Guess what will happen
System.out.println( reference.getId() ); // :S Throws NullPointerException because "reference" is pointing to NULL remember...

Это важно знать - когда больше нет ссылок на объект (в пример выше, когда reference и otherReference оба указывают на null), тогда объект «недоступен». Мы не можем работать с ним, поэтому этот объект готов к сбору мусора, и в какой-то момент VM освободит память, используемую этим объектом, и выделит другую.

196
задан nbro 1 August 2017 в 07:23
поделиться

11 ответов

Это называют, "загружаясь". Необходимо сначала создать компилятор (или интерпретатор) для языка на некотором другом языке (обычно Java или C). Как только это сделано, можно записать новую версию компилятора на языке Foo. Вы используете первый компилятор начальной загрузки, чтобы скомпилировать компилятор, и затем использовать этот скомпилированный компилятор для компиляции всего остального (включая будущие версии себя).

Большинство языков действительно создается этим способом, частично потому что разработчикам языка нравится использовать язык, который они создают, и также потому что нетривиальный компилятор часто служит полезным сравнительным тестом для того, насколько "завершенный" язык может быть.

примером этого был бы Scala. Его первый компилятор был создан в Пицце, экспериментальном языке Martin Odersky. С версии 2.0 компилятор был полностью переписан в Scala. От той точки на мог быть полностью отброшен старый компилятор Пиццы, вследствие того, что новый компилятор Scala мог использоваться для компиляции себя для будущих повторений.

225
ответ дан Graham Borland 23 November 2019 в 05:18
поделиться

Я вспоминаю слушание подкаст Радио Разработки программного обеспечения , где Dick Gabriel говорил о начальной загрузке исходного интерпретатора LISP путем записи базовой версии в LISP на бумаге и рука, собирающая его в машинный код. С тех пор остальная часть функций LISP была и записана в и интерпретирована с LISP.

74
ответ дан nbro 23 November 2019 в 05:18
поделиться

Когда Вы пишете свой первый компилятор для C, Вы пишете это на некотором другом языке. Теперь, у Вас есть компилятор для C в, скажем, ассемблере. В конечном счете Вы приедете в место, где необходимо проанализировать строки, специфически escape-последовательности. Вы напишете код для преобразования \n в символ с десятичным кодом 10 (и \r к 13, и т.д.).

После того, как тот компилятор готов, Вы начнете повторно реализовывать его в C. Этот процесс называют" начальная загрузка ".

строковый код парсинга станет:

...
if (c == 92) { // backslash
    c = getc();
    if (c == 110) { // n
        return 10;
    } else if (c == 92) { // another backslash
        return 92;
    } else {
        ...
    }
}
...

, Когда это компилирует, у Вас есть двоичный файл, который понимает '\n'. Это означает, что можно изменить исходный код:

...
if (c == '\\') {
    c = getc();
    if (c == 'n') {
        return '\n';
    } else if (c == '\\') {
        return '\\';
    } else {
        ...
    }
}
...

Поэтому, где информация то, что '\n' является кодом для 13? Это находится в двоичном файле! Это похоже на DNA: Компиляция C исходный код с этим двоичным файлом наследует эту информацию. Если компилятор скомпилирует себя, то он передаст это знание своим потомкам. С этого момента нет никакого способа видеть из одного только источника, что сделает компилятор.

, Если Вы хотите скрыть вирус в источнике некоторой программы, можно сделать это как это: Получите источник компилятора, найдите функцию, которая компилирует функции, и замените его этим:

void compileFunction(char * name, char * filename, char * code) {
    if (strcmp("compileFunction", name) == 0 && strcmp("compile.c", filename) == 0) {
        code = A;
    } else if (strcmp("xxx", name) == 0 && strcmp("yyy.c", filename) == 0) {
        code = B;
    }

    ... code to compile the function body from the string in "code" ...
}

интересные части являются A и B. A является исходным кодом для compileFunction включая вирус, вероятно, зашифрованный в некотором роде, таким образом, это не очевидно из поиска получающегося двоичного файла. Это удостоверяется, что компиляция в компилятор с собой сохранит вирусный код инжекции.

B является тем же для функции, которую мы хотим заменить нашим вирусом. Например, это мог быть функциональный "вход в систему" в исходном файле "login.c", который является, вероятно, от ядра Linux. Мы могли заменить его версией, которая примет пароль "joshua" для корневой учетной записи в дополнение к нормальному паролю.

, Если Вы компилируете это и распространяете его как двоичный файл, не будет никакого способа найти вирус путем рассмотрения источника.

первоисточник идеи: https://web.archive.org/web/20070714062657/http://www.acm.org/classics/sep95 /

43
ответ дан beppe9000 23 November 2019 в 05:18
поделиться

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

наименее привилегированное следующее. Вы пишете минимальный компилятор в ассемблере (фу) для минимального набора языка и затем используете тот компилятор для реализования дополнительных опций языка. Создание Вашего пути вплоть до Вас имеет компилятор со всеми функциями языка себя. Болезненный процесс, который обычно только делается, когда у Вас нет никакого другого выбора.

предпочтительный подход должен использовать кросс-компилятор. Вы изменяете бэкэнд существующего компилятора на различной машине для создания вывода, который работает на целевой машине. Тогда у Вас есть хороший полный компилятор и работающий над целевой машиной. Самый популярный для этого язык C, поскольку существует много существующих компиляторов, которые имеют сменные бэкэнды, которые могут быть выгружены.

Немного известного факта - то, что компилятор C++ GNU имеет реализацию, которая использует только подмножество C. Причина, являющаяся им, обычно легка найти компилятор C для новой целевой машины, которая позволяет Вам тогда создавать полный компилятор C++ GNU из него. У Вас есть теперь начальная загрузка, связал себя к наличию компилятора C++ на целевой машине.

18
ответ дан nbro 23 November 2019 в 05:18
поделиться

Добавление любопытства к предыдущим ответам.

Вот кавычка от Linux С нуля руководство на шаге, где каждый начинает создавать компилятор GCC из его источника. (Linux С нуля является способом установить Linux, который радикально отличается от установки распределения, в этом необходимо скомпилировать действительно каждый единственный двоичный файл целевой системы.)

make bootstrap

цель 'начальной загрузки' только компилирует GCC, но несколько раз компилирует его. Это использует программы, скомпилированные в первом раунде для компиляции себя во второй раз, и с другой стороны в третий раз. Это тогда сравнивает эти вторые и третьи компиляции, чтобы удостовериться, что это может размножиться безупречно. Это также подразумевает, что было скомпилировано правильно.

то, Что использование цели 'начальной загрузки' мотивировано тем, что компилятор каждый использует для создания набора инструментальных средств целевой системы, не может иметь той же самой версии целевого компилятора. Продолжение таким образом, которое каждый, несомненно, получит в целевой системе, компилятор, который может скомпилировать себя.

47
ответ дан jub0bs 23 November 2019 в 05:18
поделиться

Обычно у Вас должна быть работа (если примитивный) сокращение компилятора, работающего сначала - тогда можно начать думать о создании его самохостинг. Это на самом деле считают важной вехой в некоторых языках.

Из того, что я помню от "моно", вероятно, что они должны будут добавить несколько вещей к отражению для получения его работа: моно команды продолжают указывать, что некоторые вещи просто не возможны с Reflection.Emit; конечно, команда MS могла бы доказать их неправильно.

Это имеет некоторых реальный преимущества: это - довольно хороший модульный тест для начинающих! И у Вас только есть один язык для волнения о (т.е. возможно, что эксперт C# не мог бы знать много C++; но теперь ваш может зафиксировать компилятор C#). Но интересно, нет ли суммы профессиональной гордости на работе здесь: они просто хотят это саморазместить.

Не совсем компилятор, но я недавно работал над системой, которая является сам хостинг; генератор кода используется для генерации генератора кода... поэтому, если схема изменяется, я просто выполняю его на себе: новая версия. Если существует ошибка, я просто возвращаюсь к более ранней версии и попробовал еще раз. Очень удобный, и очень легкий поддержать.

<час>

Обновление 1

я только что смотрел это видео из Anders в PDC, и (приблизительно час в) он действительно приводит некоторым намного больше допустимых причин - все о компиляторе как услуга. Только для справки.

14
ответ дан nbro 23 November 2019 в 05:18
поделиться

Вот дамп (трудная тема для поиска на, на самом деле):

Это - также идея PyPy и Rubinius:

(я думаю, что это могло бы также относиться Forth, но я ничего не знаю о Forth.)

4
ответ дан Peter Mortensen 23 November 2019 в 05:18
поделиться

GNAT, GNU компилятор Ada, требует, чтобы компилятор Ada был полностью создан. Это может быть болью при портировании его на платформу, где нет никакого легко доступного двоичного файла GNAT.

1
ответ дан David Holm 23 November 2019 в 05:18
поделиться

На самом деле большинство компиляторов записано на языке, который они компилируют по вышеизложенным причинам.

первый компилятор начальной загрузки обычно пишется в C, C++ или блоке.

1
ответ дан nbro 23 November 2019 в 05:18
поделиться

Моно компилятор C# проекта был "саморазмещен" в течение долгого времени теперь, что это означает, то, что это было записано в самом C#.

то, Что я знаю, - то, что компилятор был запущен как чистый код C, но как только "основные" опции ECMA были реализованы, они начали переписывать компилятор в C#.

я не знаю о преимуществах записи компилятора на том же языке, но я уверен, что это должно сделать, по крайней мере, с функциями, которые может предложить сам язык (C, например, не поддерживает объектно-ориентированное программирование).

можно найти больше информации здесь .

1
ответ дан nbro 23 November 2019 в 05:18
поделиться

Возможно, можно записать BNF, описывающий BNF.

0
ответ дан Eugene Yokota 23 November 2019 в 05:18
поделиться
Другие вопросы по тегам:

Похожие вопросы: