Т еперь рассмотри, как происходит освобождение памяти. Переменная указатель хранит адрес области памяти, начиная с которого она может им пользоваться. Однако, она не хранит размера этой области. Откуда тогда функция free знает, сколько памяти необходимо освободить?
Очевидно, что информация о размере выделенного участка должна где-то храниться. Есть несколько решения этой проблемы.
· 1. Можно создать карту, в которой будет храниться тип и размер выделенного участка. Каждый раз при освобождении памяти компьютер будет обращаться к этим данным и получать информацию о размере.
· 2. Второе решение более распространено. Информация о типе и размере хранится на куче до самих данных. Таким образом, при выделении памяти резервируется места больше и туда записывается информация о выделенном участке. При освобождении памяти функция free "подсматривает", сколько памяти необходимо удалить.
Функция free освобождает память, но при этом не изменяет значение указателя, о чём нужно помнить.
Работа с двумерными и многомерными массивами
Д ля динамического создания двумерного массива сначала необходимо создать массив указателей, после чего каждому из элементов этого массива присвоить адрес нового массива.
Для удаления массива необходимо повторить операцию в обратном порядке - удалить сначала подмассивы, а потом и сам массив указателей.
?
#include <conio.h> #include <stdio.h> #include <stdlib.h> #define COL_NUM 10 #define ROW_NUM 10 void main() { float **p = NULL; unsigned i; p = (float**) malloc(ROW_NUM * sizeof(float*)); for (i = 0; i < ROW_NUM; i++) { p[i] = (float*) malloc(COL_NUM * sizeof(float)); } //Здесь какой-то важный код for (i = 0; i < ROW_NUM; i++) { free(p[i]); } free(p); } |
Сначала мы создаём массив указателей, а после этого каждому элементу этого массива присваиваем адрес вновь созданного массива. Это значит, что можно
· 1. Создавать массивы "неправильной формы", то есть массив строк, каждая из которых имеет свой размер.
· 2. Работать по отдельности с каждой строкой массива: освобождать память или изменять размер строки.
Создадим "треугольный" массив и заполним его значениями
?
#include <conio.h> #include <stdio.h> #include <stdlib.h> #define SIZE 10 void main() { int **A; int i, j; A = (int**) malloc(SIZE * sizeof(int*)); for (i = 0; i < SIZE; i++) { A[i] = (int*) malloc((i + 1) * sizeof(int)); } for (i = 0; i < SIZE; i++) { for (j = i; j > 0; j--) { A[i][j] = i * j; } } for (i = 0; i < SIZE; i++) { for (j = i; j > 0; j--) { printf("%d ", A[i][j]); } printf("\n"); } for (i = SIZE-1; i > 0; i--) { free(A[i]); } free(A); getch(); } |
Чтобы создать трёхмерный массив, по аналогии, необходимо сначала определить указатель на указатель на указатель, после чего выделить память под массив указателей на указатель, после чего проинициализировать каждый из массивов и т.д.
Calloc
Ф ункция calloc выделяет n объектов размером m и заполняет их нулями. Обычно она используется для выделения памяти под массивы. Синтаксис
?
void* calloc(size_t num, size_t size); |
Realloc
Е щё одна важная функция – realloc (re-allocation). Она позволяет изменить размер ранее выделенной памяти и получает в качестве аргументов старый указатель и новый размер памяти в байтах:
?
void* realloc(void* ptr, size_t size) |
Функция realloc может как использовать ранее выделенный участок памяти, так и новый. При этом не важно, меньше или больше новый размер – менеджер памяти сам решает, где выделять память.
Пример – пользователь вводит слова. Для начала выделяем под слова массив размером 10. Если пользователь ввёл больше слов, то изменяем его размер, чтобы хватило места. Когда пользователь вводит слово end, прекращаем ввод и выводим на печать все слова.
?
#include <conio.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #define TERM_WORD "end" #define SIZE_INCREMENT 10 void main() { //Массив указателей на слова char **words; //Строка, которая используется для считывания введённого пользователем слова char buffer[128]; //Счётчик слов unsigned wordCounter = 0; //Длина введённого слова unsigned length; //Размер массива слов. Для уменьшения издержек на выделение памяти //каждый раз будем увеличивать массив слов не на одно значение, а на //SIZE_INCREMENT слов unsigned size = SIZE_INCREMENT; int i; //Выделяем память под массив из size указателей words = (char**) malloc(size*sizeof(char*)); do { printf("%d: ", wordCounter); scanf("%127s", buffer); //Функция strcmp возвращает 0, если две строки равны if (strcmp(TERM_WORD, buffer) == 0) { break; } //Определяем длину слова length = strlen(buffer); //В том случае, если введено слов больше, чем длина массива, то //увеличиваем массив слов if (wordCounter >= size) { size += SIZE_INCREMENT; words = (char**) realloc(words, size*sizeof(char*)); } //Выделяем память непосредственно под слово //на 1 байт больше, так как необходимо хранить терминальный символ words[wordCounter] = (char*) malloc(length + 1); //Копируем слово из буффера по адресу, который //хранится в массиве указателей на слова strcpy(words[wordCounter], buffer); wordCounter++; } while(1); for (i = 0; i < wordCounter; i++) { printf("%s\n", words[i]); } getch(); for (i = 0; i < wordCounter; i++) { free(words[i]); } free(words); } |
Хочу обратить внимание, что мы при выделении памяти пишем sizeof(char*), потому что размер указателя на char не равен одному байту, как размер переменной типа char.