Подтвердить что ты не робот

Как компиляторы назначают адреса памяти для переменных?

Я преподаю курс, где студенты могут задавать вопросы о программировании (!): у меня есть этот вопрос:

Почему машина выбирает переменные в памяти? Можем ли мы сказать где хранить переменную?

Я не знаю, что сказать. Вот моя первая попытка:

Компилятор (а не машина) выбирает, где хранить переменные в адресе процесса автоматически. Используя C, мы не можем указать машине, где хранить переменные.

Но это "автоматически" несколько противоречиво и задает вопрос... и я понял, что даже не знаю, является ли это компилятором или исполняемым файлом или ОС или кто выполняет задание. Может быть, кто-то может ответить на вопрос ученика лучше меня.

4b9b3361

Ответ 1

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

Выделенные переменные

Обычно local variables помещаются в "стек". Это означает, что компилятор присваивает смещение "указателю стека", который может быть различным в зависимости от вызова текущей функции. То есть компилятор предполагает, что места памяти, такие как Stack-Pointer + 4, Stack-Pointer + 8 и т.д., доступны и могут использоваться программой. При использовании return от функции не сохраняются эти ячейки памяти.

Это сопоставляется с инструкциями по сборке, аналогичными приведенным ниже. esp - указатель стека, esp + N относится к ячейке памяти относительно esp:

mov eax, DWORD PTR SS:[esp]
mov eax, DWORD PTR SS:[esp + 4]
mov eax, DWORD PTR SS:[esp + 8]

Heap

Затем существуют переменные, выделенные в кучу. Это означает, что есть вызов библиотеки для запроса памяти из стандартной библиотеки (alloc в C или new в С++). Эта память сохраняется до конца выполнения программ. alloc и new возвращают указатели в память в области памяти, называемой кучей. Функции распределения должны быть уверены, что память не зарезервирована, что может замедлить распределение кучи в разы. Кроме того, если вы не хотите исчерпывать память, вы должны free (или delete) память, которая больше не используется. Распределение кучи довольно сложно внутри, поскольку стандартная библиотека должна отслеживать используемые и неиспользуемые диапазоны в памяти, а также освобожденные диапазоны памяти. Поэтому даже освобождение переменной, распределенной по кучам, может быть более трудоемким, чем выделение. Для получения дополнительной информации см. Как встроено malloc() внутри?

Понимание разницы между стеком и кучей имеет фундаментальное значение для обучения программированию на C и С++.

Произвольные указатели

Наивно можно предположить, что путем установки указателя на произвольный адрес int *a = 0x123 должно быть возможно адресовать произвольные местоположения в памяти компьютера. Это не совсем верно, поскольку (в зависимости от CPU и системы) программы сильно ограничены при обращении к памяти.

Получение информации о памяти

В опыте с опытным руководством может быть полезно изучить некоторые простые C-коды, скомпилировав исходный код на ассемблер (например, gcc может это сделать). Простую функцию, такую ​​как int foo(int a, int b) { return a+b;}, должна быть достаточной (без оптимизации). Затем увидите что-то вроде int bar(int *a, int *b) { return (*a) + (*b);};

При вызове bar выделите параметры один раз в стеке, один раз за malloc.

Заключение

Компилятор выполняет некоторую переменную placment и выравнивание по отношению к базовым адресам, которые получаются библиотекой программ/стандартных во время выполнения.

Для глубокого понимания вопросов, связанных с памятью, см. Ульрих Дреппер "Что каждый программист должен знать о памяти" http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.91.957

Помимо C-ish Country idenote

Тогда также есть коллекция Garbage Collection, которая популярна среди множества языков сценариев (Python, Perl, Javascript, lisp) и независимых от устройства сред (Java, С#). Это связано с распределением кучи, но немного сложнее.

Разновидности языков программирования - это только кучи (без стекового питона) или полностью основанные на стеке (вперед).

Ответ 2

Я думаю, что ответ на этот вопрос начинается с понимания макета программы в памяти. Под операционной системой основная память компьютера - это просто гигантский массив. Когда вы запускаете программу, операционная система занимает кусок этой памяти и разбивает ее на логические разделы для следующих целей:

  • stack: эта область памяти хранит информацию обо всех функциях, находящихся в настоящее время в области видимости, включая текущую функцию и всех ее предков. Сохраненная информация содержит локальные переменные и адрес для возврата к выполнению функции.

  • куча: эта область памяти используется, когда вы хотите динамически распределить некоторое хранилище. Как правило, ваша локальная переменная будет содержать адрес (то есть, он будет указателем) в куче, где хранятся ваши данные, и вы можете опубликовать этот адрес в других частях вашей программы, не опасаясь, что ваши данные будут перезаписаны, когда текущие функция выходит за рамки.

  • bss, текстовые сегменты: они более или менее выходят за рамки данного конкретного вопроса, но они хранят такие вещи, как глобальные данные и сама программа.

Надеюсь, что это поможет. В Интернете есть много хороших ресурсов. Я просто искал "макет программы в памяти" и нашел это: http://duartes.org/gustavo/blog/post/anatomy-of-a-program-in-memory

Ответ 3

Локальные переменные внутри функции обычно будут выложены последовательно в фрейме стека функций.

Ответ 4

Я хотел бы добавить несколько. Для прошивки, где вы знаете карту памяти и рабочий адрес, и где вы скомпилируете исходный код с помощью собственного компоновщика script -

Вы можете назначить настраиваемый раздел переменной с помощью атрибута раздела, а затем назначить конкретный адрес в пользовательский раздел через компоновщик script. Тогда переменная получит фиксированный/назначенный адрес.