Ниже приведено краткое сравнение различных методов выделения памяти.
Несмотря на то, что функции GlobalAlloc, LocalAlloc и HeapAlloc в конечном счете выделяют память из одной кучи, каждый из них предоставляет несколько иной набор функций. Например, HeapAlloc может быть показано, чтобы создать исключение, если не удалось выделить память, возможность недоступна в LocalAlloc. LocalAlloc поддерживает выделение дескрипторов, которые позволяют перемещению базовой памяти путем перемещений без изменения значения дескриптора, возможности, недоступные в HeapAlloc.
Начиная с 32-разрядной Windows, GlobalAlloc и LocalAlloc реализуются как функции-оболочки, которые вызывают HeapAlloc с помощью дескриптора к куче по умолчанию процесса. Таким образом, GlobalAlloc и LocalAlloc имеют большую нагрузку, чем HeapAlloc.
Так как различные распределители кучи предоставляют отличительные функциональные возможности с помощью различных механизмов, необходимо освободить память с правильной функцией. Например, память, выделенная с помощью HeapAlloc , должна быть освобождена с помощью HeapFree , а не LocalFree или GlobalFree. Память, выделенная с помощью GlobalAlloc или LocalAlloc , должна запрашиваться, проверяться и освобождаться с помощью соответствующей глобальной или локальной функции.
Выделение и освобождение динамической памяти в Си
Функция VirtualAlloc позволяет указать дополнительные параметры выделения памяти. Однако при выделении страниц используется степень детализации страницы, поэтому использование VirtualAlloc может привести к более высокому использованию памяти.
Функция malloc имеет недостаток в зависимости от времени выполнения. Новый оператор имеет недостаток в том, что компилятор зависит от языка и зависит от языка.
Функция CoTaskMemAlloc имеет преимущество эффективной работы в C, C++или Visual Basic. Это также единственный способ совместного использования памяти в приложении на основе COM, так как MIDL использует CoTaskMemAlloc и CoTaskMemFree для маршалинга памяти.
Примеры
- Резервирование и фиксация памяти
- Пример AWE
Источник: learn.microsoft.com
Динамическая память
При создании массива с фиксированными размерами под него выделяется определенная память. Например, пусть у нас будет массив с пятью элементами:
double numbers[5] = ;
Для такого массива выделяется память 5 * 8 (размер типа double) = 40 байт. Таким образом, мы точно знаем, сколько в массиве элементов и сколько он занимает памяти. Однако это не всегда удобно. Иногда бывает необходимо, чтобы количество элементов и соответственно размер выделяемой памяти для массива определялись динамически в зависимости от некоторых условий.
Например, пользователь сам может вводить размер массива. И в этом случае для создания массива мы можем использовать динамическое выделение памяти.
Что такое динамическая память. Утечка памяти. Стек и куча. Статическая память. Обзорный урок #45
Для управления динамическим выделением памяти используется ряд функций, которые определены в заголовочном файле stdlib.h :
-
malloc() . Имеет прототип
void *malloc(unsigned s);
void *calloc(unsigned n, unsigned m);
void *realloc(void *bl, unsigned ns);
void *free(void *bl);
malloc
Функция malloc() выделяет память длиной для определенного количества байт и возвращает указатель на начало выделенной памяти.
Через полученный указатель мы можем помещать данные в выделенную память. Рассмотрим простой пример:
#include #include // для подключения функции malloc int main(void) < int *ptr = malloc(sizeof(int)); // выделяем память для одного int *ptr = 24; // помещаем значение в выделенную память printf(«%d n», *ptr); free(ptr); >
Здесь с помощью функции malloc выделяется память для одного объекта int . Чтобы узнать, сколько байтов надо выделить, передаем в функцию malloc размер типа int на текущей и в результате получаем указатель ptr , который указывает на выделенную память
int *ptr = malloc(sizeof(int));
То есть поскольку int на большинстве архитектур занимает 4 байта, то в большинстве случаев будет выделяться память объемом в 4 байта. Стоит отметить, что мы также могли бы получить размер через разыменование указателя:
int *ptr = malloc(sizeof *ptr);
Для универсальности возвращаемого значения в качестве результата функция malloc() (как и calloc() и realloc() ) возвращает указатель типа void * . Но в нашем случае создается массив типа int, для управления которым используется указатель типа int * , поэтому выполняется неявное приведение результата функции malloc к типу int * .
Далее через этот указатель с помощью операции разыменования помещаем в выделенный участок памяти число 24:
*ptr = 24;
В дальнейшем, используя указатель, можно получить значение из выделенного участка памяти:
printf(«%d n», *ptr);
После завершения работы освобождаем память, передавая указатель в функцию free() :
free(ptr);
Стоит отметить, что теоретически мы можем столкнуться с тем, что функции malloc() не удастся выделить требуемую память, и тогда она возвратит NULL. Чтобы избежать подобной ситуации перед использованием указателя мы можем проверять его на значение NULL:
#include #include // для подключения функции malloc int main(void) < int *ptr = malloc(sizeof(int)); // выделяем память для одного int if(ptr != NULL) < *ptr = 24; // помещаем значение в выделенную память printf(«%d n», *ptr); >free(ptr); >
Немотря на освобождение памяти с помощью функции free() указатель сохраняет свой адрес, и теоретически мы можем обращаться к памяти по данному указателю. Однако полученные значения уже будут неопределенными и недетеминированными. Поэтому некоторые советуют после освобождения памяти также устанавливать для указателя значение NULL :
free(ptr); ptr = NULL;
Выделение памяти для массива
Подобным образом можно выделять память и под набор объектов. Например, выделим память для массива из 4-х чисел int:
#include #include int main(void) < int n = 4; int *ptr = malloc(n * sizeof(int)); // выделяем память для 4-х чисел int if(ptr) < // помещаем значения в выделенную память ptr[0] = 1; ptr[1] = 2; ptr[2] = 3; ptr[3] = 5; // получаем значения for(int i = 0; i < n; i++) < printf(«%d», ptr[i]); >> free(ptr); >
Выделение памяти для структуры
Аналогичным образом можно выделять память для одной или набора структур:
#include #include struct person < char* name; int age; >; int main(void) < // выделяем память для одной структуры person struct person *ptr = malloc(sizeof(struct person)); if(ptr) < // помещаем значения в выделенную память ptr->name = «Tom»; ptr->age = 38; // получаем значения printf(«%s : %d», ptr->name, ptr->age); // Tom : 38 > free(ptr); return 0; >
calloc
Функция calloc() имеет прототип
void *calloc(unsigned n, unsigned m);
Она выделяет память для n элементов по m байт каждый и возвращает указатель на начало выделенной памяти. В случае неудачного выполнения возвращает NULL
В отличие от функции malloc() она инициализирует все выделенные байты памяти нулями. Например, выделим память для одного объекта int :
#include #include int main(void) < // выделяем память для одного объекта int int *ptr = calloc(1, sizeof(int)); if(ptr) < // получаем значение по умолчанию — 0 printf(«Initial value: %d», *ptr); // Initial value: 0 // устанавливаем новое значение *ptr = 15; // получаем новое значение printf(«New value: %d», *ptr); // New value: 15 >free(ptr); return 0; >
Initial value: 0 New value: 15
Подобным образом можно выделить память и для других объектов. Например, выделим память для массива из 4-х объектов int :
#include #include int main(void) < // выделяем память для 4-х объектов int int n = 4; int *ptr = calloc(n, sizeof(int)); if(ptr) < // устанавливаем значения ptr[0] = 1; ptr[1] = 2; ptr[2] = 3; ptr[3] = 5; // получаем значения for(int i = 0; i < n; i++) < printf(«%d», ptr[i]); >> free(ptr); >
realloc
Функция realloc() позволяет изменить размер памяти, ранее выделенной с помощью функций malloc() b calloc() . Имеет прототип
void *realloc(void *bl, unsigned ns);
Первый параметр представляет указатель на ранее выделенный блок памяти. А второй параметр представляет новый размер блока памяти в байтах.
Если указатель bl имеет значение NULL , то есть память не выделялась, то действие функции аналогично действию malloc
Рассмотрим небольшой пример:
#include #include int main(void) < // выделяем память для 1-го объекта int int size = sizeof(int); int *ptr = malloc(size); if(ptr) < // отображаем адрес и размер памяти printf(«Addresss: %p t Size: %dn», (void*)ptr, size); >// расширяем память до размера 4-х объектов int size = 4 * sizeof(int); int *ptr_new = realloc(ptr, size); // если выделение памяти прошло успещно if(ptr_new) < printf(«Reallocationn»); // заново отображаем адрес и размер памяти printf(«Addresss: %p t Size: %dn», (void*)ptr_new, size); free(ptr_new); // освобождаем новый указатель >else < free(ptr); // освобождаем старый указатель >>
Здесь сначала выделяем память для одного объекта int с помощью функции malloc.
int size = sizeof(int); int *ptr = malloc(size);
Если память успешно выделена, то выводим на консоль адрес и размер выделенного блока памяти. Затем с помощью функции realloc расширяем память до 4 объектов int
size = 4 * sizeof(int); int *ptr_new = realloc(ptr, size);
Если увеличение памяти прошло успешно, то заново выводим данные на консоль и освобождаем память по новому указателю. Если увеличение памяти прошло не удачно, то освобождаем память но старому указателю.
Консольный вывод в моем случае
Addresss: 0000018B078A82F0 Allocated: 4 Reallocation Addresss: 0000018B078A82F0 Allocated: 16
Стоит отметить, что нам необязательно создавать новый указатель, мы можем присвоить значение старому указателю:
#include #include int main(void) < int size = sizeof(int); int *ptr = malloc(size); if(ptr) < printf(«Addresss: %p t Allocated: %dn», (void*)ptr, size); >size = 4 * sizeof(int); ptr = realloc(ptr, size); // используем старый указатель if(ptr) < printf(«Reallocationn»); printf(«Addresss: %p t Allocated: %dn», (void*)ptr, size); >free(ptr); >
Источник: metanit.com
Способы выделения памяти в программах: абсолютное, статическое, динамическое и автоматическое распределения. Механизм стека и кучи при реализации процессоров языка программирования.
Статическое выделение памяти – выделение памяти под данные внутри сегмента данных программы. Такие данные существуют на протяжении всей жизни программы до ее завершения.
Автоматическое распределение – выделение памяти под данные в стеке. Такие данные существуют на протяжении работы текущей подпрограммы (функции или процедуры), затем уничтожаются.
Динамическое выделение – выделение памяти под данные самой программой, когда это необходимо. Время жизни таких данных зависит от программы.
В стеке размещаются данные для возврата из подпрограмм, а также их аргументы и автоматические данные. Все это может потребовать достаточно большого размера стека. Как правило, программист может определять размер стека в программе.
Куча. Для реализации динамических структур данных используют т.н. кучу (heap). Это объем памяти, в котором можно выделить участок для произвольного элемента данных. Для кучи есть 2 операции: выделения памяти ALLOCATE и освобождения FREE. Эти функции не делают никаких действий с собственно памятью.
При выделении программист получает адрес, а при освобождении доступный объем кучи становится больше. Одного адреса для этих операций недостаточно, требуется еще и размер элемента данных. Если указатель типизированный, размер будет получен автматически. В случае нетипизированного указателя размер должен быть передан в функцию.
Для реализации кучи ЯВУ снабжаются диспетчерами памяти, которые выделяют и освобождают память, имеют сведения о ее фрагментации, знают наибольший кусок свободной памяти и ее общее количество и т.п. При ненадобности память должна своевременно освобождаться. При использовании динамической памяти возможноа ситуация образования «мусора» – кусков памяти, на которые утеряны ссылки, но которые не были своевремменно освобождены, поэтому менеджер считает их занятыми. Для оптимизации известна процедура “уборка мусора” – перестройка динамических структур с освобождением памяти из-под тех данных, на которые отсутствуют ссылки.
Среды ссылок: глобальная, локальная и нелокальная их реализация в исполняющих системах яву. Понятие программного модуля: подпрограммы, функции, сопрограммы.
Модульное программирование подразумевает разбиение программы на подпрограммы по специфике обрабатываемых данных. Для этой цели в ЯВУ используются функции и процедуры. При вызове подпрограммы в стеке сохраняется текущее значение счетчика команд (ближняя модель вызова) и значение сегмента кода (дальняя модель вызова). При использовании дальней модели вызова подпрограмма необязательно должна находиться в том же сегменте, что и вызывающая программа. По окончании выполнения кода подпрограммы эти данные восстанавливаются, управление передается на следующий оператор после оператора вызова процедуры.
До вызова подпрограммы в стек помещаются параметры – аргументы подпрограммы. Если передан не сам аргумент, а его адрес, то подпрограмма может изменить аргумент, в противном случае нет, т.к. по завершению работы подпрограммы стек очищается от аргументов. Отличие функций от процедур в том, что функции могут возвращать значения в вызывающую программу и их можно присваивать, например, какой-либо переменной. В действительности функция при возврате значений просто модифицирует регистры процессора, а ЯВУ по соглашению использует данные из этих регистров.
Источник: studfile.net