В С++ объекты создаются динамически при помощи new-выражений и разрушаются при помощи delete-выражений.
T* obj = new T; delete obj; T* arr = new T[100]; delete[] arr;
New-выражения и delete-выражения используют функции выделения динамической памяти operator new , operator new[] и функции освобождения operator delete , operator delete[] соответственно.
Реализация С++ предоставляет несколько глобальных функций выделения и освобождения. При этом они могут быть переопределены пользователем без нарушения ODR (но не более одного раза в программе).
void* operator new(std::size_t); void operator delete(void*) noexcept; void operator delete(void*, std::size_t) noexcept; void* operator new[](std::size_t); .
Пользователь также может добавлять свои функции выделения и освобождения, в виде статических членов класса или свободных функций в глобальном пространстве имен. Такие пользовательские функции должны соблюдать семантику встроенных (не выделять уже выделенные адреса и т.п.).
Выделение и освобождение динамической памяти в Си
Выражения new и delete для одного объекта
Функции operator new и operator delete только выделяют и освобождают память. Конструирование и разрушение объекта происходит в самом выражении new и delete . Для вызова конструктора используется new который не выделяет память — размещающий new .
// Эквивалент кода // T* obj = new T(a1, a2); T* obj; < void* mem = operator new(sizeof(T)); try < obj = new(mem) T(a1, a2); // Размещающий new, вызывает конструктор T на памяти mem >catch (. ) < // Исключение в конструкторе operator delete(mem); throw; >> // Примерный* эквивалент кода // delete obj; obj->~T(); // Вызов деструктора operator delete(obj); // *) При виртуальном деструкторе компилятор сгенерирует что-то похожее на void* mem = obj->~T(); // псевдокод operator delete(mem); // Т.к. при множественном наследовании может быть mem != obj
Из кода выше видно, что new-выражение требует обе функции, operator new и operator delete .
Функции operator new и operator delete в стандартной библиотеке
Обычный new
void* operator new(std::size_t size);
T* p = new T; assert(p != nullptr);
void* operator new(std::size_t size) < for (;;) < void* mem = unspecified_alloc(size); // Может быть malloc, зависит от реализации. if (mem) return mem; // Пользовательская функция-обработчик нехватки памяти. std::new_handler user_handler_fn = std::get_new_handler(); if (!user_handler_fn) throw std::bad_alloc(); user_handler_fn(); // Либо предоставляет память, // либо бросает bad_alloc или завершает программу. >>
Пользователь может установить свою функцию new_handler при помощи функции std::set_new_handler .
Не-бросающий исключений new
void* operator new(std::size_t size, const std::nothrow_t
T* p = new(std::nothrow) T; if (!p) < /* не удалось выделить память */ >
void* operator new(std::size_t size, const std::nothrow_t try < return operator new(size); >catch (const std::bad_alloc return nullptr; >>
Размещающий new
void* operator new(std::size_t size, void* ptr) noexcept
Ничего не выделяет, возвращают аргумент ptr .
Что такое динамическая память. Утечка памяти. Стек и куча. Статическая память. Обзорный урок #45
Позволяют вызвать конструктор объекта.
char mem[sizeof(T)]; T* p = new(mem) T;
Размещающий operator delete также существует, и также ничего не делает. Нужен только потому что new-выражение требудет наличия operator delete .
Динамические массивы, new[] и delete[]
Функции operator new[] и operator delete[] служат для выделения памяти, и ничем не отличаются от функций для одного объекта. Стандартные реализации просто вызывают operator new :
void* operator new[](std::size_t size)
Размер динамического массива сохраняется самим выражением new[] , примерно так:
// Эквивалент кода (без учета выравнивания) // T* arr = new T[100]; T* arr; < void* mem = operator new[](sizeof(std::size_t) + sizeof(T)); std::size_t arr = reinterpret_cast(reinterpret_cast(mem) + sizeof(std::size_t)); try < for (size = 0; size != 100; ++size) < new(tmp_arr + size) T; // Конструктор элемента >> catch (. ) < while (size != 0) < —size; arr[size].~T(); // Деструктор элемента >operator delete[](mem); throw; > > // Эквивалент кода (без учета выравнивания) // delete[] arr; < std::size_t while (size != 0) < —size; arr[size].~T(); // Деструктор элемента >operator delete[](size); >
Массивы созданные при помощи выражения new T[N] могут быть освобождены только через delete[] ,
и наоборот, объекты созданные new T() , должны быть быть освобождены delete .
Размещающий new[]
void* operator new[](std::size_t size, void* ptr) noexcept;
Он существует, но мы не знаем сколько байт будут резервироваться копилятором под размер объекта, поэтому не сдедует его использовать:
// char mem[100 * sizeof(T) + sizeof(std::size_t)]; // Компилятор может использовать больше чем sizeof(std::size_t), тогда мы выйдем за границы mem. // new(mem) T[100];
Отслеживать
ответ дан 22 июл 2017 в 16:12
30.9k 13 13 золотых знаков 96 96 серебряных знаков 156 156 бронзовых знаков
Динамическая память
Выделяется и освобождается — с помощью специальных инструкций (т. е. по инициативе разработчика). Это позволяет по ходу работы программы контролировать и корректировать объём используемой памяти и, следовательно, создавать программы способные обрабатывать большие объёмы данных, обходя ограниченность физической памяти машины. Выделяется во время работы программы.
Все объекты, выделяемые динамически, размещаются в куче (heap). Если не освобождать динамическую память, то у вас memory leak. Динамическая память, как и любая память, будет освобождена по завершении программы (учтите это, ибо выше писалось только про освобождение разработчиком). Работа с динамической памятью
Выделяется память с помощью оператора new, а освобождается — с помощью оператора delete. В момент, когда динамическая память выделена, она должна быть связана с некоторым указателем, подходящего типа (при выделении указывается тип и количество необходимых ячеек данного типа).
int* p; p = new int; *p = 10; cout
Если не освобождать динамическую память, то она будет занята до завершения программы, что неприемлемо. При выделении одной динамической переменной (одной ячейки памяти), можно сразу инициализировать её значение:
int* p; p = new int(10); cout
Можно выделять сразу несколько ячеек динамической памяти, получая динамический массив. Для этого его размер указывается в квадратных скобках после типа. Чтобы удалить динамический массив и освободить память используется оператор delete[].
int* p; p = new int[13]; for (int i=0; i delete[] p; // память освобождена, из неё удалены все элементы
Cразу после создания динамический массив автоматически заполняется нулями (в отличии от обычного массива в статической или стековой памяти). Если в указатель, уже хранящий адрес какого-то фрагмента динамической памяти, записать новый адрес, то фрагмент динамической памяти будет потерян, т. е. он не будет освобождён, но к нему никак нельзя будет обратиться (например, чтобы освободить этот фрагмент).
int* p; p = new int(13); int a = 666; p = // теперь до 13 никак не добраться
Проблема становится особенно острой, когда в памяти теряются целые массивы (они занимают больше места, чем отдельные переменные).
int* p; for (int i=1; i delete[] p;
На каждом шаге цикла создаётся динамический массив из 100 элементов. Всего таких массивов будет создано 10, но только от последнего из них память будет освобождена после выхода из цикла. 9 массивов продолжат занимать место в памяти до конца программы.
9 массивов * 100 элементов * 4 байта = 3600 байт потерянной памяти, которую никак нельзя использовать (ни в этой программе, не в других запущенных). Очень важно после использования динамической памяти не забывать освобождать её в нужный момент! Сайт-источник : http://itedu.ru/courses/cpp/cpp-dynamic-memory-allocation Примеры: С++: Представлена выше, выделена зеленым шрифтом.
Си: Стандартные функции динамического выделения памяти Функции динамического выделения памяти находят в оперативной памяти непрерывный участок требуемой длины и возвращают начальный адрес этого участка. Функции динамического распределения памяти:
void* malloc(РазмерБлокаПамятиВБайтах); void* calloc(ЧислоЭлементов, РазмерЭлементаВБайтах); void* realloc(УказательНаБлокПамяти, НовыйРазмерБлокаВБайтах);
Для использования функций динамического распределения памяти необходимо подключение библиотеки malloc.h:
#include malloc.h
Поскольку все представленные функции в качестве возвращаемого значения имеют указатель на пустой тип void, требуется явное приведение типа возвращаемого значения. Для определения размера массива в байтах, используемого в качестве аргумента функции malloc()требуется количество элементов умножить на размер одного элемента.
Поскольку элементами массива могут быть как данные простых типов, так и составных типов (например, структуры), для точного определения размера элемента в общем случае рекомендуется использование функции int sizeof(тип); которая определяет количество байт, занимаемое элементом указанного типа. Память, динамически выделенная с использованием функций calloc(), malloc(), может быть освобождена с использованием функции free(указатель); «Правилом хорошего тона» в программировании является освобождение динамически выделенной памяти в случае отсутствия ее дальнейшего использования.
Однако если динамически выделенная память не освобождается явным образом, она будет освобождена по завершении выполнения программы. Функция realloc выполняет перераспределение блоков памяти. Размер блока памяти, на который ссылается параметр ptrmem изменяется на size байтов. Блок памяти может уменьшаться или увеличиваться в размере.
Эта функция может перемещать блок памяти на новое место, в этом случае функция возвращает указатель на новое место в памяти. Содержание блока памяти сохраняется даже если новый блок имеет меньший размер, чем старый. Отбрасываются только те данные, которые не вместились в новый блок.
Если новое значение size больше старого, то содержимое вновь выделенной памяти будет неопределенным. В случае, если ptrmem равен NULL, функция ведет себя именно так, как функция malloc, т. е. выделяет память и возвращает указатель на этот участок памяти. В случае, если size равен 0, ранее выделенная память будет освобождена, как если бы была вызвана функция free, и возвращается нулевой указатель.
Примеры: Выделение памяти при помощи malloc:
int *p = (int *)malloc(sizeof(int)); *p = 34; printf(«%i», *p); // 34 free(p);
Выделение при помощи calloc:
int *p = (int *)calloc(5, sizeof(int)); int i; for(i = 0; i < 5; i++) *(p+i) = i + 1; for(i = 0; i < 5; i++) printf(«%i «, *(p+i)); // 1 2 3 4 5 free(p);
Выделение памяти при помощи realloc:
int *p = (int *)realloc(NULL, 5 * sizeof(int)); int i; for(i = 0; i < 5; i++) *(p+i) = i + 1; for(i = 0; i < 5; i++) printf(«%i «, *(p+i)); // 1 2 3 4 5 int *b = (int *)realloc(p, 6 * sizeof(int)); *(b+5) = 6; for(i = 0; i < 6; i++) printf(«%i «, *(b+i)); // 1 2 3 4 5 6 free(b); // нам не надо освобождать p, так как старый блок памяти // освобождается автоматически. При этом, p может совпадать // с b или же указывать на мусор.
Небольшие заметки по поводу памяти в плюсах (на основе опыта, исправляйте ошибки и дополняйте меня): если у нас недостаточно памяти, valloc, calloc и realloc возвращают NULL. Поэтому проверки на null поинетры нужны. В плюсах new либо вернёт значение, либо кинет исключение; если вы пытаетесь использовать память, которая вам не принадлежит, то это undefined behavior.
Может быть, вас убъёт ось за то, что лезете не в свой кусок памяти. Может, упадёте с segmentation fault, или попортите данные своего же приложения и будете долго и муторно искать ошибку не там, а может быть, у вас девушка забеременеет. Или всё будет работать. То, что malloc выглядит функцией для инициализации одной переменной, а calloc – для массива – это херня. Все три (включая realloc) выделяют последовательный кусок памяти, так что хоть malloc(n * sizeof(int)), хоть calloc(n, sizeof(int)) – одна херня. в память только для чтения нельзя писать, например:
char *str = «Foo»; *str = ‘b’;
NULL поинтеру нельзя присвавать какое либо значение, это segmentation fault. Например:
int * pointer = NULL; *pointer = 13;
Источник: ru.stackoverflow.com
Динамический массив в С++
Динамическое выделение памяти необходимо для эффективного использования памяти компьютера. Например, мы написали какую-то программку, которая обрабатывает массив. При написании данной программы необходимо было объявить массив, то есть задать ему фиксированный размер (к примеру, от 0 до 100 элементов). Тогда данная программа будет не универсальной, ведь может обрабатывать массив размером не более 100 элементов. А если нам понадобятся всего 20 элементов, но в памяти выделится место под 100 элементов, ведь объявление массива было статическим, а такое использование памяти крайне не эффективно.
В С++ операции new и delete предназначены для динамического распределения памяти компьютера. Операция new выделяет память из области свободной памяти, а операция delete высвобождает выделенную память. Выделяемая память, после её использования должна высвобождаться, поэтому операции new и delete используются парами. Даже если не высвобождать память явно, то она освободится ресурсами ОС по завершению работы программы. Рекомендую все-таки не забывать про операцию delete .
// пример использования операции new int *ptrvalue = new int; //где ptrvalue – указатель на выделенный участок памяти типа int //new – операция выделения свободной памяти под создаваемый объект.
Операция new создает объект заданного типа, выделяет ему память и возвращает указатель правильного типа на данный участок памяти. Если память невозможно выделить, например, в случае отсутствия свободных участков, то возвращается нулевой указатель, то есть указатель вернет значение 0. Выделение памяти возможно под любой тип данных: int, float,double, char и т. д.
// пример использования операции delete: delete ptrvalue; // где ptrvalue – указатель на выделенный участок памяти типа int // delete – операция высвобождения памяти
Разработаем программу, в которой будет создаваться динамическая переменная.
// new_delete.cpp: определяет точку входа для консольного приложения. #include «stdafx.h» #include using namespace std; int main(int argc, char* argv[]) < int *ptrvalue = new int; // динамическое выделение памяти под объект типа int *ptrvalue = 9; // инициализация объекта через указатель //int *ptrvalue = new int (9); инициализация может выполнятся сразу при объявлении динамического объекта cout
Источник: cppstudio.com
Тема. Распределение памяти. Динамическое выделение памяти.
Память, выделяемая в процессе выполнения программы, называется динамической. После выделения динамической памяти она сохраняется до ее явного освобождения, что может быть выполнено только с помощью специальной операции или библиотечной функции.
Если динамическая память не освобождена до окончания программы, то она освобождается автоматически при завершении программы. Тем не менее, явное освобождение ставшей ненужной памяти является признаком хорошего стиля программирования.
В процессе выполнения программы участок динамической памяти доступен везде, где доступен указатель, адресующий этот участок.
Для работы с динамической памятью используют указатели. С их помощью осуществляется доступ к участкам динамической памяти, которые называются динамическими переменными. Для хранения динамических переменных выделяется специальная область памяти, называемая «кучей».
Динамические переменные создаются с помощью специальных функций и операций. Они существуют либо до конца работы программы, либо до тех пор, пока не будет освобождена выделенная под них память с помощью специальных функций или операций. То есть время жизни динамических переменных – от точки создания до конца программы или до явного освобождения памяти.
В С++ используется два способа работы с динамической памятью:
- использование операций new и delete;
- использование семейства функций mal1ос (calloc) (унаследовано из С).
Тема. Одномерные динамические массивы.
Динамический массив – это массив, размер которого заранее не фиксирован и может меняться во время исполнения программы. Для изменения размера динамического массива язык программирования С++, поддерживающий такие массивы, предоставляет специальные встроенные функции или операции.
Динамические массивы дают возможность более гибкой работы с данными, так как позволяют не прогнозировать хранимые объемы данных, а регулировать размер массива в соответствии с реально необходимыми объемами. Объявление одномерных динамических массивов Под объявлением одномерного динамического массива понимают объявление указателя на переменную заданного типа для того, чтобы данную переменную можно было использовать как динамический массив.
Синтаксис: Тип * ИмяМассива; ИмяМассива – идентификатор массива, то есть имя указателя для выделяемого блока памяти. Тип – тип элементов объявляемого динамического массива. Элементами динамического массива не могут быть функции и элементы типа void. Например: int *a; double *d; В данных примерах a и d являются указателями на начало выделяемого участка памяти.
Указатели принимают значение адреса выделяемой области памяти для значений типа int и типа double соответственно. Таким образом, при динамическом распределении памяти для динамических массивов следует описать соответствующий указатель, которому будет присвоено значение адреса начала области выделенной памяти.
Выделение памяти под одномерный динамический массив Для того чтобы выделить память под одномерный динамический массив в языке С++ существует 2 способа. 1) при помощи операции new, которая выделяет для размещения массива участок динамической памяти соответствующего размера и не позволяет инициализировать элементы массива.
Синтаксис: ИмяМассива = new Тип [ВыражениеТипаКонстанты]; ИмяМассива – идентификатор массива, то есть имя указателя для выделяемого блока памяти. Тип – тип указателя на массив. ВыражениеТипаКонстанты – задает количество элементов (размерность) массива. Выражение константного типа вычисляется на этапе компиляции. 2) при помощи библиотечной функции malloc (calloc), которая служит для выделения динамической памяти. Синтаксис: ИмяМассива = (Тип *) malloc(N*sizeof(Тип)); или ИмяМассива = (Тип *) calloc(N, sizeof(Тип));
Источник: studfile.net