Как процессор понимает язык программирования

Обновлено: 21.11.2024

Переключатели имеют абстрактное значение, присвоенное им людьми. Точно так же, как наши системы счисления используют позиционную запись в десятичном формате (основание 10). Компьютеры, использующие двоичный код, имеют ряды последовательных переключателей, которые представляют числа в двоичном формате (с основанием 2)

Каждая позиция – это N-я степень основания (десятичного, двоичного или восьмеричного и шестнадцатеричного)

3 2 1 0 (степень - позиция разряда)
(0-9) (0-9) (0-9) (0-9) десятичный (символы по основанию N-1, т.е. 10 - 1 = 9)

[7 x 10 в степени 3 (1000) = 7000] + 0 + 0 + [ 3 x 10 в степени 0 (1) = 3 ] = 7003

3 2 1 0 (степень - позиция разряда)
(0-1) (0-1) (0-1) (0-1) двоичный (символы по основанию N-1, т.е. 2 - 1 = 1), поэтому переключатель находится в положении ON или OFF

0 + [1 x 2 в степени 2 (4) = 4] + [1 x 2 в степени 1 (2) = 2] + 0 = 6 десятичных знаков


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

"Это правда, что тяжелая работа еще никого не убивала. Но я думаю, зачем рисковать." - Рональд Рейган

Для этого и нужны машины.

Есть проблема?
Спи на нем.

Извините, но я категорически не согласен. Это как раз одна из вещей, которых я хотел избежать в статье — как это ясно сказано в самой статье (и даже в ее названии), так как ее цель — дать людям некоторое представление о том, как работают процессоры, не перегружая их техническими детали, такие как то, что вы только что предоставили. Я заметил, что когда вы бросаете много этой ерунды многим людям, которые не имеют предварительного понимания темы, это отпугивает их, заставляя их думать, что это «слишком сложно», но если введение мягкое, они всегда могут перейти к больше деталей, как только они хорошо разберутся в теме на более высоком уровне абстракции).

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

Понимать более низкие и более высокие уровни инструкций по программированию. Понимание компиляторов, компиляции, компоновки и развертывания.

На аппаратном уровне компьютеры понимают один язык, называемый машинным языком (также называемый объектным кодом). Это набор инструкций, поддерживаемых аппаратным обеспечением процессора компьютера, и он специфичен для каждого типа процессора. Этот язык объектного кода является числовым по своей природе и выражается в двоичном коде, который представляет собой числовое кодирование, состоящее только из 1 и 0 (основание 2). Программировать в двоичном формате очень утомительно, поэтому были созданы языки более высокого уровня, чтобы упростить создание программ. В языке более высокого уровня вы используете синтаксис, похожий на английский и более простой для понимания, чтобы выразить то, что вы хотите, чтобы компьютер делал. Задачей языка более высокого уровня является перевод этих письменных инструкций в двоичный объектный код для выполнения компьютером. На самом деле, когда программный файл создается на языке более высокого уровня, он будет содержать только двоичные инструкции для компьютера, а не ваш исходный код.

Обычно программист пишет инструкции на выбранном языке более высокого уровня, в нашем случае на Java, и эти инструкции или исходный код хранятся в текстовом виде в файле. Затем этот исходный файл передается программе, называемой компилятором, которая переводит исходный язык в объектный код в двоичной форме и записывает его в другой файл, называемый программой. Примером может служить файл .exe в Windows. Может быть дополнительный шаг, называемый связыванием, который представляет собой процесс объединения инструкций программиста с библиотеками инструкций, созданными кем-то другим. Иногда файл программы может быть отправлен на другой компьютер для выполнения. Это называется развертыванием.

Когда программа запускается на компьютере, операционная система компьютера открывает файл программы, считывает и выполняет найденные двоичные инструкции.

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

В настоящее время большинство программ пишутся на языках высокого уровня, таких как C, Java или Python.Эти языки предназначены больше для людей, чем для машин, поскольку скрывают от программиста некоторые аппаратные детали конкретного компьютера.

Проще говоря, языки высокого уровня упрощают задачу указания компьютеру, что делать. Однако, поскольку компьютеры понимают инструкции только в машинном коде (в форме 1 и 0), мы не можем правильно общаться с ними без какого-либо переводчика.

Вот почему существуют языковые процессоры.

Языковой процессор — это специальная система транслятора, используемая для преобразования программы, написанной на языке высокого уровня, которую мы называем «исходным кодом», в машинный код, который мы называем «объектной программой» или «объектным кодом».< /p>

Для разработки языкового процессора требуется очень точное описание лексики и синтаксиса, а также семантики языка высокого уровня.

Существует три типа языковых процессоров:

  • Ассемблер
  • Переводчик
  • Компилятор

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

Языки ассемблера и ассемблер

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

Каждый мнемонический символ представляет собой код операции или инструкцию, и обычно нам нужно несколько из них вместе, чтобы сделать что-то полезное. Эти инструкции можно использовать для перемещения значений между регистрами (в архитектуре Intel86-64 эта команда будет MOV ), для выполнения основных арифметических операций со значениями, таких как сложение, вычитание, умножение и деление (ADD, SUB, MUL, DIV), а также основные логические операции, такие как сдвиг числа влево или вправо или отрицание ( SHL , SHR , NEG ). Он также может использовать безусловные и условные переходы, которые полезны для реализации цикла for, while или оператора if (JMP, JE, JLE).

Например, если процессор интерпретирует двоичную команду 10110 как "переместить из одного регистра в другой регистр", язык ассемблера заменит ее командой, такой как MOV .

Каждый регистр также имеет двоичный идентификатор, например 000 . Его также можно заменить на более «человеческое» имя, такое как EAX , которое является одним из основных регистров в x86.

Если бы мы, скажем, хотели переместить значение в регистр, машинный код выглядел бы примерно так:

  • 00001: команда перемещения.
  • 000: идентификатор регистра.
  • 00001010: значение, которое мы хотим переместить.

На языке ассемблера это можно записать примерно так:

  • MOV — это команда перемещения.
  • EAX — идентификатор реестра.
  • A – это шестнадцатеричное значение, которое мы хотим переместить (10 в десятичном формате).

Если бы мы захотели записать простое выражение EAX = 7 + 4 - 2 в машинном коде, оно выглядело бы примерно так:

  • 00001 — команда "переместить".
  • 00010 — это команда «добавить».
  • 00011 – команда "вычитания".
  • 000, 001 — идентификаторы регистров
  • 00000111, 00000100, 00000010 — целые значения, которые мы используем в этих выражениях.

В ассемблере эта группа двоичных чисел будет записана так:

  • MOV — это команда перемещения.
  • ADD – это команда добавления.
  • SUB — это команда вычитания.
  • EAX , R8 , R9 — идентификаторы регистров
  • 7, 4, 2: целые числа, которые мы используем в этих выражениях.

Хотя он по-прежнему не так удобочитаем, как язык высокого уровня, он все же гораздо более удобочитаем, чем двоичная команда. Аппаратные компоненты процессора и регистров гораздо более абстрактны.

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

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

Интерпретируемые языки и интерпретатор

Каждая программа имеет этапы перевода и этапа выполнения. В интерпретируемых языках эти две фазы переплетаются — инструкции, написанные на языке программирования высокого уровня, выполняются напрямую без предварительного преобразования в объектный код или машинный код.

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

Интерпретаторы используются с 1952 года, и их задача заключалась в упрощении программирования в рамках ограничений компьютеров того времени (например, в первом поколении компьютеров было значительно меньше места для хранения данных, чем сейчас). Первым интерпретируемым языком высокого уровня был Lisp, впервые реализованный в 1958 году на компьютере IBM704.

Бесплатная электронная книга: Git Essentials

Ознакомьтесь с нашим практическим руководством по изучению Git, включающим передовые практики, общепринятые стандарты и памятку. Перестаньте гуглить команды Git и на самом деле изучите их!

В настоящее время наиболее распространенными интерпретируемыми языками программирования являются Python, Perl и Ruby.

Компилируемые языки и компилятор

В отличие от интерпретируемых языков программирования, фаза перевода и фаза выполнения в скомпилированных языках программирования полностью разделены, а перевод выполняется компилятором.

Компилятор — это языковой процессор, который считывает весь исходный код, написанный на языке высокого уровня, и переводит его в эквивалентный объектный код как единое целое. Обычно этот объектный код хранится в файле. При наличии ошибок в исходном коде компилятор указывает их в конце компиляции вместе со строками, в которых обнаружены ошибки. После их удаления исходный код можно перекомпилировать.

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

Первым языком программирования высокого уровня был FORTRAN, созданный в 1957 году группой под руководством Джона Бэкуса из IBM.

В настоящее время наиболее распространенными компилируемыми языками являются C++, Rust и Haskell.

Языки байт-кода

Языки байт-кода, также называемые "переносимым кодом" или "языками p-кода", относятся к типу языков программирования, подпадающих под категории оба. em> интерпретируемые и компилируемые языки, поскольку они используют как компиляцию, так и интерпретацию при переводе и выполнении кода.

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

Байт-код универсален и может быть передан в скомпилированном виде на другие устройства (со всеми преимуществами скомпилированного кода). Затем ЦП преобразует его в специальный машинный код для устройства. При этом вы можете скомпилировать исходный код один раз и запускать его везде — при условии, что на устройстве есть еще один уровень, который используется для преобразования байт-кода в машинный код.

Самой известной виртуальной машиной для интерпретации байт-кода является Виртуальная машина Java (JVM), которая настолько распространена, что на ней созданы реализации для нескольких языков.

При первом запуске программы на языке байт-кода происходит задержка, пока код компилируется в байт-код, но скорость выполнения значительно увеличивается по сравнению со стандартными языками интерпретации (поскольку исходный код оптимизирован для интерпретатора).< /p>

Одним из самых больших преимуществ языков байт-кода является их независимость от платформы, которая раньше была характерна только для интерпретируемых языков, а программы намного быстрее, чем обычные интерпретируемые языки, когда дело доходит до выполнения.

Еще одна вещь, о которой стоит упомянуть, — это компиляция точно в срок (JIT). В отличие от компиляции с опережением времени (AOT), код компилируется во время выполнения. Это существенно повышает скорость компиляции и использует преимущества производительности компиляции с гибкостью интерпретации.

Опять же, динамическая компиляция не всегда должна быть лучше/быстрее, чем статическая компиляция — в основном это зависит от того, над каким проектом вы работаете.

Преимущества и недостатки: компиляция и интерпретация

Производительность

Поскольку компилятор преобразует весь исходный код языка программирования в исполняемый машинный код для ЦП, анализ исходного кода занимает много времени, но после завершения анализа и компиляции общее выполнение выполняется намного быстрее. .

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

Отладка

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

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

Имейте в виду, что на компиляцию крупных проектов могут уйти десятки минут, а иногда даже часы.

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

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

Исходный код и объектный код

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

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

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

Вот почему при передаче программы пользователю достаточно просто отправить ему объектный код, а не исходный код, обычно в виде .exe-файла в Windows.

Интерпретируемый код более восприимчив к атакам путем внедрения кода, а тот факт, что он не проверяется на тип, приводит нас к совершенно новому набору программных исключений и ошибок.

Заключение

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

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

Компьютер понимает один язык, называемый машинным кодом. Этот код, состоящий из единиц и нулей, сгруппированных в команды, называемые инструкциями, непосредственно соответствует электрическим сигналам в ЦП. Первое программное обеспечение было «написано» непосредственно в этих единицах и нулях, запрограммировано через переключатели, перфокарты и проводку.

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

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

Тогда у вас есть компилируемые языки "высокого уровня", такие как C. Как и ассемблер, код C должен быть преобразован в машинный код, прежде чем его можно будет запустить. Программа, называемая компилятором, преобразует C в машинный код. Современные компиляторы C, такие как gcc, обычно написаны на C, но первый компилятор C должен был быть написан на чем-то другом, например на ассемблере (собранном в машинный код). Пример:

Наконец, у вас есть интерпретируемые языки, такие как Perl. Интерпретируемый язык не компилируется. Вместо этого у вас есть программа, называемая интерпретатором. Интерпретатор Perl — это программа на C (скомпилированная в машинный код), которая принимает текст сценария Perl в качестве входных данных, анализирует сценарий и выполняет его операции. Другими словами, на самом деле всю работу выполняет интерпретатор. В большинстве операционных систем есть способ указать, что скрипт на интерпретируемом языке — это программа, которую можно запустить, вызвав интерпретатор. В UNIX вы используете "шебанг", который является первой строкой следующего примера:

Shebang сообщает UNIX, что этот файл является интерпретируемой программой, и он запускается, передавая его в качестве входных данных для исполняемого файла, расположенного в /usr/bin/perl .

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

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

В процессоре есть множество схем, которые выполняют сложение, умножение, сдвиг, загрузку, сохранение и многое другое. Когда процессор может выполнять любое из этих действий с любыми данными, находящимися в его регистрах, вы должны иметь возможность указать процессору, что делать. Таким образом, вы не только помещаете переменные A и B в соответствующие регистры, но и код операции определяет, какое действие должно быть выполнено с этими входными данными. Документация процессора расскажет вам о том, какие побочные эффекты будут возникать и как их интерпретировать, а также о том, куда направляется ваш результат (какой-то выходной регистр).

Assembly — это интерпретатор языка низкого уровня, который может преобразовывать список операторов одного процессора и преобразовывать его в фактическое двоичное представление, отформатированное в соответствии с ожиданиями процессора. Двоичный формат важен, потому что мы говорим об отдельных проводах в цепях, которые будут под напряжением или нет, что логически представлено 1 или 0.

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

Компиляторы – это программы, которые используют некоторый формальный язык в качестве входных данных и могут абстрагироваться от символов и грамматики и сводить их к языку ассемблера или непосредственно к двоичному коду, который мы называем машинным языком. Если вы ориентируетесь на язык ассемблера, то позже вы можете передать эту сборку через ассемблер, ориентированный на любой тип компьютерного процессора, и получить одну и ту же программу для любой цели!

Эти правила, которым следует компилятор, концептуально просты, и сами языки концептуально просты; многие вещи, допустимые в естественном языке, например двусмысленности, не допускаются. Первыми были FORTRAN и Lisp, и они были написаны на ассемблере. В наши дни гораздо проще написать языковые компиляторы на основном языке высокого уровня (это называется начальной загрузкой), а затем снова написать компилятор на целевом языке. Первоначально Python был написан на C, а теперь у нас есть компилятор Python под названием PyPy, который был написан на Python, использует Python в качестве входных данных и выводит программу. Если я вспомню свою историю, я думаю, что язык C был сначала написан на B.

Существует целая отрасль компьютерных наук для разбора языка, и есть множество инструментов (LEX, YACC, BYSON, классика, которые приходят на ум), где вы можете указать грамматику, и эти программы выведут программы, которые могут читать входные данные, соответствующие установленным вами языковым правилам. На выходе получается абстрактное синтаксическое дерево, которое затем сводится к программе.

Читайте также: