Массивы в языке Си

Массивы служат для хранения множества данных одного типа.

Массив характеризуется типом хранимых данных и размером массива - количество хранимых данных.

Массивы делятся на статические - создаваемые при запуске программы и динамические - создаваемые в процессе выполнения программы.

Массивы бывают одномерные и многомерные. Легко визуализировать массивы с размерностью до трех. Одномерные массивы это хранение элементов в линию, двумерные - прямоугольником, трехмерные - параллелепипедом. Четырехмерный массив - это одномерный массив трехмерных массивов и т.д.

Статические массивы

Статические массивы создаются при запуске программы в статической памяти и существуют всё время выполнения программы.

Статические массивы создаются в области памяти называемой стеком. Размер стека ограничен сильнее чем размер кучи (heap) (динамическая память), поэтому нельзя создать большой статический массив (например в 100 миллионов записей). И в общем случае размер динамического массива может быть больше чем статического.

Размер статического массива должен быть известен на момент запуска программы, поэтому он должен либо быть явно прописан в коде цифрами, либо вставляться через макроподстановки (некоторые компиляторы позволяют использовать переменную для задания размера статического массива). В любом случае вы НЕ МОЖЕТЕ задать размер статического массива во время выполнения программы.

Инициализация статического массива


int a[5]; // Статический массив a состоящий из 5 элементов int. 
int a[5] = {}; // Статический массив инициализированный нулями
static int a[5]; // Статический массив инициализированный нулями
int a[3] = {1, 3, 8}; //Создание и явная инициализация статического массива со значениями 1, 3, 8.
int a[] = {1, 3, 8}; //Создание и явная инициализация статического массива со значениями 1, 3, 8.
int a[2][3];//Двумерный массив из двух строк (row) и трех столбцов (column)

int a[2][3]={ //Двумерный массив с явной инициализацией
  {1, 2, 3},
  {4, 5, 6},
};

int a[][3]={ //Двумерный массив с явной инициализацией . Первую размерность можно не указывать
  {1, 2, 3},
  {4, 5, 6},
};

Ниже приведен пример программы с прямой и косвенной адресацией элементов массива.


#include <stdio.h> // Требуется для prinf()
#define AR_SIZE 10 // Размер массива
int main(){
    int a[10]; // Статический массив размером 10
    #define AR_SIZE 10 // Размер массива можно задать через макроопределение
    int b[AR_SIZE]; // Статический массив размером AR_SIZE
    for(int n = 0; n < AR_SIZE; n++){ // Перебираем массив
        printf("a[%d] = %d\n", n, a[n]); // Выводим значения
        printf("a + %d = %d\n", n, *(a + n)); // Аналогично предыдущей строке (косвенная адресация)
    }
    return 0;
}

Динамические массивы

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

Динамические массивы создаются в динамической памяти называемой кучей. Размер кучи ограничен операционной системой и для 32-х битный систем не может превышать 4 гигабайта.

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

Необходимость ручного выделения памяти под динамические массивы функцией malloc() и освобождения памяти функцией free() усложняет программу и может вызывать утечки памяти. Поэтому, если есть возможность - всегда используйте статические массивы.

Утечкой памяти называют ситуацию когда динамическая память, используемая программой, постоянно растет. Такая ситуация возникает когда выделение памяти (функцией malloc()) происходит чаще чем освобождение (функцией free()). В результате чего динамическая память кончается и программа завершается с ошибкой и теряет работоспособность. Выделение памяти может происходить в цикле и программисту приходится вручную следить чтобы она своевременно освобождалась. Существует множество языков где процессом освобождения памяти занимается так называемый сборщик мусора (garbage collector), но на данный момент данные языки сильно проигрывают Си по производительности. Для маленьких и короткоживущих программ проблема утечки памяти может быть не актуальна, но для программ и сервисов которые работают постоянно это бывает серьезной проблемой, решением которой бывают кривые подходы вроде периодических ручных или автоматических перезапусков программы - не делайте так.

Ниже приведен пример программы с созданием, обработкой и уничтожение одномерного динамического массива.


#include <stdio.h> //printf()
#include <stdlib.h> // malloc(), free()
int main(){
    const int AR_SIZE = 5; // Размер массива
    int* ar = malloc(5 * sizeof(int)); // Создание динамического массива на 5 элементов. Необходимо далее их инициализировать.
    printf("&ar = %p, ar = %p \n", &ar, ar);
    for(int i = 0; i < AR_SIZE; i++){
        ar[i] = i * i; // Инициализация массива
        printf("&ar[%d] = %p, ar[%d] = %d \n", i, &ar[i], i, ar[i]); // Использование массива
    }
    free(ar); // Освобождение памяти
}

Двумерный динамический массив

Работа с двумерным динамическим массивом заметно сложнее чем с одномерным, а на практике размерность массива может быть и больше.


#include <stdio.h> //printf()
#include <stdlib.h> // malloc(), free()
int main(){
    const int ROW = 2, COL = 4; // Размер массива рядов, столбцов
    char **ar = malloc(ROW * sizeof (char *)); // Выделение памяти под указатели на строки (ряды)
    printf("&ar = %p, ar = %p \n", &ar, ar); // Адрес массива
    for (int i = 0; i < ROW; i++){ // Перебираем строки (ряды)
        ar[i] = malloc(COL * sizeof(char)); // Выделение памяти под хранение столбцов
        printf("&ar[%d] = %p, ar[%d] = %p \n", i, &ar[i], i, ar[i]); // Адреса массивов
        for(int n = 0; n < COL; n++){ // Перебираем элементы столбца(ов)
            ar[i][n] = i * COL + n; // Заполняем значения массива возростающими значениями
        }
    }
    for (int i = 0; i < ROW; i++){ // Перебираем строки (ряды)
        for(int n = 0; n < COL; n++){ // Перебираем элементы столбца(ов)
            printf("&ar[%d][%d] = %p, ar[%d][%d] = %d \n", i, n, &ar[i][n], i, n, ar[i][n]); // Выводим значения массива
        }
    }
    for (int i = 0; i < ROW; i++){ // Перебираем строки (ряды)
        free(ar[i]); // Освобождение памяти
    }
    free(ar); // Освобождение памяти
}
2023-12-18



Понравилась страница?
Добавить в закладки
Или поделиться!

Связанные темы

Статические и динамические массивы в языке Си. Описание и примеры кода.

#include void * malloc(size_t size_in_bytes); // size_in_bytes - Размер памяти требуемый для выделения, в байтах. // Возвращаемое значение - указатель на начало блока непрерывной выделенной памяти. Или ноль в случае неудачи.
memccpy() функция языка Си. Копирование блока памяти с поиском символа.
memcpy() функция языка Си. Копирование области памяти.
memmove() функция языка Си. Перемещение областей памяти.
restrict ключевое слово языка Си.
sizeof оператор в языке Си. Узнать размер переменной.