Перейти к содержанию

Абстрактный Компьютер: Зачем нам это нужно?

Прежде чем изучать любой язык программирования, первая и самая важная идея, которую нужно иметь в виду, состоит в том, что, за исключением крайне ограниченных случаев, разработчики всегда программируют на базе абстракций компьютеров и никогда не ориентируются на конкретное компьютерное оборудование. Если вы программируете на высокоуровневых языках, таких как Python, вы ориентируетесь на абстрактные компьютеры; если вы программируете на низкоуровневых языках, таких как C, вы ориентируетесь на абстрактные компьютеры; даже если вы пишете машинный код напрямую (машинный код представляет собой двоичные последовательности вроде 00111001010100111, которые, хотя и едва читаемы для людей, могут выполняться на компьютерном оборудовании напрямую), вы все равно ориентируетесь на абстрактные компьютеры.

Почему так происходит? Зачем нам дополнительный уровень абстракции? Не было бы проще программировать непосредственно для оборудования? Существуют две рациональности, лежащие в основе абстрактных компьютеров, о которых я объясню ниже.

Необходимость стандартизации

Как показано на примере архитектуры набора инструкций (ISA) (Instruction Set Architecture) приведенном в предыдущем модуле, наличие общего уровня абстракции (стандарта) на разных компьютерах позволяет одному и тому же программному обеспечению работать на разных аппаратных средствах, а также разному программному обеспечению работать на одном и том же аппаратном обеспечении. То есть, значение абстракции заключается в ее совместном использовании на разных компьютерах и программных приложениях.

Без такой общей абстракции, программные приложения направлены непосредственно на определенное аппаратное обеспечение, что означает, что одно программное приложение может работать только на одном типе оборудования, например, на конкретной модели ЦП, такой как Intel Core i7 12700H. Если мы хотим поддерживать разные компьютеры, нам придется написать программное обеспечение для каждого из них. Наоборот, компьютер может выполнять только программные приложения, которые конкретно нацелены на него. Как видите, без общей абстракции разработка программного обеспечения крайне сложна и непродуктивна, а функциональность компьютеров крайне ограничена.

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

%%{init: { "flowchart": { "curve": "linear" } } }%%
graph TD

Программа-1 --> ISA
Программа-2 --> ISA
Программа-3 --> ISA

ISA --> Компьютер-1
ISA --> Компьютер-2
ISA --> Компьютер-3

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

Абстракция упрощает программирование

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

push   %rbp
mov    %rsp,%rbp
lea    0xe4c(%rip),%rax
mov    %rax,%rsi
lea    0x2e7e(%rip),%rax
mov    %rax,%rdi
call   1090 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
mov    0x2dff(%rip),%rdx
mov    %rdx,%rsi
mov    %rax,%rdi
call   10a0 <_ZNSolsEPFRSoS_E@plt>
mov    $0x0,%eax
pop    %rbp
ret

(полная дизассемблированная версия опущена для краткости, это показывает только наиболее важную часть кода)

Функция вышеуказанного кода - вывести "Hello World" на экран вашего компьютера, но вам не нужно понимать, как она это делает. Главное, что нужно запомнить, состоит в том, что программирование под ISA напрямую противоречит логике человека.

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

#include <iostream>

int main() {
    std::cout << "Hello World" << std::endl;
    return 0;
}

А код, выполняющий то же самое, в языке Python, являющемся более высокоуровневым языком программирования, будет выглядеть так:

print("Hello World")

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

Поздравляю! Вы узнали о абстрактных компьютерах и об их рациональности. Далее мы рассмотрим наименьшую абстракцию компьютеров, которая распространяется на все архитектуры набора инструкций (ISA).