Найти в Дзене
ZDG

Языки программирования: загибают пальцы за нас

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

Вернемся к простейшей задаче: нужно сохранить в памяти число 5.

Как я бы действовал, если бы не было вообще никаких языков программирования? Мне бы, типа, сказали: вот тебе компьютерная память. Она уже частично чем-то занята, там не твоё, не трогай. Вот тут, начиная с адреса 100 – можешь ею пользоваться.

Вообще говоря, я мог бы прямо своими руками менять данные в памяти, буквально трогать её. Это если бы память была в виде тумблеров или кнопок (кстати, она такая и была).

Один из первых персональных компьютеров с памятью, которую можно менять руками.
Один из первых персональных компьютеров с памятью, которую можно менять руками.

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

Я хочу дать процессору такую команду: "запиши в байт по адресу 100 число 5". Как нетрудно догадаться, команды для процессора – это тоже числа, потому что в компьютере нет ничего, кроме чисел. То есть команду, которую я написал текстом, нужно специальным образом закодировать в определенное число, в котором будет содержаться и сама операция "запиши", и адрес 100, и пояснение, что нужен именно байт, и моё число 5. И только после этого такую команду поймет и выполнит процессор.

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

Команды для процессора пришлось бы писать в таком виде.
Команды для процессора пришлось бы писать в таком виде.

Языки программирования призваны облегчить эту задачу. Но нужно понимать: всё, что мы пишем и видим на языке программирования – не существует и не выполняется так, как написано. Это не прямые инструкции для процессора, это всего лишь текст для кого-то другого, кто умеет переводить его в машинный код. И этот кто-то другой прочитает этот текст и создаст машинный код, который уже и будет исполнен процессором. Этот кто-то другой называется транслятор (то есть переводчик), но пока это всё, что нужно знать.

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

mov a, 5
...
a db 0

Что делает транслятор языка ассемблера, когда получает такой текст?

"Ага, я вижу, что ты написал здесь mov a, 5. Это значит записать (mov), куда записать? В что-то под названием "a". Что записать? 5. Да без проблем, сейчас составлю машинную инструкцию... Стоп, а что такое "a"? А вот, вижу, ты дальше выделил один байт памяти (db – data byte) и назначил ему метку "a". Я сам вычислю адрес этого байта памяти, не парься. И теперь везде, где ты пишешь "a", я буду знать, что ты имеешь в виду этот адрес. Короче, вот твоя готовая машинная инструкция."

Разница – уже колоссальная. Во-первых, мы можем писать инструкции для процессора в более понятном человеку виде: mov a, 5. Если не совсем понятно, то mov это от слова move, "переместить". Во-вторых, мы можем писать число 5 в привычной десятичной системе, а не в двоичной. В-третьих, мы можем назначать адресам любые человеческие метки (a, b, cat, dog, Eyjafjallajokull и т.п.). В-четвертых, для сохранения чего-либо не нужно даже знать адрес. Нужно только застолбить место (db) и присвоить ему метку, а транслятор сам вычислит, какой там получится адрес.

Писать программы мгновенно стало в тысячи раз легче.

Посмотрим на более развитый язык, C:

char a = 5;

Что делает транслятор языка C:

"Вижу, что ты хочешь зарезервировать один байт памяти. Потому что ты написал char (сокращение от character, т.е. символ), а это символьный тип, то есть 1 байт. Ты дал этому байту метку "a" и хочешь записать туда 5. Всё ясно, я сам зарезервирую 1 байт в свободной памяти и буду знать, что у него метка "а", поэтому везде, где ты напишешь "a", я буду обращаться к этому байту. Вот твоя готовая машинная инструкция."

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

Посмотрим на JavaScript:

var a = 5;

Что делает транслятор языка JavaScript:

"Вижу, что ты хочешь создать в памяти новую переменную (var – variable) c меткой "a". Какого она типа, сколько байт занимает? Неважно, потом разберусь. А, вот вижу, что ты хочешь записать туда 5. Это целое число, тогда я буду считать, что это переменная целочисленного типа. Значит, выделю на неё... ВОСЕМЬ байт."

Отличие от предыдущих языков здесь в том, что JavaScript не только резервирует для нас память, но и самостоятельно угадывает, какой у нее должен быть тип. Но, как видно, мы уже немного потеряли контроль над памятью, так как вместо 1 байта выделилось 8. Почему? Потому что он заранее не знает, какие ещё числа вы туда захотите записать, и поэтому выделит памяти по максимуму. Это одна из проблем "легких" языков программирования, которые делают всё за нас.

– Вы и пальцы за меня загибать будете?
– Агааа!
– Вы и пальцы за меня загибать будете? – Агааа!

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

BASIC:
LET A = 5

Pascal:
var a: byte;
a := 5;

Python:
a = 5

ActionScript:
var a:Number = 5;

PHP, PERL:
$a = 5;

Go:
var a byte = 5

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