Найти тему
progblog progblog

000. Что такое программирование вообще

С чего обычно начинается любой курс по обучению программированию? Автор выбирает язык программирования, который ему нравится, и с места в карьер начинает рассказывать про конструкции этого языка, операторы, классы и так далее. Человек с нулевым опытом программирования реагирует на это примерно так:

что за хрень?
что за хрень?

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

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

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

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

Для начала давайте поймём, что такое бит, байты и так далее. Бит - это такая штука, которая равна либо единице, либо нулю. Ничего больше. Вообще ничего.

Байт это уже упорядоченный набор из восьми битов. Некоторые утверждают, что байт это число из 8 бит. Это не так. Два байта подряд тоже могут быть числом. И четыре, и шесть, и так далее. Числом байт или их множество делает их интерпретация как число. Вы также можете интерпретировать несколько битов как число, причем таких интерпретаций может быть много. Например 11111101 как число со знаком равно -2, а этот же набор битов как число без знака равен 253.

То есть: бит это 0 или 1. Байт это просто 8 битов подряд. Число - это то, как мы воспринимаем какую-либо последовательность битов или байтов. Больше нам пока знать ничего не надо.

Теперь перейдем к тому, как эти байты и биты обрабатываются. Если убрать всё лишнее и ненужное для понимания, то архитектура начинки вашего компьютера выглядит как-то так:

-2

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

Делает процессор это с помощью такой штуки как "шина". Шина - это такая магистраль из кучи проводов. Часть из них используется для передачи битов "адресов", часть - для передачи битов "данных" и часть - для того чтоб процессор мог сказать, чего он в данный момент времени вообще хочет. Это тоже провода с битами. Все устройства умеют как-то договариваться, кто "занимает" шину и передает команды. Как - нам сейчас не важно.

Что такое адрес? Это какое-то число, состоящее из битов. У памяти есть свои адреса, на которые она отзывается, и у устройств тоже есть свои адреса. Адреса разных устройств, а также устройств и памяти не пересекаются, то есть не бывает так, чтоб на один адрес отозвались несколько устройств или устройство и память. Но при этом может быть так что некоторые адреса ничейные: никто на них не отзывается. Прочитать по таким адресам можно что угодно: либо нули, либо случайные данные, как повезет. Записать туда ничего не получится: операция просто будет проигнорирована, а процессор ничего не заметит.

Если происходит запись не одного а нескольких байт, то они пишутся в последовательные адреса. Например, записывая байты АBCD по адресу 3072, вы запишете байт А по адресу 3072, байт B по адресу 3073, байт C по адресу 3074, и байт D по адресу 3075.

Если у вас в компьютере установлен гигабайт памяти, то он будет отзываться на адреса от "нуля" до "(2 в степени 30) минус 1". Минус один - потому что счет идет от нуля, например: 4 элемента имеют номера 0,1,2,3.

Теперь, рассмотрим устройство процессора.

В процессоре есть регистры и рабочие блоки. Что такое регистр: это последовательность бит длиной например 8, 16, 32 и 77 бит. Помним, что содержимое этих бит - не обязательно число. Это может быть команда, которую процессор сейчас выполняет. У регистра нет адреса, но есть некоторое "имя", по которому к нему можно "обратиться". У некоторых регистров есть имена, по которым можно обратиться к части его битов. Например в 64битных процессорах x86(это Интел, АМД) есть регистр RAX длиной 64 бита. К младшим 32битам(с номерам от 0 до 31) можно обратиться по имени EAX, а младшим 16 битам - по имени АХ, и к самым младшим 8ми - по имени AL. Регистры обычно бывают такие:

  • ProgramCounter: адрес команды которую сейчас надо выполить.
  • Регистры с данными: они содержат столько же бит, сколькобитен ваш процессор. На 32х-битном процессоре они размером 32 бита, на 64х-битном - 64 бита. Их может быть 8, 16, 32, иногда 64. Регистров много не бывает.
  • Регистр флажков: флажками их назвали американцы, а по-русски правильно называть их отметками. Каждый бит в этом регистре - это "отметка" о чём-либо, и у него помимо номера есть отдельное имя. Например регистр FLAGS в процессорах Интел содержит отметки: Z(последняя комадна дала число равное нулю), С(последняя команда дала число, которое не уместилось в регистр) и т.д. Эти отметки устанавливаются сами в процессе выполнения команд.
  • StackPointer - адрес стека. На самом деле он входит в список "регистров с данными", но используется в ряде команд хитрым способом.

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

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

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

Обращаются к регистрам рабочие блоки процессора. Каждый "герц" из своих гигагерцев процессор совершает операции. В общих чертах "обычный день" процессора выглядит так:

Процессор смотрит в регистр PC(program counter) где в памяти лежит команда которую надо выполнить. Дальше они обращается к шине и просит прочитать ему байт по этому адресу. Команда может быть длиннее одного байта, поэтому процессор будет продолжать запросы к шине, увеличивая адрес на единицу, пока не считает всю команду.

Дальше он начнет команду выполнять: как-то интерпретирует её биты, и отдает команды рабочим блокам. Команды могут быть такие:

  • Взять содержимое регистра AAA, взять содержимое регистра BBB, и, интерпретируя их как целые числа без знака, сложить, а результат положить в регистр CCC. А также установить регистр "отметок" согласно свойствам полученного результата
  • Взять содержимое регистра отметок, проверить там признак Х и если он равен единице, записать в PC какое-то указанное в команде число. Либо прибавить его к PC.
  • вычесть из регистра "указателя стека" число 8(для 64битной машины - 8байт=64 бита). затем по адресу, равному содержимому "указателю стека" записать в память содержимое регистра PC для следующей за текущей команды. затем записать в регистр PC число,указанное в команде. Это в общем-то вызов процедуры.
  • Прочитать или записать содержимое регистра ААА по адресу, равному (содержимому регистра BBB плюс 4*содержимое регистра CCC+плюс восемь). Так кстати можно обращаться и к стеку.
  • Ничего не делать. Такая команда тоже есть.
  • И так далее.

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

Зачем мы это всё прочитали? А затем, что любой язык программирования несет в себе печать вот этого развития технологий. Во всех языках программирования используется стек для вызова процедур, в них есть переменные, которые лежат где-то в памяти либо в стеке, либо в регистрах. Переменные численного типа у них всех размером 8, 16, 32 или 64 бита. Во всех языках так или иначе есть указатели - это такие числа, которые указывают куда-то в память где, по относительным от этого указателя адресам лежат другие переменные и так далее. Не может быть нормального ненаркоманского языка программирования, который игнорирует железо на котором он работает.