Планировал писать про указатели, но после осмысления материала понял, что надо копать глубже. Чтобы было видно, как создаются проблемы и как они решаются, и что всё это происходит естественным путём, и в результате возникают контексты исполнения, глобальные и локальные переменные и прочее.
Представим, что мы изобретаем простейший компьютер, где может работать только одна программа, и она всегда загружается с самого начала памяти, то есть с адреса 0.
Пусть программа имеет длину 2 килобайта. Значит, адреса памяти с 0 по 2047 будут заняты кодом программы, а с 2048 и до конца памяти будут свободны.
В программе мы оперируем некими данными (пусть это будет число 5), и их надо положить в память на хранение. Но куда именно?
Так как вся память принадлежит нашей программе, мы можем выбрать любую свободную ячейку памяти. Например, с адресом 3000. Но проще и логичнее выбирать их по порядку. Первая свободная ячейка находится по адресу 2048, значит её и выберем.
Тогда на придуманном нами машинном языке напишем:
2048 = 5
По нашей логике это будет означать: запиши число 5 по адресу 2048.
Но как мы отличаем число от адреса? В данном случае адрес слева, а число справа.
Далее, чуть усложняем операцию. Нужно взять данные из адреса 2049 и записать их по адресу 2048. Тогда это должно выглядеть так:
2048 = 2049
Но мы уже договорились, что справа – число, поэтому по адресу 2048 запишутся не данные из адреса 2049, а просто число 2049, что неправильно. Значит, назрела необходимость отличать содержимое адреса от самого адреса. Допустим, мы придумали писать так:
2048 = [2049]
Квадратные скобки будут означать содержимое адреса. Но так как слева у нас тоже содержимое адреса, то теперь можно написать:
[2048] = [2049]
Что будет читаться так: прочитай содержимое адреса 2049 и запиши его в содержимое адреса 2048. Логично? И с числом тоже нет проблем:
[2048] = 5
Массивы
Что, если мы хотим создать переменную-массив, например из 10 элементов? По-прежнему зарезервируем для неё адрес 2048, но также зарезервируем и 9 следующих адресов.
Чтобы получать доступ к элементам массива, например к элементу с индексом 3, будем писать так:
2048[3] = 5
Смотрите: [3] это содержимое по адресу 3, как мы и договаривались. Но только это не абсолютный адрес, а относительный – относительно начала массива, адрес которого записан перед ним. Такая запись эквивалентна:
[2048 + 3] = 5
Актуальный адрес 2051 получается путём сложения абсолютного и относительного адреса.
Имена переменных
Квадратные скобки напоминают о массивах, и это неспроста. Если считать, что вся память это массив с именем memory, то такая запись окажется вполне привычной:
memory[2048] = 5
Память начинается с адреса 0, значит, эквивалентная запись это
0[2048] = 5
Следовательно, когда мы пишем
[2048] = [2049]
Это то же самое, что
0[2048] = 0[2049]
или
[0 + 2048] = [0 + 2049]
Имя memory в нашем случае имеет мета-значение 0. Мы не можем его изменить, это конкретный физический адрес, который просто назван memory.
Таким образом, имена переменных не являются необходимостью и более того, на низком уровне не существуют. Мы всегда оперируем адресами. Например, вместо
a = b + c[3]
мы могли бы писать:
[2048] = [2049] + 2050[3]
Но имена переменных нужны просто потому, чтобы нам было легче.
Что пошло не так?
Массив с именем array вполне укладывается в изобретённую ранее схему:
array[3] = 5
Если имя array соответствует адресу 2048, то получим
2048[3] = 5
Проблем нет. Но если написать так:
foo = bar
То проблемы есть. Эти две переменные – не массивы. Если foo соответствует адресу 2048, а bar адресу 2049, то эквивалентная запись будет:
2048 = 2049
Что со всех сторон некорректно. Запись должна быть такой:
[foo] = [bar]
Что даст нам верный вариант:
[2048] = [2049]
Что делать?
Как видим, в языках программирования используют запись для массивов типа array[3], которая укладывается в логику физической адресации, и запись для обычных (скалярных) переменных типа foo, которая не укладывается. Почему искусственно создана такая разница? Почему нельзя писать [foo], чтобы всё было стройно?
Ответ я вижу только один – для удобства. В коде используется очень много операций типа foo = bar, и практически всегда подразумевается, что это [foo] = [bar]. Поэтому для краткости и чистоты кода скобки опускаются.
Представим иначе: любую скалярную переменную можно считать массивом из одного элемента и писать так:
foo[0] = bar[0]
Часть [0] не влияет ни на что, поэтому её просто опускаем (но она продолжает незримо присутствовать!) и остаётся foo = bar.
В любом случае это некий договор, соглашение, что здесь пишем так, а здесь рыбу заворачивали. Об этом следует помнить, и тогда некоторые вещи будут понятнее.
И это соглашение, как раз в силу своей неконсистентности, в дальнейшем породит новые проблемы.
Читайте дальше: