Когда компьютер хранит переменную, когда программе необходимо получить значение переменной, как компьютер узнает, где искать в памяти значение этой переменной?
compilers
memory-access
variable-binding
MCMastery
источник
источник
Ответы:
Я бы посоветовал вам заглянуть в удивительный мир компиляции! Ответ в том, что это немного сложный процесс.
Чтобы попытаться дать вам интуицию, помните, что имена переменных существуют исключительно ради программиста. В конце концов компьютер превратит все в адреса.
Локальные переменные (как правило) хранятся в стеке, то есть они являются частью структуры данных, представляющей вызов функции. Мы можем определить полный список переменных, которые функция (возможно) будет использовать, посмотрев на эту функцию, чтобы компилятор мог видеть, сколько переменных ей нужно для этой функции и сколько места занимает каждая переменная.
Есть немного магии, называемой указателем стека, который является регистром, который всегда хранит адрес того места, где начинается текущий стек.
Каждой переменной присваивается «смещение стека», в котором она хранится в стеке. Затем, когда программе требуется доступ к переменной
x
, компилятор заменяетсяx
наSTACK_POINTER + x_offset
, чтобы получить фактическое физическое место, которое она хранит в памяти.Обратите внимание, что именно поэтому вы получаете указатель назад, когда используете
malloc
илиnew
в C или C ++. Вы не можете определить, где именно в памяти находится выделенное кучей значение, поэтому вам нужно сохранить указатель на него. Этот указатель будет в стеке, но он будет указывать на кучу.Детали обновления стеков для вызовов и возвратов функций сложны, поэтому я бы порекомендовал «Книгу дракона» или «Книгу тигра», если вам интересно.
источник
Программа говорит это. В компьютерах изначально нет понятия «переменные» - это язык высокого уровня!
Вот программа на C:
и вот код сборки, в который он компилируется: (комментарии начинаются с
;
)Для "int a = 1;" CPU видит инструкцию «сохранить значение 1 по адресу (значение регистра rbp, минус 4)». Он знает, где хранить значение 1, потому что программа сообщает это.
Аналогично, следующая инструкция говорит: «загрузить значение по адресу (значение регистра rbp, минус 4) в регистр eax». Компьютер не должен знать о таких вещах, как переменные.
источник
%rsp
указатель стека процессора.%rbp
регистр, который ссылается на бит стека, используемый текущей функцией. Использование двух регистров упрощает отладку.Когда компилятор или интерпретатор встречает объявление переменной, он решает, какой адрес он будет использовать для хранения этой переменной, а затем записывает адрес в таблицу символов. Когда встречаются последующие ссылки на эту переменную, адрес из таблицы символов заменяется.
Адрес, записанный в таблице символов, может быть смещением от регистра (такого как указатель стека), но это деталь реализации.
источник
Точные методы зависят от того, о чем конкретно вы говорите и насколько глубоко вы хотите зайти. Например, хранение файлов на жестком диске отличается от хранения чего-либо в памяти или хранения чего-либо в базе данных. Хотя понятия похожи. И то, как вы делаете это на уровне программирования, - это другое объяснение, чем то, как это делает компьютер на уровне ввода-вывода.
Большинство систем используют своего рода механизм каталогов / индексов / реестра, чтобы позволить компьютеру находить данные и получать к ним доступ. Этот индекс / каталог будет содержать один или несколько ключей и адрес, в котором фактически находятся данные (будь то жесткий диск, ОЗУ, база данных и т. Д.).
Пример компьютерной программы
Компьютерная программа может получить доступ к памяти различными способами. Обычно операционная система предоставляет программе адресное пространство, и программа может делать то, что она хочет, с этим адресным пространством. Он может записывать напрямую на любой адрес в своем пространстве памяти, и он может отслеживать, как он хочет. Иногда это зависит от языка программирования и операционной системы или даже в зависимости от предпочтений программиста.
Как упоминалось в некоторых других ответах, точное используемое кодирование или программирование отличаются, но обычно за кулисами используется нечто вроде стека. Он имеет регистр, в котором хранится область памяти, где начинается текущий стек, а затем метод определения, где в этом стеке находится функция или переменная.
Во многих языках программирования более высокого уровня, он позаботится обо всем этом для вас. Все, что вам нужно сделать, это объявить переменную и сохранить что-то в этой переменной, и это создаст необходимые стеки и массивы за кулисами для вас.
Но, учитывая универсальность программирования, на самом деле нет единственного ответа, поскольку программист может в любой момент выбрать прямую запись по любому адресу в пределах выделенного ему пространства (при условии, что он использует язык программирования, который позволяет это). Затем он может сохранить его местоположение в массиве или даже просто жестко закодировать его в программу (т.е. переменная «альфа» всегда хранится в начале стека или всегда сохраняется в первых 32 битах выделенной памяти).
Резюме
Таким образом, в принципе, должен быть какой-то механизм за сценой, который сообщает компьютеру, где хранятся данные. Одним из наиболее популярных способов является своего рода индекс / каталог, который содержит ключ (и) и адрес памяти. Это реализуется всевозможными способами и обычно инкапсулируется пользователем (а иногда даже инкапсулируется программистом).
Справка: Как компьютеры запоминают, где хранят вещи?
источник
Это знает из-за шаблонов и форматов.
Программа / функция / компьютер фактически не знает, где что-либо находится. Он просто ожидает, что что-то будет в определенном месте. Давайте использовать пример.
Наш новый класс 'simpleClass' содержит 3 важные переменные - два целых числа, которые могут содержать некоторые данные, когда они нам нужны, и указатель на другой объект 'simpleClass'. Давайте предположим, что мы находимся на 32-битной машине ради простоты. 'gcc' или другой компилятор 'C' создаст шаблон для работы с нами для выделения некоторых данных.
Простые типы
Во-первых, когда кто-то использует ключевое слово для простого типа, такого как «int», компилятор делает примечание в разделе «.data» или «.bss» исполняемого файла, так что когда он выполняется операционной системой, данные доступно для программы. Ключевое слово int выделяет 4 байта (32 бита), а длинное int выделяет 8 байтов (64 бита).
Иногда, ячейка за ячейкой, переменная может появиться сразу после инструкции, которая должна загрузить ее в память, поэтому она будет выглядеть так в псевдосборке:
Это закончится значением «5» в EAX и EBX.
Во время выполнения программы выполняется каждая инструкция, кроме «5», так как немедленная загрузка ссылается на нее и заставляет процессор пропускать ее.
Недостатком этого метода является то, что он действительно практичен только для констант, так как было бы нецелесообразно хранить массивы / буферы / строки в середине вашего кода. Поэтому, как правило, большинство переменных хранятся в заголовках программ.
Если нужно получить доступ к одной из этих динамических переменных, то можно обработать непосредственное значение, как если бы это был указатель:
Это закончится значением «0x0AF2CE66» в регистре EAX и значением «5» в регистре EBX. Можно также добавлять значения в регистры вместе, поэтому мы сможем найти элементы массива или строки, используя этот метод.
Другим важным моментом является то, что можно сохранять значения при использовании адресов аналогичным образом, чтобы впоследствии можно было ссылаться на значения в этих ячейках.
Сложные типы
Если мы сделаем два объекта этого класса:
тогда мы можем назначить указатель на второй объект на поле, доступное для него в первом объекте:
Теперь программа может ожидать, что найдет адрес второго объекта в поле указателя первого объекта. В памяти это будет выглядеть примерно так:
Здесь следует отметить один очень важный факт: у 'newObjA' и 'newObjB' нет имен при компиляции. Это просто места, где мы ожидаем, что некоторые данные будут в. Таким образом, если мы добавим 2 ячейки в & newObjA, мы найдем ячейку, которая действует как 'nextObject'. Следовательно, если мы знаем адрес 'newObjA' и где ячейка 'nextObject' относительно него, то мы можем узнать адрес 'newObjB':
Это закончится словами «2 + & newObjA» в «EAX» и «& newObjB» в «EBX».
Шаблоны / Форматы
Когда компилятор компилирует определение класса, он на самом деле компилирует способ создания формата, способ записи в формат и способ чтения из формата.
Приведенный выше пример является шаблоном для односвязного списка с двумя переменными int. Эти типы конструкций очень важны для динамического распределения памяти, наряду с двоичными и n-арными деревьями. Практическое применение n-арных деревьев - это файловые системы, состоящие из каталогов, указывающих на файлы, каталоги или другие экземпляры, распознаваемые драйверами / операционной системой.
Чтобы получить доступ ко всем элементам, подумайте о дюймовом черве, продвигающемся вверх и вниз по структуре. Таким образом, программа / функция / компьютер ничего не знает, а просто выполняет инструкции для перемещения данных.
источник