Страницы

Поиск по вопросам

понедельник, 17 декабря 2018 г.

Освобождение памяти в Stack'e

Всем известно,что с Stack это некий участок памяти,который аллоцируется на каждый поток в виде размера 1МБ , в нем хранятся ссылки(ObjRef) на ReferenceType,пользовательские структуры,примитивные данные,ну и локальные переменные метода.
А теперь вопрос: что делает CLR, когда Stack полностью заполниться, и соответственно удалять по сути нечего.То бишь Stack переполнен?
Может ли CLR расширить его границы с 1МБ до 2МБ?(или это не возможно,в связи с чем,мы просто получим Exception, который оповестит нас о переполнении стека).
Другой вопрос: в контексте unsafe кода,unamanged участку памяти выделяется так же 1МБ или аллоцировать можно кастомный размер?


Ответ

Для начала стоит отметить что стек в момент выполнения кода - это не какой-то абстрактный safe-механизм. .NET использует JIT-компиляцию, так что в реальности выполняется код, привязанный к конкретной платформе, с использованием механизма стека этой платформы. В случае x86 - сегмент стека + пара регистров SS/ESP и операциями push/pop. Никакого отдельного стека для unsafe не создается.
Что происходит при заполнении стека и можно ли его увеличить по достижению лимита? Нет, в общем случае - нельзя.
Дело в том, что стек, по крайней мере в x86/64 - это структура данных, заполняемая с конца. Т.е. каждое помещение чего-то в стек сдвигает его указатель ближе к началу. Это идет из древних (еще до-.net) времен, когда памяти было мало, и стандартное разделение памяти выглядело так:
[код][данные-куча --> ....... пустое место....... <-- данные-стек]
Физическая память делилась между кучей и стеком, и у программиста всегда был выбор - выделить побольше памяти под что-то в куче, или положить побольше объектов в стеке. К тому же такая раскладка эффективно избавляла от необходимости контролировать размер отдельно кучи, и отдельно - стека. Т.к. если в ней куча и стек встретились - то памяти не осталось совсем.
С тех пор многое поменялось (хотя раскладка выше все еще актуальна на некоторых микроконтроллерах). В x86 Для стека теперь обычно выделен отдельный сегмент. Но он, по традиции, заполняется с конца. Т.е. каждая операция push уменьшает значение SP на размер положенного в стек.
По достижению ESP значения 0 - процессор выбрасывает ошибку. Механизма "довыделения памяти" в стеке в x86 нет - просто потому, что нельзя "довыделить" память в начало сегмента - а выделить память в конце и переложить все данные в стеке процессор не осмеливается - для него это слишком сложная операция. Это могла бы сделать операционная система, но по крайней мере Windows на x86 так не поступает.
На выходе по достижению 0 указателем стека вы получаете StackOverflowException в вашем коде .NET. Это индикатор достижения лимита "железного" стека, а не какого-то хитрого стека CLR.

Стоит отметить, что к StackOverflowException на x86 может привести также бесконечная рекурсия, причем даже в случае если вы не объявляете локальные переменные. Дело в том, что механизм вызовов методов на x86 (call) сохраняет в стеке адрес возврата из вызываемого метода. А оператор возврата (ret) достает адрес из стека. Поэтому слишком глубокая цепочка вызовов забивает стек.
Собственно, именно поэтому окно просмотра вызовов и называется Call Stack - информация цепочке возврата хранится только в стеке. Причем на x86 - в том же физическом стеке что и параметры фунций и локальные переменные.

Комментариев нет:

Отправить комментарий