Monday, November 4, 2019

Память в си++.


Сначала был бит. И в конечном итоге в памяти мы храним данные: записываем, считываем, освобождаем, скорость с которой мы это делаем, допустимый объем, все это здорово. Но мы будем говорить не об этом, мы поговорим о том, как бы нам было удобнее хранить и использовать данные.
-
Первым делом нам нужны имена для наших значений, и еще нам нужно разбить данные на кусочки, получаем переменные и их типы. Пока не будем углубляться в то, что типы могут быть простыми, составными и пользовательскими. Но заострим внимание на том, что сами по себе имена переменных, это тоже данные которые нам нужно хранить. Тут нам на помощь приходят пространства имен, которые в свою очередь определяют время жизни переменной. Классический пример это фигурные скобки, в рамках которых не может быть переменных с одним именем и в тоже время наши переменные не могут быть доступны за их рамками. 
-
Не знаю кто придумал функции, но это круто, мы можем делить код на блоки, организовывая выполнение выражений в удобную для человека форму, путем передачи в них параметров и получения из них результата и все это посредством наших переменных. Передача и возврат по значению, по ссылке, по указателю, это тоже все круто, но мы чуть про другое. Для удобства, функции могут иметь вложенные функции, из одних функций могут вызываться другие функции, функции могут передаваться в качестве параметров и это порождает иерархию пространств имен и переменных, за которой человеку сложно уследить. А еще хотелось бы экономно использовать память, где все это должно храниться. Учитывая, что только человек знает, какие переменные ему нужны, оставим на его совести их создание, но может тогда хотя-бы помочь ему освобождать память. И тут нам на помощь приходит первый вид переменных:
-
Автоматические переменные. Переменные, которые автоматически удаляются, выходя за пределы пространства, в котором были объявлены. Заметим, что они все еще доступны во вложенных пространствах, но могут попасть в тень переменных с таким же именем. Частным случаем автоматических переменных, являются глобальные переменные. Это переменные, которые объявлены в глобальном пространстве имен и создаются при старте программы и освобождается при завершении. Поэтому первой точкой входа при старте программы является глобальное пространство имен, а затем уже функция main, которая запускается первой среди определенных в этом пространстве функций. Так как такие переменные активно порождаются и освобождаются, тем самым фрагментируя память, для эффективного хранения и управления используют структуру данных - стек. И выделяют отдельную область памяти, которая тоже называется стек.
-
Отлично, а как же нам тогда общаться между функциями? Частично эту проблему решают глобальные переменные, но это не всегда удобно, глобальное пространство имен делает видимой такую переменную для всех функций. Часто это бывает необходимо, когда нам нужны общие для всех константы, но что, если мы хотим ограничить доступ к переменной в рамках конкретного пространства имен. Например, чтобы созданная в рамках какой-то функции, переменная не освобождалась при выходе из функции, а сохраняла свое значение и была доступна при последующих запусках этой функции. В этом случае нам поможет второй вид переменных: Статические переменные. Такие переменные объявляются с помощью ключевого слова static. Их инициализация происходит при первом обращении к ним и значение хранится в специальной области памяти, которая называется статической. Пользовательские типы немного сложнее в работе со статической памятью, но это отдельная тема для разговора.
-
Говоря об использовании и управлении памятью нельзя не сказать про указатели. И это как раз тот инструмент, который позволяет эффективно управлять памятью вручную. Во-первых, оперировать значениями может быть очень ресурсоемко, например, пользовательский тип может в себе содержать целую иерархию простых и пользовательских типов, что займет значительный объем памяти и работа с ним может значительно нагрузить систему. Если привести аналогию из жизни, то проще носить между инстанциями документы на груз, чем таскать с собой грузовой контейнер. Таким “документом” на переменную является ее адрес в памяти. Но нам не хочется оперировать самими адресами, нужно что-то более подходящее. Для этого служат указатели(pointers), это переменные, которые хранят адреса переменных.
int x = 0;
int * pX = nullptr;
pX = &x;
int y = *pX;
-
Во-вторых, нам хотелось бы создавать и освобождать память, когда мы того сами захотим, без привязки к пространствам имен или времени жизни программы. Нужны данные - разместили (new), нет в них необходимости – удалили (delete), т.е. полный контроль. Для этих целей нам нужна отдельная область памяти и называется она куча(heap). Это место, где размещаются значения, на которые указывают указатели, в свою очередь сами указатели размещаются в стеке. Работа с указателями всегда считалась не простой, т.к. возлагает ответственность за освобождение памяти на человека (в отличии от автоматических переменных) и может привести к утечкам памяти. Облегчить ситуацию призваны так называемые “умные указатели”, а некоторые современные языки программирования (Java, C#) решают эту задачу с помощью “сборщиков мусора”. На что хотелось бы обратить внимание, при освобождении памяти, на которую указывает указатель, сам указатель также требуется обнулить (nullptr), рассмотрим на примере:

#include <iostream>

class Box{
public:
    Box();
    ~Box();
private:
    int bigData;
};

Box::Box() {
    std::cout << "Memory allocated!\n";
}

Box::~Box() {
    std::cout << "Memory deallocated!\n";
}

int main() {
    std::cout << "Pointer created!\n";
    Box * box = nullptr;
    std::cout << "address: " << box << std::endl;

    box = new Box();
    std::cout << "address: " << box << std::endl;

    delete box;
    std::cout << "address: " << box << std::endl;

    std::cout << "Pointer released!\n";
    box = nullptr;
    std::cout << "address: " << box << std::endl;

    return 0;
}
 
Output:
>>Pointer created!
>>address: 0
>>Memory allocated!
>>address: 0x26e00
>>Memory deallocated!
>>address: 0x26e00
>>Pointer released!
>>address: 0 

Перегуд В.

No comments: