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

Сегменты памяти

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

Различия между стеком и кучей

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

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

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

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

Стек и куча в действии

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

Утечка кучи и переполнение стека

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

Сфера

Теперь, зная, что мы знаем о том, как стеки хранят данные о переменных и функциях, мы можем ясно видеть, что некоторые функциональные данные и данные переменных недоступны после того, как функция завершила выполнение и была извлечена из стека. Эти переменные больше не существуют в памяти, и их нельзя вызвать. Глобальные и статические переменные имеют собственное пространство и память, и к ним можно получить доступ в любом месте приложения. Обычно мы использовали область видимости в своих интересах при инкапсуляции данных, повторном использовании имен переменных и т. Д., И теперь мы знаем, что область видимости является побочным продуктом нашего стека LIFO.

Передача по ссылке и передача по значению

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

Такое поведение хорошо видно на языках, которые различают примитивные типы и объекты. Java содержит как примитивы, так и объекты. Примитивные типы достаточно малы, чтобы их можно было объявить в стеке, и это, безусловно, верно для собственных примитивных типов Java. Однако все объекты в Java используют кучу. В Java множество объектов, и их создание можно определить по ключевому слову «новый». Сохраняя в уме наши правила передачи по ссылке и передаче по значению, мы можем ожидать, что примитивные типы Java int, char и boolean передаются по значению и не могут быть изменены с помощью аргументов в функции. Однако, когда в функцию передаются такие объекты, как массивы, строки или другие пользовательские объекты, исходный объект может быть изменен.

Все больше и больше языков приспосабливаются к менталитету «все является объектом», например, в Ruby. В Ruby все является объектом, и поэтому все хранится в куче. Интерпретаторы в последние годы стали работать быстрее и эволюционировали от простой сборки мусора, чтобы обеспечить полный переход к хранению данных в куче, где мы можем использовать преимущества динамического распределения в большинстве случаев. Однако данные меньшего размера по-прежнему передаются по значению.