О присваивании (2)
Небольшая добавка к заметке про x += 5.
В жизни каждого программиста был момент, когда учитель информатики писал на доске:
x = x + 1,
после чего смотрел на класс – поймут или нет? Класс, разумеется, не понимал и смотрел на учителя: что он несет? В школьной математике переменные неизменяемы: если взялся решать квадратное уравнение или систему уравнений, x не может по ходу дела переобуться во что-то другое. Да, можно подать на вход разные иксы и получить разные игреки. Но одно дело входные данные, а другое – их мутация в полете.
Учитель начинал рассказывать про коробочку, из которой достали сто рублей,
прибавили рубль и положили обратно в коробочку. Что ж, какое-то объяснение это
давало. Чувствуя наше сомнение, учитель откровенно продавливал свою
аргументацию: если написать в Паскале x := x + 1, то икс увеличится. Видите?
Спорить с этим было нельзя, но ясности не добавляло. Класс принимал объяснение как данность, но знаете что? Где-то глубоко во мне сидела мысль, что учитель сам не понимает, как это работает. Да, он знает, какой получится результат, но не понимает истинных причин. Просто потому, что когда что-то знаешь, объяснение простое.
Если бы меня попросили объяснить, как работает x = x + 5, я бы ответил так.
В языках высокого уровня мы работаем с переменными: x, y и так далее. Этот
код становится машинными командами, а в них никаких переменных нет. Лучше всего
это видно на примере ассемблера. В ассемблере нет переменных – только
адреса. Для удобства некоторым адресам можно сопоставить метку – логическое
имя. Меткой может быть что угодно: переменная, точка входа в процедуру, место,
куда нужно совершить условный переход и так далее.
Выше я написал “переменная”, но это не совсем верно: в ассемблере нет переменных. Мы резервируем память и одновременно добавляем к ней метку. Если метку убрать, программа скомпилируется и будет работать, просто мы не сможем сослаться на эту память – или сможем, но косвенно, отталкиваясь от других меток.
Таким образом, метка – это адрес, просто именованный. Метка представляет собой число, и к нему можно что-то прибавить, чтобы сместиться в памяти. Если же взять метку в квадратные скобки, получим значение по указанному адресу. В этом и состоит двойственная природа метки: это адрес (число), однако можно либо взять ее значение по адресу, либо поместить значение по этому адресу.
Если предположить, что в ассемблерном коде есть метки a, b и c, то
выражение a = b + c выглядит так (пишу по памяти):
mov eax [b] ; поместить в eax ЗНАЧЕНИЕ b
add eax [c] ; добавить к eax ЗНАЧЕНИЕ c
move a eax ; поместить ПО АДРЕСУ a значение eax
Видим, что в случае с “переменными” b и c мы использовали оператор [],
чтобы получить значение. А когда мы “присваиваем” результат в a, квадратные
скобки не требуются – метка используется как адрес.
Вот почему выражение a = b + c следует читать так: в адрес a заносится
выражение, полученное как… Важно, что не “a становится” чем то, а именно “в
адрес a” заносится то-то и то-то. Большая разница.
Язык Си предлагает указатели – считай, именованные адреса с той же
семантикой. Мы вправе работать с ними как вздумается: использовать как числа для
адресной арифметики или получать из них значения. Указатели – непростая тема, но
по крайней мере здесь есть какой-то контроль. В выражении x = x + 5 этого
контроля нет. Как я уже говорил, слева от присваивания стоит адрес, а если это
что-то сложное вроде items[get_index(foo)], оно вычисляется, чтобы получить
этот адрес.
Таким образом, переменная в Си — это тоже метка, и компилятор трактует ее по
ситуации: иногда как значение, а иногда как адрес. Единственный язык, где адрес
и значение не перемешиваются — это ассемблер. В нем foo — это всегда адрес, а
[foo] — значение по адресу.
Как ни странно, присваивание неплохо ложится на объектную модель. Как мы
выяснили, переменная – это тройка адрес-тип-значение. Ее операции: дай адрес,
дай значение по адресу, помести значение по адресу. Можно представить переменную
объектом с методами get и set. В этом случае выражение a = b + c мы бы
записали так:
a.set(b.get() + c.get())
Или, если речь идет об x = x + 1:
x.set(x.get() + 1)
Другое дело, что на тот момент, когда на доске впервые записано x = x + 1,
ученик еще не знает об объектах. Поэтому в ход идет коробочка, из которой
достали сто рублей, добавили и положили обратно.
Повторюсь: кому-то сказанное покажется банальным, но я считаю, о присваивании говорят мало – а тема очень интересная и сложная.
Нашли ошибку? Выделите мышкой и нажмите Ctrl/⌘+Enter