Что происходит со стеком при выходе из метода?

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

Stack

Стек удаляется после выхода из метода, но что это значит? Является ли указатель на стеке только вернувшимся к началу стека, делая его пустым? Надеюсь, это не слишком широкий вопрос. Я не совсем уверен, что происходит за кулисами, когда стек очищается от выхода из метода.

Ответ 1

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

Стек - это всего лишь область памяти, у нее есть начальный и конечный адрес. JVM (java virtual mashine) имеет регистр, который указывает на текущую вершину стека (указатель стека). Если вызывается новая функция, в регистр будет добавлено смещение, чтобы получить новое пространство в стеке.

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

Локальные переменные и другие элементы (например, адрес возврата, параметры...) могут все еще находиться в стеке и будут перезаписаны следующим вызовом функции.

Кстати: вот почему Java хранит все объекты в куче. Когда объект будет находиться в стеке, и вы вернете ссылку, указывающую на стек, объект может быть уничтожен следующим вызовом функции.

Ответ 2

Во время выполнения функции все локальные переменные создаются в стеке. Это означает, что стек растет, чтобы сделать достаточно места для этих переменных.

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

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

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

Ответ 3

Помните, что стек - это зона памяти, назначенная процессу.

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

В Java ссылки на объекты находятся в стеке, когда сам объект находится в куче. Если все ссылки на объект удаляются из стека, сборщик мусора удалит объект из кучи.

Надеюсь, мой ответ вам поможет. Кроме того, проверьте это.

Ответ 4

Возможно, вам будет полезно подумать о том, как может выглядеть ваш скомпилированный код на уровне машины (или, лучше для нас, людей). Рассмотрим это как возможный пример в сборке X86:

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

call the_method

Когда это произойдет, текущий указатель инструкции вставляется в стек. Указатель стека указывает на него. Теперь мы находимся в функции:

the_method:
   push ebp
   mov  ebp, esp

Текущий базовый указатель сохраняется в стеке, а базовый указатель затем используется для ссылки на вещи в стеке (например, переданные в переменных).

   sub  esp, 8

Далее, 8 байтов (при условии выделения двух четырехбайтовых целых чисел) выделяются в стеке.

   mov [ebp-4], 4
   mov [ebp-8], 2

Локальные переменные назначаются. Это можно было бы сделать, просто нажав их, но, скорее всего, будет задействован sub. Перейдите в конец:

   mov esp, ebp
   pop ebp
   ret

Когда это произойдет, указатель стека находится прямо там, где он был, когда мы начали, указав на сохраненный базовый указатель (сохраненный указатель кадра). Это возвращается в EBP, оставляя ESP, указывая на указатель возврата, который затем "выталкивается" в EIP с помощью ret. Фактически, стек разматывается. Несмотря на то, что фактические места памяти не изменились для двух локальных переменных, они эффективно выше стека (физически ниже в памяти, но я думаю, что вы понимаете, что я имею в виду.)

Ответ 5

Является ли указатель в стеке только что возвращен в начало стека, создавая его пустым?

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

Чтобы проиллюстрировать: если func1, называемый func2, называемый func3, стек будет выглядеть примерно так:

func1 args/local vars... | func2 args/local vars... | func3 args/local vars...

После возврата func3 это будет:

func1 args/local vars... | func2 args/local vars...

Ответ 6

Стек - это просто стек, как правило, стек кадров, с фреймами, содержащими параметры, локальные переменные и экземпляры объектов, а также некоторые другие вещи, зависящие от вашей операционной системы.

Если у вас есть экземпляр объектов в стеке, то есть MyClass x, а не MyClass * x = new MyClass(), тогда объект x будет разорван и его деструктор вызывается, когда стек перематывается к предыдущему кадру, что существенно просто превращает текущий указатель стека (внутренний) в предыдущий кадр. На большинстве родных языков память не будет очищена и т.д.

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