Управление динамической памятью


В прошлых темах рассматривалось выделение и освобождение динамической памяти под массивы. При выделении памяти с помощью функций malloc()/calloc()/realloc() мы получаем указатель на выделенный блок памяти. И мы можем использовать выделенный блок памяти везде, где доступен указатель.

Указатель имеет в принципе все те же области видимости, что и обычные переменные. Но область видимости определяет не только контекст, рамках которого мы можем использовать указатель, но момент, когда необходимо освобождать выделенную динамическую память. И в зависимости от области видимости указателя может быть три варианта:

Указатель определен в блоке кода. В этом случае указатель будет доступен только в пределах данного блока кода. Соответственно память необходимо освобождать при выходе из этого блока.

Указатель определен как статический объект. В этом случае динамическая память выделяется один раз и доступна через указатель при каждом повторном входе блок. В этом случае память нужно освобождать только после завершения ее использования.

Указатель является глобальным объектом по отношению к блоку. В этом случае динамическая память доступна во всех блоках, где доступен указатель, а память нужно освобождать только после завершения ее использования.

Первый вариант, если указатель определен в функции:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <stdlib.h>

void createPointer()
{
int *p = NULL;
int n = 1;
if(p==NULL)
{
p = malloc(n * sizeof(int));
*p = 1;
}
printf("%d \t", (*p));
(*p)++;
free(p);
}

int main(void)
{
createPointer();
createPointer();
createPointer();
return 0;
}
В целях демонстрации указатель p указывает на блок выделенной динамической памяти размером в 4 байта, то есть для одного элемента типа int. Переменная указателя определена в автоматической памяти как локальная переменная функции createPointer, поэтому выделенная динамическая память доступна только в рамках этой функции createPointer. Соответственно в конце выполнения этой функции нужно освободить память вызовом функции free().

При каждом новом вызове функции createPointer в main будет заново выделяться динамическая память.

Консольный вывод:

1 1 1
Теперь рассмотрим второй вариант - указатель определен как статический объект:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>
#include <stdlib.h>

int * createPointer()
{
static int *p = NULL;
int n = 1;
if(p==NULL)
{
p = malloc(n * sizeof(int));
*p = 1;
}
printf("%d \t", (*p));
(*p)++;
return p;
}

int main(void)
{
int *ptr;
ptr=createPointer();
ptr=createPointer();
ptr=createPointer();
free(ptr);
return 0;
}
Поскольку выделенная динамическая память после выхода из функции createPointer сохраняется, то нет смысла ее повторно выделять. И для этого указатель p изначально имеет значение NULL,а с помощью проверки на это значение мы можем решить надо ли выделять память.

Так как статический указатель перестает быть нужен в конце работы программы, то при вызове функции createPointer мы получаем указатель в переменную ptr. И в конце функции main освобождаем выделенную память с помощью функции free.

Консольный вывод:

1 2 3
Третий вариант, когда указатель является глобальным объектом:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <stdlib.h>

int *p = NULL; // глобальный объект

void createPointer()
{
printf("%d \t", (*p));
(*p)++;
}

int main(void)
{
int n = 1;
p = malloc(n * sizeof(int));
*p = 1;

createPointer();
createPointer();
createPointer();

free(p);
return 0;
}
Указатель определен как глобальный объект, соответственно освобождать динамическую память необходимо, когда она нам будет больше не нужна. Поэтому функция free() вызывается ближе к концу функции main.