Виды памяти
Многие программисты, занимающиеся разработкой программ с использованием компилируемых языков программирования, часто не задумываются о том, как происходит работа с памятью в их программах, где хранятся данные и в момент их инициализации, где хранится исполняемый код. Попытаемся суммировать знания о типах памяти и объяснить, что где хранится.
Поясним на примерах на языке С++
Исполняемый код программы хранится в сегменте кода. Данный сегмент защищён от записи, и, как правило, обычным программистам не требуется выполнять над ним каких-либо действий. Простейший и, возможно, единственный пример работы с сегментом данных — получение указателя на функцию. Значение указателя на функцию — это адрес точки входа в эту функцию в сегменте данных.
Глобальные данные (т. е. переменные, объявленные вне какой-либо функции и вне функции main) будут размещены в сегменте данных. При этом глобальные переменные с модификатором const размещаются в защищённом от записи сегменте данных (или сегменте констант), и любая попытка их изменения посредством применения const_cast приведёт к аварийному завершению программы. При разработке необходимо учитывать, что все глобальные данные инициализируются до входа в функцию main, а уничтожаются после выхода из main.
Данные, объявленные внутри какой-либо функции (т. е. локальные данные), будут хранится в стеке. Это относится и к функции main. Поскольку main является точкой входа в программу, локальные данные, объявленные в main, будут хранится на дне стека. По мере вызова других функций из функции main стек будет использоваться для хранения локальных данных вызываемых функций. При возврате из функции все её локальные данные уничтожаются и стек очищается.
После возврата стек будет содержать локальные данные вызывающей функции. Исключение составляют строковые литералы — т. е. переменные или константы вида
char* str = “Hello World!”
или
const char* str = “There is a rabbit”
Эти данные будут помещены в сегмент констант. Также исключение составляют локальные данные, объявленные с модификатором static – они будут храниться в сегменте данных, но в отличие от глобальных данных их инициализация происходит только при первом обращении.
При разработке многопоточных программ необходимо понимать, что каждый поток будет иметь свой стек, но сегменты данных будут общими для всех потоков вашего приложения. Кстати, не многие программисты знают, что память динамически можно также выделять и на стеке. В языках С и С++ для этого служит функция alloca. Важно понимать, что динамически выделенная на стеке память освобождается автоматически при выходе из функции, которая её выделила. Таким образом, этот блок памяти существует только в контексте функции, которая его выделила.
Помимо сегментов данных и стека программе также доступна куча — память, которую программа может запрашивать дополнительно по необходимости в процессе работы, т. е. динамически. Динамически выделенная на куче память становится доступной всем функциям и потокам программы.
Языки С и С++ не поддерживают автоматическое удаление динамически выделенных блоков памяти, которые больше не нужны — так называемую сборку мусора. Поэтому программисты должны сами следить за своевременным освобождением более ненужных блоков динамической памяти.
Нередко в сложных многопоточных приложениях эта задача становится довольно трудной и ведёт к аварийным завершениям программы и/или утечкам памяти. Здесь приходит на помощь замечательное нововведение стандарта С++11 — умные указатели.
Есть вопрос? Напишите в комментариях!