Найти тему
ZDG

Дед-программист научил адресам, семья дерётся за компьютер

Оглавление

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

Представим, что мы изобретаем простейший компьютер, где может работать только одна программа, и она всегда загружается с самого начала памяти, то есть с адреса 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

-2

Массивы

Что, если мы хотим создать переменную-массив, например из 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.

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

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

Читайте дальше: