Malloc

Материал из Национальной библиотеки им. Н. Э. Баумана
Последнее изменение этой страницы: 18:04, 10 июня 2019.

Malloc (от англ. memory allocation, выделение памяти) — это функция выделения динамической памяти, входящая в стандартную библиотеку языка Си.

Описание

Динамическое выделение памяти, необходимо когда размер используемого пространства заранее не известен. Для этого используется выделение памяти на куче. Недостатков у такого подхода два: во-первых, память необходимо вручную очищать, во-вторых, выделение памяти – достаточно дорогостоящая операция.Для выделения памяти существует функция malloc(), позволяющая выделять память по мере необходимости. Данная функция находится в библиотеке и имеет следующий синтаксис:

1  void* malloc(size_t);	//функция выделения памяти

Здесь size_t – размер выделяемой области памяти в байтах; void* - обобщенный тип указателя, т.е. не привязанный к какому-либо конкретному типу. Рассмотрим работу данной функции на примере выделения памяти для 10 элементов типа double.

 1 include 
 2 #include 
 3 int main() 
 4 { 
 5 double* ptd; 
 6 ptd = (double *)malloc(10 * sizeof(double));
 7 if (ptd != NULL)
 8 { 
 9 for (int i = 0;i < 10;i++)
10 ptd[i] = i; 
11 } else printf(“Не удалось выделить память.); 
12 free(ptd); 
13 return 0; 
14 }

При вызове функции malloc() выполняется расчет необходимой области памяти для хранения 10 элементов типа double. Для этого используется функция sizeof(), которая возвращает число байт, необходимых для хранения одного элемента,типа double. Затем ее значение умножается на 10 и в результате получается объем для 10 элементов типа double. В случаях, когда по каким-либо причинам не удается выделить указанный объем памяти, функция malloc() возвращает значение NULL. Данная константа определена в нескольких библиотеках. Если функция malloc() возвратила указатель на выделенную область памяти, т.е. не равный NULL, то выполняется цикл, где записываются значения для каждого элемента. Чтобы упростить процесс изменения параметров в С++ вводится такое понятие как ссылка. Ссылка представляет собой псевдоним (или второе имя), который программы могут использовать для обращения к переменной. Для объявления ссылки в программе используется знак & перед ее именем. Особенность использования ссылок заключается в необходимости их инициализации сразу же при объявлении, например:

1  
2 int var; 
3 int &var2 = var;

Здесь объявлена ссылка с именем var2, которая инициализируется переменной var. Это значит, что переменная var имеет свой псевдоним var2, через который возможно любое изменение значений переменной var. Преимущество использования ссылок перед указателями заключается в их обязательной инициализации, поэтому программист всегда уверен, что переменная var2 работает с выделенной областью памяти, а не с произвольной, что возможно при использовании указателей. В отличие от указателей ссылка инициализируется только один раз, при ее объявлении. Повторная инициализация приведет к ошибке на стадии компиляции. Благодаря этому обеспечивается надежность использования ссылок, но снижает гибкость их применения. Обычно ссылки используют в качестве аргументов функций для изменения передаваемых переменных внутри функций. Следующий пример демонстрирует применение такой функции:

 1 void swap (int& a, int& b) 
 2 { 
 3 int temp = a;
 4 a = b; 
 5 b = temp;
 6 } 
 7 int main()
 8 { 
 9 int agr1 = 10, arg2 = 5;
10 swap(arg1, arg2);
11 return 0;
12 }

В данном примере функция swap() использует два аргумента, представляющие собой ссылки на две переменные. Используя имена ссылок a и b, осуществляется манипулирование переменными arg1 и arg2, заданных в основной функции main() и переданных как параметры функции swap(). Преимущество функции swap() (которая использует ссылки, а не указатели на переменные) заключается гарантии того, что функция в качестве аргументов будет принимать соответствующие типы переменные, а не какую-либо другую информацию, и ссылки будут инициализированы корректно перед их использованием. Это отслеживается компилятором в момент преобразования текста программы в объектный код и выдается сообщение об ошибке, если использование ссылок неверно. В отличие от указателей со ссылками нельзя выполнять следующие операции:

  • нельзя получить адрес ссылки, используя оператор адреса C++.
  • нельзя присвоить ссылке указатель.
  • нельзя сравнить значения ссылок, используя операторы сравнения C++.
  • нельзя выполнять арифметические операции над ссылкой, например, добавить смещение.[Источник 1]

Разбор кода

Для выделения памяти в си используется функция malloc (memory allocation) из библиотеки
1  stdlib.h
2 void * malloc(size_t size);

Функция выделяет size байтов памяти и возвращает указатель на неё. Если память выделить не удалось, то функция возвращает NULL. Так как malloc возвращает указатель типа void, то его необходимо явно приводить к нужному нам типу. Например, создадим указатель, после этого выделим память размером в 100 байт.

 1 #include <conio.h>
 2 #include <stdio.h>
 3 #include <stdlib.h>
 4  
 5 void main() {
 6     int *p = NULL;
 7     p = (int*) malloc(100);
 8  
 9     free(p);
10 }

После этого необходимо освободить память функцией free. Используя указатель, можно работать с выделенной памятью как с массивом. Пример: пользователь вводит число – размер массива, создаём массив этого размера и заполняем его квадратами чисел по порядку. После этого выводим и удаляем массив.

 1 #include <conio.h>
 2 #include <stdio.h>
 3 #include <stdlib.h>
 4  
 5 void main() {
 6     const int maxNumber = 100;
 7     int *p = NULL;
 8     unsigned i, size;
 9  
10     do {
11         printf("Enter number from 0 to %d: ", maxNumber);
12         scanf("%d", &size);
13         if (size < maxNumber) {
14             break;
15         }
16     } while (1);
17  
18     p = (int*) malloc(size * sizeof(int));
19  
20     for (i = 0; i < size; i++) {
21         p[i] = i*i;
22     }
23  
24     for (i = 0; i < size; i++) {
25         printf("%d ", p[i]);
26     }
27  
28     _getch();
29     free(p);
30 }

Разбирем код:

1  p = (int*) malloc(size * sizeof(int));

Здесь (int *) – обозначает приведение типов. Необходимо написать такой же тип, как и у указателя. size * sizeof(int) – обозначает сколько байт выделить. sizeof(int) – обозначает размер одного элемента массива.

После этого необходимо начать работать с указателем точно также, как и с массивом. В конце не забываем удалять выделенную память. Теперь представим на рисунке, что происходило. Пусть введём число 5 (см. рисунок 1).
Рисунок 1 – Выделение память

Как видно на картинке функция malloc выделила память на куче по определённому адресу, после чего вернула его. Теперь указатель p хранит этот адрес и может им пользоваться для работы. В принципе, он может использоваться и любым другим адресом. Когда функция malloc "выделяет память", то она резервирует место на куче и возвращает адрес этого участка. У пользователя будет гарантия, что компьютер не отдаст память кому-то ещё. Когда пользователь вызывает функцию free, то он освобождает память, то есть говорит компьютеру, что эта память может быть использована кем-то другим. Он может использовать память, а может и нет. При этом сама переменная не зануляется, она продолжает хранить адрес, которым ранее пользовалась.[Источник 2]

Характерные ошибки при использовании

Память остаётся «занятой», даже если ни один указатель в программе на неё не ссылается (для освобождения памяти используется функция free). Накопление «потерянных» участков памяти приводит к постепенной деградации системы. Ошибки, связанные с неосвобождением занятых участков памяти, называются утечками памяти (англ. memory leaks).

  • Если объём обрабатываемых данных больше, чем объём выделенной памяти, возможно повреждение других областей динамической памяти. Такие ошибки называются ошибками переполнения буфера (англ. buffer overflow).
  • Если указатель на выделенную область памяти после освобождения продолжает использоваться, то при обращении к «уже не существующему» блоку динамической памяти может произойти исключение (англ. exception), сбой программы, повреждение других данных или не произойти ничего (в зависимости от типа операционной системы и используемого аппаратного обеспечения).
  • Если для одной области памяти free вызывается более чем один раз, то это может повредить данные самой библиотеки, содержащей malloc/free, и привести к непредсказуемому поведению в произвольные моменты времени.[Источник 3]

Защитный Malloc

Защитный Malloc является специальной версией malloc библиотеки, заменяющей стандартную библиотеку во время отладки. Охраняет использование Malloc несколькими метододами,чтобы попытаться разрушить Ваше приложение в отдельном моменте, где происходит ошибка памяти. Например, когда память освобождена, это помещает отдельные выделения памяти на различных страницах виртуальной памяти и затем удаляет всю страницу. Последующие попытки получить доступ к освобожденной памяти вызывают непосредственное исключение памяти, а не слепой доступ в память, которая могла бы теперь содержать другие данные. Когда катастрофический отказ происходит, можно тогда пойти и проверить точку отказа в отладчике для идентификации проблемы.

  • Обнаружение дважды освобожденной памяти

malloc библиотека сообщает о попытках вызвать free на уже освобожденном буфере. Если Вы включили MallocStackLoggingNoCompact опция, можно использовать зарегистрированные данные стека для обнаружения где в коде второе free вызов был выполнен. Можно тогда использовать эту информацию, чтобы установить надлежащую точку останова в отладчике и разыскать проблему.

  • Обнаружение повреждения «кучи»

Для включения проверки «кучи» присвойте значения MallocCheckHeapStart и MallocCheckHeapEach переменные окружения. Необходимо установить обе из этих переменных для включения проверки «кучи». MallocCheckHeapStart переменная говорит malloc библиотеке сколько malloc вызовы для обработки прежде, чем инициировать первую проверку «кучи». Установите второе в число malloc вызовы для обработки между проверками «кучи». MallocCheckHeapStart когда повреждение «кучи» происходит в предсказуемое время, переменная полезна. Как только это поражает надлежащую стартовую точку, malloc библиотека начинает регистрировать сообщения выделения к Окну терминала. Можно наблюдать число выделений и использовать эту информацию для определения приблизительно, где повреждается «куча». Скорректируйте значения для MallocCheckHeapStart и MallocCheckHeapEach по мере необходимости сужать реальную точку повреждения.

  • Обнаружение ошибок разрушения памяти

Для нахождения ошибок разрушения памяти включите MallocScribble переменная. Эта переменная пишет недопустимые данные в освобожденные блоки памяти, выполнение которых заставляет исключению происходить. При использовании этой переменной необходимо также установить MallocStackLogging и MallocStackLoggingNoCompact переменные для журналирования расположения исключения. Когда исключение происходит, можно тогда использовать malloc_history команда для разыскивания кода, выделившего блок памяти. Можно тогда использовать эту информацию, чтобы отследить через код и искать любые непрекращающиеся указатели на этот блок.[Источник 4]

Обобщение

Функция malloc позволяет выделять динамическую память и работает так: Malloc возвращает указатель void на выделенное пространство или NULL Если возникает нехватка памяти. Для возврата указателя на тип, отличное от void, стоит использовать приведение типов для возвращаемого значения. Дисковое пространство, на который указывает возвращаемое значение, будет гарантированно соответствовать требованиям к выравниванию для хранения объектов любого типа, если таковые требования не превышают базовые (В Visual C++, базовые основное выравнивание, необходимое для двойные, или 8 байт. В коде для 64-разрядных платформ это ограничение составляет 16 байтов.)

Источники

  1. 4.2. Функции malloc() и free(). // Научная библиотека. [2019]. Дата обновления: 15.04.2019. URL:http://sernam.ru/c_42.php (дата обращения 11.05.2019).
  2. Динамическое выделение памяти // Learnc.info. [2013-2019]. Дата обновления: 17.06.2016. URL:https://learnc.info/c/memory_allocation.html (дата обращения 11.05.2019).
  3. Malloc // Mediaknowledge. [2019]. Дата обновления: 06.09.2015. URL:http://mediaknowledge.ru/e09d250501b76553.html (дата обращения 11.05.2019).
  4. Включение функций отладки Malloc // Spec-Zone. [2019]. Дата обновления: 23.05.2017. URL:http://spec-zone.ru/RU/OSX/documentation/Performance/Conceptual/ManagingMemory/Articles/MallocDebug.html (дата обращения 11.05.2019).