Привет, Хабр! Представляю вашему вниманию перевод статьи «Demystifying memory management in modern programming languages» за авторством Deepu K Sasidharan.
В данной серии статей мне бы хотелось развеять завесу мистики над управлением памятью в программном обеспечении (далее по тексту — ПО) и подробно рассмотреть возможности, предоставляемые современными языками программирования. Надеюсь, что мои статьи помогут читателю заглянуть под капот этих языков и узнать для себя нечто новое.
Углублённое изучение концептов управления памятью позволяет писать более эффективное ПО, потому как стиль и практики кодирования оказывают большое влияние на принципы выделения памяти для нужд программы.
Часть 1: Введение в управление памятью
Управление памятью — это целый набор механизмов, которые позволяют контролировать доступ программы к оперативной памяти компьютера. Данная тема является очень важной при разработке ПО и, при этом, вызывает затруднения или же вовсе остаётся черным ящиком для многих программистов.
Стек как структура данных. Полное понимание! Динамические структуры данных #4
Для чего используется оперативная память?
Когда программа выполняется в операционный системе компьютера, она нуждается в доступе к оперативной памяти (RAM) для того, чтобы:
- загружать свой собственный байт-код для выполнения;
- хранить значения переменных и структуры данных, которые используются в процессе работы;
- загружать внешние модули, которые необходимы программе для выполнения задач.
Стек
Стек используется для статичного выделения памяти. Он организован по принципу «последним пришёл — первым вышел» (LIFO). Можно представить стек как стопку книг — разрешено взаимодействовать только с самой верхней книгой: прочитать её или положить на неё новую.
- благодаря упомянутому принципу, стек позволяет очень быстро выполнять операции с данными — все манипуляции производятся с «верхней книгой в стопке». Книга добавляется в самый верх, если нужно сохранить данные, либо берётся сверху, если данные требуется прочитать;
- существует ограничение в том, что данные, которые предполагается хранить в стеке, обязаны быть конечными и статичными — их размер должен быть известен ещё на этапе компиляции;
- в стековой памяти хранится стек вызовов — информация о ходе выполнения цепочек вызовов функций в виде стековых кадров. Каждый стековый кадр это набор блоков данных, в которых хранится информация, необходимая для работы функции на определённом шаге — её локальные переменные и аргументы, с которыми её вызывали. Например, каждый раз, когда функция объявляет новую переменную, она добавляет её в верхний блок стека. Затем, когда функция завершает свою работу, очищаются все блоки памяти в стеке, которые функция использовала — иными словами, очищаются все блоки ее стекового кадра;
- каждый поток многопоточного приложения имеет доступ к своему собственному стеку;
- управление стековой памятью простое и прямолинейное; оно выполняется операционной системой;
- в стеке обычно хранятся данные вроде локальных переменных и указателей;
- при работе со стеком есть вероятность получать ошибки переполнения стека (stack overflow), так как максимальный его размер строго ограничен. Например, ошибка при составлении граничного условия в рекурсивной функции совершенно точно приведёт к переполнению стека;
- в большинстве языков существует ограничение на размер значений, которые можно сохранить в стек;
КАК РАБОТАЕТ СТЕК | ОСНОВЫ ПРОГРАММИРОВАНИЯ
Использование стека в JavaScript. Объекты хранятся в куче и доступны по ссылкам, которые хранятся в стеке. Тут можно посмотреть в видеоформате
Куча
Куча используется для динамического выделения памяти, однако, в отличие от стека, данные в куче первым делом требуется найти с помощью «оглавления». Можно представить, что куча это такая большая многоуровневая библиотека, в которой, следуя определённым инструкциям, можно найти необходимую книгу.
- операции на куче производятся несколько медленнее, чем на стеке, так как требуют дополнительного этапа для поиска данных;
- в куче хранятся данные динамических размеров, например, список, в который можно добавлять произвольное количество элементов;
- куча общая для всех потоков приложения;
- вследствие динамической природы, куча нетривиальна в управлении и с ней возникает большинство всех проблем и ошибок, связанных с памятью. Способы решения этих проблем предоставляются языками программирования;
- типичные структуры данных, которые хранятся в куче — это глобальные переменные (они должны быть доступны для разных потоков приложения, а куча как раз общая для всех потоков), ссылочные типы, такие как строки или ассоциативные массивы, а так же другие сложные структуры данных;
- при работе с кучей можно получить ошибки выхода за пределы памяти (out of memory), если приложение пытается использовать больше памяти, чем ему доступно;
- размер значений, которые могут храниться в куче, ограничен лишь общим объёмом памяти, который был выделен операционной системой для программы.
Почему эффективное управление памятью важно?
В отличие от жёстких дисков, оперативная память весьма ограниченна (хотя и жёсткие диски, безусловно, тоже не безграничны). Если программа потребляет память не высвобождая её, то, в конечном итоге, она поглотит все доступные резервы и попытается выйти за пределы памяти. Тогда она просто упадет сама, или, что ещё драматичнее, обрушит операционную систему. Следовательно, весьма нежелательно относиться легкомысленно к манипуляциям с памятью при разработке ПО.
Различные подходы
Современные языки программирования стараются максимально упростить работу с памятью и снять с разработчиков часть головной боли. И хотя некоторые почтенные языки всё ещё требуют ручного управления, большинство всё же предоставляет более изящные автоматические подходы. Порой в языке используется сразу несколько подходов к управлению памятью, а иногда разработчику даже доступен выбор какой из вариантов будет эффективнее конкретно для его задач (хороший пример — C++). Перейдём к краткому обзору различных подходов.
Ручное управление памятью
Язык не предоставляет механизмов для автоматического управления памятью. Выделение и освобождение памяти для создаваемых объектов остаётся полностью на совести разработчика. Пример такого языка — C.
Он предоставляет ряд методов (malloc, realloc, calloc и free) для управления памятью — разработчик должен использовать их для выделения и освобождения памяти в своей программе. Этот подход требует большой аккуратности и внимательности. Так же он является в особенности сложным для новичков.
Сборщик мусора
Сборка мусора — это процесс автоматического управления памятью в куче, который заключается в поиске неиспользующихся участков памяти, которые ранее были заняты под нужды программы. Это один из наиболее популярных вариантов механизма для управления памятью в современных языках программирования. Подпрограмма сборки мусора обычно запускается в заранее определённые интервалы времени и бывает, что её запуск совпадает с ресурсозатратными процессами, в результате чего происходит задержка в работе приложения. JVM (Java/Scala/Groovy/Kotlin), JavaScript, Python, C#, Golang, OCaml и Ruby — вот примеры популярных языков, в которых используется сборщик мусора.
-
Сборщик мусора на основе алгоритма пометок (Mark Часть 1: Введение в управление памятью
- Part 2: Memory management in JVM(Java, Kotlin, Scala, Groovy)
- Part 3: Memory management in V8(JavaScript/WebAssembly)
- Part 4: Memory management in Go (in progress)
- Part 5: Memory management in Rust (in progress)
- Part 6: Memory management in Python (in progress)
- Part 7: Memory management in C++ (in progress)
- Part 8: Memory management in C# (in progress)
Ссылки
- http://homepages.inf.ed.ac.uk
- https://javarevisited.blogspot.com
- http://net-informations.com
- https://gribblelab.org
- https://medium.com/computed-comparisons
- https://en.wikipedia.org/wiki/Garbage*collection*(computer_science)
- https://en.wikipedia.org/wiki/Automatic_Reference_Counting
- https://blog.sessionstack.com
Вы можете подписаться на автора статьи в Twitter и на LinkedIn.
За вычитку перевода отдельное спасибо Александру Максимовскому и Катерине Шибаковой
Источник: habr.com
Программирование на ассемблере (стек). Статья 5
Сегодня, пожалуй, самая трудная и важная тема, для тех, кто собирается программировать на ассемблере — стек . Она тесно примыкает к такой теме, как интеграция программ на языке ассемблера с программами на других языках. Так что будьте внимательны. Если что-то не ясно, пишите в комментах и постараюсь развернуто.
Что такое стек. Стек в ассемблере
Стек это область памяти, которая есть у каждой выполняющейся программы. Эта память с особым способом доступа. Мы уже использовали память, которая расположена в секции .data . Эту область памяти можно назвать глобальной. Она выделяется при запуске программы и доступна из всех ее частей. Можно сказать, что это память для глобальных переменных.
А вот о стеке мы пока не говорили. Стек очень удобен для временного хранения содержимого регистров процессора, он используется при вызове подпрограмм (функций) и, наконец, он очень важен при интеграции с программами на других языках.
Когда рассказывают о стеке, то обычно вспоминают такое краткое его определение, как » первый пришел — последний ушел «. Сравнивают стек также со стопкой тарелок, доступ к которой осуществляется сверху стопки: новую тарелку кладем на верх стопки и, соответственно, можем взять тарелку сверху стопки. Для этого в ассемблере есть две команды: push (положить в стек) и pop (взять из стека). Кроме этого, есть специальный регистр процессора — rsp , который всегда содержит адрес последнего положенного в стек числа . При этом запоминаем: стек всегда растет в область младших адресов , т.е. когда выполняем команду push , содержимое rsp уменьшается на 8 . Давайте я еще раз подчеркну, что я рассматриваю 64-битовые системы, а не 32-битовые и тем более не 16-битовые.
Стек, также используется при вызове функций с помощью команды call . Перед тем, как перейти по адресу (на метку), указанному в команде, адрес следующей за call команды кладется в стек . Возврат из функции осуществляется командой ret . Эта команда берет с вершины стека число, которое там находится, рассматривает его как адрес и осуществляет туда переход, освобождая стек от этого числа (равносильно двум командам: в начале pop , а потом безусловный переход jmp по полученному адресу). Наконец, стек используется для передачи в функцию параметров и хранения там локальных переменных. Но это особый разговор и этому будет посвящена отдельная статья и даже не одна (sic!).
Программное использование стека на ассемблере
Ниже, представлены три программы, разобравшись в которых, в том как они работают, вы можете с уверенностью сказать, вы в принципе уже понимаете как и для чего нужен стек.
И так программа asm4.s . В ней показаны два способа доступа к стеку: через команды push и pop , и путем непосредственного обращения к содержимому стека через регистр rsp . То, что программа выводит на консоль строку it’s stack и означает, что в написании программы мы правильно использовали стек. Ваша задача хорошо понять, как это работает. Да, и запомним одну важную истину: необходимо вовремя восстанавливать стек . Если вы используете команды push , то потом нужно использовать команды pop , чтобы стек был восстановлен. Ну или восстановить стек другим способом. В противном случае программа не сможет возвратиться из процедуры и наступит ее крах.
Следующая программа asm6.s демонстрирует механизм того, как помещать данные в стек непосредственно через регистр rsp . Мы кладем туда адрес сообщения, а потом извлекаем данное при помощи стандартной команды pop .
Наконец программа asm7.s . В ней мы используем команду call для вызова функции, в которой осуществляется вывод строки. Обращаю внимание, на команду ret . Она осуществляет возврат из функции. Укажем также на роль команд push и pop в процедуре print . Есть хорошее правило, если вы программируете на ассемблере. При возврате из функции регистры не должны меняться.
Ну за исключением тех случаев, когда через них возвращаются какие то данные. Вот здесь в начале процедуры мы сохраняем регистры, которые мы используем, а потом перед выходом восстанавливаем их.
Пока, любители ассемблера. Программисты на языке ассемблера это элита. Подписываемся на мой канал Old Programmer .
Источник: dzen.ru
Программа стек как работать
Класс std::stack представляет стек — контейнер, который работает по принципу LIFO (last-in first-out или «последний вошел — первым вышел») — первым всегда извлекается последний добавленный элемент. Стек можно сравнить со стопкой предметов, например, стопкой тарелок — тарелки добавляются сверху, каждая последующая тарелка кладется поверх предыдущей. А если надо взять тарелку, то сначала берется та, которая в самом верху (которую положили самой последней).
Для работы со стеком надо подключать заголовочный файл . Определение пустого стека:
#include #include int main() < std::stackstack; // пустой стек строк >
Размер стека
С помощью функции size() можно получить количество элементов в стеке, а с помощью функции empty() проверить стек на наличие элементов (если возвращается true , то стек пуст):
#include #include int main() < std::stackstack; if(stack.empty()) < std::cout std::cout
добавление элементов
Для добавления в стек применяется функция push() , в которую передается добавляемый элемент:
#include #include int main() < std::stackstack; // добавляем три элемента stack.push(«Tom»); stack.push(«Bob»); stack.push(«Sam»); std::cout