В прошлых темах рассматривалось выделение и освобождение динамической памяти под массивы. При выделении памяти с помощью функций malloc()/calloc()/realloc() мы получаем указатель на выделенный блок памяти. И мы можем использовать выделенный блок памяти везде, где доступен указатель.
Указатель имеет в принципе все те же области видимости, что и обычные переменные. Но область видимости определяет не только контекст, рамках которого мы можем использовать указатель, но момент, когда необходимо освобождать выделенную динамическую память. И в зависимости от области видимости указателя может быть три варианта:
Указатель определен в блоке кода. В этом случае указатель будет доступен только в пределах данного блока кода. Соответственно память необходимо освобождать при выходе из этого блока.
Указатель определен как статический объект. В этом случае динамическая память выделяется один раз и доступна через указатель при каждом повторном входе блок. В этом случае память нужно освобождать только после завершения ее использования.
Указатель является глобальным объектом по отношению к блоку. В этом случае динамическая память доступна во всех блоках, где доступен указатель, а память нужно освобождать только после завершения ее использования.
Первый вариант, если указатель определен в функции:
#include <stdio.h> #include <stdlib.h> void createPointer() { int *p = malloc(sizeof(int)); if(!p) return; // если выделение памяти прошло неудачно, выходим из функции *p = 1; printf("%d \n", (*p)); free(p); // конец блока - освобождаем память } int main(void) { createPointer(); createPointer(); createPointer(); return 0; }
В целях демонстрации указатель p указывает на блок выделенной динамической памяти размером в 4 байта, то есть для одного элемента типа int.
Переменная указателя определена в автоматической памяти как локальная переменная функции createPointer, поэтому выделенная динамическая память доступна только
в рамках этой функции createPointer. Соответственно в конце выполнения этой функции нужно освободить память вызовом функции free()
.
При каждом новом вызове функции createPointer в main будет заново выделяться динамическая память.
То же самое относится к вложенным блокам, например:
#include <stdio.h> #include <stdlib.h> void createPointer() { printf("Code block starts\n"); { int* p = malloc(sizeof(int)); if (!p) return; // если выделение памяти прошло неудачно, выходим из функции *p = 1; printf("%d \n", (*p)); free(p); // конец блока - освобождаем память } printf("Code block ends\n"); } int main(void) { createPointer(); return 0; }
Здесь указатель определяется во вложенном безымянном блоке и до конца этого блока память должна быть освобождена.
Теперь рассмотрим второй вариант - указатель определен как статический объект:
#include <stdio.h> #include <stdlib.h> int * createPointer() { static int *p = NULL; if(p==NULL) { p = malloc(sizeof(int)); *p = 1; } printf("%d \n", (*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
Третий вариант, когда указатель является глобальным объектом:
#include <stdio.h> #include <stdlib.h> int* p = NULL; // глобальный объект void createPointer() { printf("%d \n", (*p)); (*p)++; } int main(void) { p = malloc(sizeof(int)); *p = 1; createPointer(); createPointer(); createPointer(); free(p); return 0; }
Указатель определен как глобальный объект, соответственно освобождать динамическую память необходимо, когда она нам будет больше не нужна. Поэтому функция free() вызывается ближе к концу функции main.