Перейти к содержанию

Лекция 14: Работа с динамической памятью

"Понимание позволяет понимать"


Введение в динамическую память

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

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

Преимущества динамической памяти

  • Позволяет создавать структуры данных, размер которых неизвестен заранее (например, массивы переменной длины).
  • Упрощает управление памятью для сложных программ.
  • Динамическая память позволяет эффективно использовать ресурсы, выделяя память только по мере необходимости.

Структура памяти программы

Память программы делится на несколько сегментов:

  • Стек: используется для хранения локальных переменных и аргументов функций. Работает по принципу LIFO (Last In, First Out).
  • Куча: область динамической памяти, в которой память выделяется и освобождается вручную с помощью функций malloc, calloc, realloc и free.
  • Сегмент данных: хранит глобальные переменные и статические данные программы.
  • Сегмент кода: содержит исполняемый код программы.

Каждый сегмент выполняет свою роль в обеспечении корректной работы программы, а управление памятью в куче — одна из ключевых задач программиста.


Функции для динамического распределения памяти

Для управления динамической памятью в языке C используется стандартная библиотека <stdlib.h>, которая предоставляет следующие функции:

  • malloc(size_t size): выделяет блок памяти указанного размера.
  • calloc(size_t n, size_t size): выделяет память для массива из n элементов и инициализирует их нулями.
  • realloc(void *ptr, size_t size): изменяет размер ранее выделенного блока памяти. Если новый размер меньше, лишняя память освобождается. Если новый размер больше, выделяется дополнительная память.
  • free(void *ptr): освобождает ранее выделенную память.

Пример использования функций:

#include <stdlib.h>
#include <stdio.h>

int main() {
    int *arr = (int *)malloc(5 * sizeof(int)); // Выделение памяти для 5 элементов
    if (arr == NULL) {
        printf("Ошибка выделения памяти\n");
        return 1;
    }

    for (int i = 0; i < 5; i++) {
        arr[i] = i * i; // Заполнение массива
        printf("%d ", arr[i]);
    }
    printf("\n");

    free(arr); // Освобождение памяти
    return 0;
}

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

  1. Всегда проверяйте, что результат вызова malloc или calloc не равен NULL. Это гарантирует, что память была успешно выделена.
  2. После завершения работы с памятью обязательно вызывайте free, чтобы избежать утечек.
  3. Никогда не используйте указатель после вызова free — это приведет к неопределенному поведению программы.
  4. В случае использования realloc всегда проверяйте результат, поскольку эта функция может вернуть NULL в случае неудачи, а старый указатель может быть всё еще действительным.

Пример с проверкой выделения памяти:

int *ptr = (int *)malloc(10 * sizeof(int));
if (!ptr) {
    printf("Ошибка: не удалось выделить память\n");
    exit(EXIT_FAILURE);
}
free(ptr);

Одномерные динамические массивы

Одномерный динамический массив — это массив, размер которого определяется во время выполнения программы. Для создания такого массива в C используется функция malloc.

Пример создания массива:

#include <stdlib.h>
#include <stdio.h>

int main() {
    int n;
    printf("Введите размер массива: ");
    scanf("%d", &n);

    int *arr = (int *)malloc(n * sizeof(int));
    if (arr == NULL) {
        printf("Ошибка выделения памяти\n");
        return 1;
    }

    for (int i = 0; i < n; i++) {
        arr[i] = i + 1;
        printf("%d ", arr[i]);
    }
    printf("\n");

    free(arr);
    return 0;
}

Если размер массива нужно изменить, можно использовать функцию realloc, которая перераспределяет память.


Динамическое выделение памяти для двумерных массивов

Для двумерных массивов память можно выделять двумя способами:

1. Одним блоком (плоский массив)

int *matrix = (int *)malloc(rows * cols * sizeof(int));

Доступ к элементу: matrix[i * cols + j].

2. Массив указателей

#include <stdlib.h>
#include <stdio.h>

int main() {
    int rows, cols;
    printf("Введите количество строк и столбцов: ");
    scanf("%d %d", &rows, &cols);

    int **matrix = (int **)malloc(rows * sizeof(int *));
    for (int i = 0; i < rows; i++) {
        matrix[i] = (int *)malloc(cols * sizeof(int));
    }

    // Заполнение и вывод
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            matrix[i][j] = i + j;
            printf("%d ", matrix[i][j]);
        }
        printf("\n");
    }

    // Освобождение памяти
    for (int i = 0; i < rows; i++) {
        free(matrix[i]);
    }
    free(matrix);

    return 0;
}

Важные моменты

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

Заключение

Работа с динамической памятью открывает широкие возможности для управления памятью в программах, но требует осторожности. Ошибки, такие как утечки памяти, двойное освобождение памяти или использование невалидных указателей, могут привести к сбоям программы. В современных языках программирования, таких как Python или Java, управление памятью происходит автоматически, но в C и C++ программисты несут полную ответственность за правильное распределение и освобождение памяти. Это требует дисциплины и внимания на каждом этапе работы с динамической памятью.


Let the bodies hit the floor
Let the bodies hit the floor
Let the bodies hit the floor
Let the bodies hit the floor