Небольшая добавка к заметке про 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, ученик еще не знает об объектах. Поэтому в ход идет коробочка, из которой достали сто рублей, добавили и положили обратно.

Повторюсь: кому-то сказанное покажется банальным, но я считаю, о присваивании говорят мало – а тема очень интересная и сложная.