Давайте на секунду забудем о виртуальных машинах (я обещаю, что вернемся к тем, что ниже) и начнем с этого важного факта:
C не имеет сборки мусора.
Для языка, обеспечивающего сборку мусора, должна быть какая-то "среда выполнения" / среда выполнения / вещь, которая будет выполнять это.
Вот почему Python, Java и Haskell требуют "runtime" , а C, который не делает этого, может просто скомпилировать нативный код.
Обратите внимание, что psyco был оптимизатором Python, который компилировал код Python в машинный код, однако большая часть этого машинного кода состояла из вызовов функций времени выполнения C-Python, таких как PyImport_AddModule
, PyImport_GetModuleDict
и т. д.
Haskell / GHC находится в лодке, похожей на психо-скомпилированный Python. Int
добавляются как простые машинные инструкции, но более сложные вещи, которые распределяют объекты и т. Д., Вызывают среду выполнения.
Что еще?
C не имеет "исключений"
Если бы мы добавили исключения в C, наш сгенерированный машинный код должен был бы что-то сделать для каждой функции и для каждого вызова функции.
Если мы затем добавим также «замыкания», будет добавлено больше материала.
Теперь, вместо того, чтобы повторять этот шаблонный машинный код в каждой функции, мы могли бы вместо этого вызывать подпроцедуру для выполнения необходимых действий, например, PyErr_Occurred
.
Так что теперь, в основном, каждая исходная строка источника соответствует некоторым вызовам некоторых функций и меньшей уникальной части.
Но если мы делаем так много вещей на оригинальную строку исходного кода, зачем вообще беспокоиться о машинном коде?
Вот идея (кстати, назовем эту идею «Виртуальная машина»).
Давайте представим ваш код Python, например:
def has_no_letters(text):
return text.upper() == text.lower()
В качестве структуры данных в памяти, например:
{ 'func_name': 'has_no_letters',
'num_args': 1,
'kwargs': [],
'codez': [
('get_attr', 'tmp_a', 'arg_0', 'upper'), # tmp_a = arg_0.upper
('func_call', 'tmp_b', 'tmp_a', []), # tmp_b = tmp_a() # tmp_b = arg_0.upper()
('get_attr', 'tmp_c', 'arg_0', 'lower'),
('func_call', 'tmp_d', 'tmp_c', []),
('get_global', 'tmp_e', '=='),
('func_call', 'tmp_f', 'tmp_e', ['tmp_b', 'tmp_d']),
('return', 'tmp_f'),
]
}
Теперь давайте напишем интерпретатор, который выполняет эту структуру данных в памяти.
Давайте обсудим преимущества этого по сравнению с прямыми текстовыми интерпретаторами, а затем преимущества по сравнению с компиляцией в машинный код.
Преимущества виртуальных машин перед прямыми текстовыми интерпретаторами
- Система VM выдает все синтаксические ошибки перед выполнением кода.
- При оценке цикла система ВМ не анализирует исходный код при каждом запуске.
- Создание виртуальной машины быстрее, чем прямой текстовый интерпретатор.
- Таким образом, прямой интерпретатор работает медленнее с длинными именами переменных и быстрее с короткими именами переменных. Это побуждает людей писать дурацкий математический код, такой как
wt(f, d(o, e), s) <= th(i, s) + cr(a, p * d + o)
Преимущества виртуальных машин перед компиляцией в машинный код
- Структура данных в памяти, описывающая программу, или «код ВМ», вероятно, будет гораздо более компактной, чем машинный код, полный шаблонного кода, который снова и снова делает то же самое для каждой исходной строки кода. Это заставит систему виртуальной машины работать быстрее, так как из памяти потребуется меньше «инструкций».
- Создание виртуальной машины гораздо проще, чем создание компилятора для машинного кода. Возможно, вы можете сделать это сейчас, даже не зная никакой сборки / машинного кода.