Перейти к основному содержимому

Для программ на C

к сведению

Данная статья основана на материале статьи "Dynamic Array in C".

Динамические массивы - очень полезные структуры данных. Они могут быть инициализированы с переменным размером во время выполнения. Этот размер может быть изменен позже в программе, чтобы расширить (или) уменьшить массив. В отличие от массивов фиксированного размера и массивов переменной длины, массивы с динамическим размером размещаются в куче.

В этой статье рассматриваются:

  • построение динамических массивов;
  • использование низкоуровневых функций, таких как malloc, free, realloc для реализации массивов динамического размера.

Функция malloc

Мы можем использовать несколько функций C, таких как malloc, free, calloc, realloc, reallocarray, для реализации массивов динамического размера.

Вызов функции malloc эквивалентен запросу операционной системы выделить n байт. Если выделение памяти прошло успешно, malloc возвращает указатель на блок памяти. В противном случае он возвращает NULL. malloc ссылается на "выделение памяти".

Сигнатура функции:

void *malloc(size_t size);

Пример использования malloc:

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

int main() {
int n = 10;
int * p = malloc(n);
if (p == NULL) {
printf("Unable to allocate memory :(\n");
return -1;
}
printf("Allocated %d bytes of memory\n", n);
return 0;
}

В приведенном выше фрагменте мы используем malloc для создания n байт памяти и назначения ее указателю p. Компиляция его с приведенными ниже флагами немедленно выдает ошибку. Очевидно, что было сделано что-то не так. Мы не освобождали память.

❯ gcc -fsanitize=leak malloc-snippet.c && ./a.out
Allocated 10 bytes of memory

=================================================================
==46405==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 10 byte(s) in 1 object(s) allocated from:
#0 0x7f011904a5d1 in __interceptor_malloc ../../../../src/libsanitizer/lsan/lsan_interceptors.cpp:56
#1 0x56087c6bf1a8 in main (/home/appaji/dynamic-size-arrays-in-c/a.out+0x11a8)
#2 0x7f0118e76564 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x28564)

SUMMARY: LeakSanitizer: 10 byte(s) leaked in 1 allocation(s).

Доступ к элементам массива

В C добавление 1 к указателю p увеличивает адрес указателя на sizeof(pType), где pType - это тип данных, на который указывает p. Используя это, мы можем получить доступ к его элементам, добавив i к базовому указателю. Давайте обсудим это на примере (посмотрите на изображение ниже).

  • a - это массив с плавающей запятой. Ясно, что a[0] является базовым указателем для этого массива.
  • ptr также указывает на &a, т.е. базовый указатель.
  • ptr++ подразумевает добавление байтов sizeof(float), т.е. 4 байта к ptr.
  • Итак, теперь ptr указывает на следующий элемент массива, т.е. a[1]. Аналогично, добавив соответствующий i в ptr, мы можем получить доступ к любому элементу в этом массиве, например: *(ptr + i) = a[i].

Доступ к элементам динамического массива

Функция calloc

Функция calloc выделяет непрерывный блок памяти заданного размера и инициализирует каждый блок нулевым значением. В то время как malloc выделяет один блок памяти, но значение в указанном местоположении является случайным ("мусор"). calloc выделяет память и обнуляет выделенные блоки. calloc относится к "непрерывному распределению.

Сигнатура функции:

void *calloc(size_t nmemb, size_t size);

malloc vs calloc

  • calloc принимает два аргумента, тогда как malloc принимает один. nmemb представляет количество блоков памяти. size представляет размер каждого блока. Это больше подходит для выделения памяти для массивов.
  • malloc выделяет память сразу, в одном блоке, в то время как calloc выделяет память в нескольких блоках, которые являются смежными.

Примечание: нулевое значение означает не просто 0. Если мы выделяем массив структур, calloc присваивает NULL строкам, 0 - целым числам, числам с плавающей запятой и т.д.

Функция free

Функция free освобождает динамическую память. Вызов free(p) непосредственно перед возвратом в приведенном выше фрагменте кода предотвратил бы ошибку.

осторожно

Функция free ДОЛЖНА быть вызвана явно после использования динамической памяти, независимо от того, какая функция используется для его создания (malloc, calloc и т.д.).

Пример работы с динамическим массивом

В приведенном ниже коде продемонстрированы основные механизмы работы с динамическим массивами.

#include <stdio.h>
#include <time.h> // time
#include <stdlib.h> // srand, rand
#include <locale.h>


// Вывод динамического массива.
void printDynamicArray(int n, const int *arr) {
for (int i = 0; i < n; i++) {
printf("%d ", *(arr + i)); // разыменование указателя
}
}

// Генерировать случайного целого числа из диапозона [lower, upper].
int iRandom(int lower, int upper) {
// https://www.geeksforgeeks.org/generating-random-number-range-c/
int num = (rand() % (upper - lower + 1)) + lower;
return num;
}


int main() {
setlocale(LC_ALL, "Russian");

// это значение можно считать с клавиатуры
int n = 13;
printf("Размер массива: %d\n", n);

// cоздаем достаточно места для целых чисел 'n'
// https://www.scaler.com/topics/c/dynamic-array-in-c/
int *arr = calloc(n, sizeof(int));
if (arr == NULL) {
printf("Не удается выделить память\n");
return EXIT_FAILURE;
}

// использовать текущее время в качестве
// начальное значение для генератора случайных чисел
srand(time(0));

// сброс первого числа, чтобы не
// повторялось, пока srand не изменяется
rand();

// цикл инициализации массива
for (int i = 0; i < n; i++) {
// целая случайная величина
arr[i] = iRandom(1, 9);
}
printf("Заданный массив: ");
printDynamicArray(n, arr);
printf("\n");

printf("Удаление первого элемента, т.е., arr[0] = %d.\n", arr[0]);
for (int i = 1; i < n; i++) {
arr[i - 1] = arr[i];
}

// перераспределение памяти
arr = realloc(arr, (n - 1) * sizeof(int));

printf("Модифицированный массив: ");
printDynamicArray(n, arr);

// освобождаем выделенную память
free(arr);
return EXIT_SUCCESS;
}

Вывод программы будет таким:

Размер массива: 13
Заданный массив: 4 3 1 9 3 7 4 9 7 1 7 4 8
Удаление первого элемента, т.е., arr[0] = 4.
Модифицированный массив: 3 1 9 3 7 4 9 7 1 7 4 8 8

Давайте рассмотрим приведенный пример более подробно:

  • calloc создала память для n целых чисел, где n - это ввод может получаться путем вводы с клавиатуры.
  • arr содержит ячейку памяти созданной памяти. Мы проверяем, прошло ли распределение успешно, и выходим из программы, если это не так.
  • Для примера в цикле мы перемещаем массив на один элемент влево, чтобы мы могли удалить последний элемент. Обратите внимание, что мы используем синтаксис, подобный массиву, для ссылки на элементы массива.
  • Вызов realloc изменил размер памяти на (n - 1) байт.
  • С помощью вызова free мы освободили память и завершили программу с возвращением 0.