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

Последнее обновление: 10.01.2023

В прошлых темах рассматривалось выделение и освобождение динамической памяти под массивы. При выделении памяти с помощью функций 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.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850