Лекция 10 Работа с динамической памятью Функции стандартной библиотеки malloc calloc free realloc Операторы new delete void *malloc(size_t size) malloc возвращает указатель на место в памяти для объекта размера size или, если памяти запрашиваемого объема нет, NULL. Выделенная область памяти не инициализируется. void *calloc(size_t nobj, size_t size) calloc возвращает указатель на место в памяти, отведенное для массива nobj объектов, каждый из которых размера size, или, если памяти запрашиваемого объема нет, NULL. Выделенная область памяти обнуляется. К указателю, возвращаемому функциями malloc и calloc, должна быть применена операция приведения к соответствующему типу как это сделано в следующем фрагменте программы: int *ip; ip = (int *) calloc(n, sizeof(int)); void free(void *р) free освобождает область памяти, на которую указывает p; эта функция ничего не делает, если p равно NULL. В p должен стоять указатель на область памяти, ранее выделенную одной из функций: calloc, malloc или realloc. void *realloc(void *p, size_t size) realloc заменяет на size размер объекта, на который указывает p. Для части, размер которой равен наименьшему из старого и нового размеров, содержимое не изменяется. Если новый размер больше старого, дополнительное пространство не инициализируется, realloc возвращает указатель на новое место памяти или, если требования не могут быть удовлетворены, NULL (*p при этом не изменяется). Операции new и delete Операции new и delete выполняют динамическое распределение и отмену распределения памяти, аналогично стандартным библиотечным функции семейства malloc и free синтаксис: указатель = new тип <инициализатор>; delete указатель; new пытается создать объект с указанного типа , распределив (при возможности) sizeof(тип) байт в свободной области памяти (которую также называют "кучей"). Продолжительность существования в памяти данного объекта - от точки его создания и до тех пор, пока операция delete не отменит распределенную для него память, либо до конца работы программы. В случае успешного завершения new возвращает указатель нового объекта. Пустой указатель означает неудачное завершение операции (например, недостаточный объем или слишком большая фрагментированность кучи). Как и в случае malloc, прежде чем пытаться обращаться к новому объекту, следует проверить указатель на наличие пустого значения. Однако, в отличие от malloc, new сама вычисляет размер "имени", и явно указывать операцию sizeof нет необходимости. Далее возвращаемый указатель будет иметь правильный тип, "указатель", без необходимости явного приведения типов. name *nameptr // name может иметь любой тип if (!(nameptr = new name)) { errmsg("Недостаточно памяти для name"); exit (1); } // использование *nameptr для инициализации объекта new name ... delete nameptr; // удаление name и отмена //распределения //sizeof(name) байтов памяти константа NULL, определенная в заголовочных файлах C как указатель, равный нулю. Рекомендуется использовать просто 0, так как это значение типа int будет правильно преобразовано стандартными способами в соответствии с контекстом. Поскольку гарантируется, что объектов с нулевым адресом нет, пустой указатель можно использовать для проверки, ссылается указатель на конкретный объект или нет. Пример int* n = new int; int* m = new int (10); int* q = new int [10]; динамические переменные уничтожаются следующим образом: delete n; delete m; delete [] q; Если память выделялась с помощью new[ ], для освобождения памяти необходимо применять delete[ ]. Размерность массива при этом не указывается. Если квадратных скобок нет, то никакого сообщения об ошибке не выдается, но помечен как свободный будет только первый элемент массива, а остальные окажутся недоступны для дальнейших операций. создание динамического многомерного массива int ** m = (int **) new int [5][10]; int*** mat_ptr = (int***) new int[3][10][12]; Недопустимо: int*** mat_ptr = (int***) new int[3][ ][12]; int*** mat_ptr = int*** new int[ ][10][12]; Более универсальный и безопасный способ выделения памяти под двумерный массив int **a = new int *[n_row]; for(int i = 0; i<n_row; i++) a[i] = new int [n_col]; Освобождение памяти из-под массива с любым количеством измерений выполняется с помощью операции delete [] Типичные ошибки программирования Предположение о том, что размер структуры(класса) является простой суммой размеров его данных-элементов. Не осуществляется возвращение динамически выделенной памяти, когда эта память уже более не требуется. Это может явится причиной преждевременного переполнения памяти. Иногда это явление называют «утечкой памяти». Типичные ошибки программирования Освобождение операцией delete памяти, которая не была выделена динамически операцией new. Ссылка на область памяти, которая была освобождена Выход за границы области памяти Хороший стиль программирования Используя операцию new проверьте, не вернула ли она нулевой указатель. Выполните соответствующую обработку ошибки, если операция new не выделила область памяти. Когда память, которая динамически выделена операцией new, более не требуется, используйте операцию delete для немедленного освобождения памяти. #include <stdio.h> #include <string.h> #include <stdlib.h> const int l_name = 30; struct Man { char name[l_name + 1]; int birth_year; float pay; }; int compare(const void *man1, const void *man2); int main(){ FILE *fbin; if ((fbin = fopen("dbase.bin", "rb")) == NULL ) { puts("Ошибка открытия файла\n"); return 1; } fseek(fbin, 0, SEEK_END); int n_record = ftell(fbin) / sizeof(Man); Man *man = new Man [n_record]; fseek(fbin, 0, SEEK_SET); fread(man, sizeof(Man), n_record, fbin); fclose(fbin); qsort(man, n_record, sizeof(Man), compare); for (int i = 0; i < n_record; i++) printf("%s %5i %10.2f\n", man[i].name, man[i].birth_year, man[i].pay); return 0; } int compare(const void *man1, const void *man2) { return strcmp(((Man *) man1)->name, ((Man *) man2)->name); }