1) То есть считал строку, скомпилировал - получил двоичный код. Выполнил этот двоичный код.
НЕТ. Считал — выполнил. Выполняется машинный код интерпретатора, а не программы.
3) Байт-код понятен среде/виртуальной машине (.NET, Java), которая компилирует байт-код в двоичный код
В ЧИСТОМ СЛУЧАЕ — НЕТ. Считал команду байт-кода — выполнил. Точно так же выполняется машинный код интерпретатора, а не программы. Что-то вроде:
switch (opcode) {
case OP_ADD: {
auto res = stack.pop() + stack.pop();
stack.push(res);
}
.....
}
Байт-код используется по нескольким причинам.
1. 3 быстрее 1.
2. С расширением языка мы меняем только компиляцию в байт-код. Оптимизация байт-кода, интерпретатор — остаются.
А компиляция в машинный код в 1 и 3 — это так называемая JIT-компиляция. Она может выполняться не всегда. Одни команды могут быть в машинном коде, а другие — интерпретироваться.
Кроме того, байт-код часто используется и в классических компиляторах 2-го типа! Это позволяет делать многоплатформенные коллекции компиляторов — все frontend’ы компилируют в байт-код, затем с байт-кодом делаем какую-то магию вроде оптимизации, отдаём его backend’у, и тот готовит OBJ-файл для нужного процессора.