В реальном программировании часто бывает, что требуемый объём памяти зависит от фактических входных данных и не может быть предопределён. Такие задачи трудно решить с помощью статических массивов. Для решения вышеуказанных проблем язык C предоставляет некоторые функции управления памятью. Эти функции управления памятью в сочетании с указателями позволяют динамически выделять пространство памяти по мере необходимости для построения динамических массивов, а также освобождать неиспользуемое пространство для последующего использования, обеспечивая средство эффективного использования ресурсов памяти.
Динамический массив — понятие, противоположное статическому массиву. Длина статического массива задаётся заранее, и после того как его размер указан в программе, изменить его нельзя. Динамический массив отличается; его размер можно изменять по мере необходимости программы. Память для динамического массива выделяется из кучи (динамическое выделение). Память для него выделяется путём выполнения кода. Она выделяется только тогда, когда программа выполняет эти операторы. Программист отвечает за освобождение памяти.
Зачем использовать динамические массивы?
В реальном программировании часто бывает, что требуемый объём памяти зависит от фактических входных данных и не может быть предопределён. Такие задачи трудно решить с помощью статических массивов. Для решения вышеуказанных проблем язык C предоставляет некоторые функции управления памятью. Эти функции управления памятью в сочетании с указателями позволяют динамически выделять пространство памяти по мере необходимости для построения динамических массивов, а также освобождать неиспользуемое пространство для последующего использования, обеспечивая средство эффективного использования ресурсов памяти.
Сравнение динамических массивов и статических массивов
Для статических массивов их создание очень удобно, их не нужно освобождать после использования, а обращение к ним простое. Однако их фатальный недостаток в том, что после создания их размер нельзя изменить!
Для динамических массивов их создание затруднительно, а после использования программист должен освободить их сам; иначе это может серьезно привести к утечкам памяти. Однако их использование очень гибкое, так как они могут динамически выделять размер в соответствии с потребностями программы.
Как построить динамический массив
Принципы, которых следует придерживаться
При выделении выделяйте слой за слоем от внешнего слоя к внутреннему.
При освобождении освобождайте слой за слоем от внутреннего слоя к внешнему.
Указатели, необходимые для построения
Для построения одномерного динамического массива нужен одномерный указатель.
Для двух измерений нужны одномерные и двумерные указатели.
Для трех измерений требуются одномерные, двумерные и трехмерные указатели.
И так далее.
Функции, необходимые для конструирования
| Прототип функции | Return | Описание функции |
| void *malloc(unsigned int size); | При успехе: возвращает начальный адрес выделенного пространства. При неудаче: возвращает нулевой указатель. | Выделяет из системы пространство кучи размером size байт. |
| void *calloc(unsigned int num, unsigned int size); | При успехе: возвращает начальный адрес выделенного пространства. При неудаче: возвращает нулевой указатель. | Выделяет пространство кучи для num элементов размером size байт каждый. |
| void free(void *p); | Нет возвращаемого значения | Освобождает пространство кучи, на которое указывает p. |
| void *realloc(void *p,unsigned int size); | При успехе: возвращает начальный адрес вновь выделенного пространства. При неудаче: возвращает нулевой указатель. | Изменяет пространство кучи, на которое указывает p, до размера size. |
Пояснение:
-
(1) Он указан как тип void *. Это не означает, что функция после вызова не имеет возвращаемого значения, а означает, что она возвращает адрес узла. Тип этого адреса — void (без типа или неопределённый тип), то есть начальный адрес области памяти. Его конкретный тип нельзя определить, пока он не будет использован на основе данных каждого поля. Его можно преобразовать к другим типам с помощью приведения. Например: double *pd=NULL; pd=(double *)calloc(10,sizeof(double)); Это означает запрос у системы 10 последовательных областей хранения типа double и использование указателя pd для указания на начальный адрес этой последовательной области. Кроме того, (double) используется для преобразования возвращаемого типа calloc(), чтобы адрес данных типа double можно было присвоить указателю pd.
-
(2) Цель использования sizeof — вычислить количество байтов, занимаемых типом, чтобы соответствовать разным компиляторам.
(3) Поскольку динамическое выделение памяти не всегда может быть успешным, следует добавить процедуру обработки исключений, чтобы программа не останавливалась и не оставляла пользователя в недоумении. Обычно используется такой фрагмент обработки исключений: if(p==NULL) /* orif(!p)*/ { printf("Dynamic申pleaseMemoryfailure!\n"); exit(1); //ExceptionLogout }
-
(4) Заголовочные файлы для этих четырёх функций все включены в
в. -
(5) Выделенное пространство кучи не имеет имени; его можно найти только через возвращённый указатель.
-
(6) Никогда не применяйте free к блоку памяти, выделенному не динамически. Нельзя также освобождать одну и ту же область памяти дважды с помощью free. Например: free(p);free(p);
(7) При вызове free() память, на которую указывает переданный указатель, освобождается, но значение указателя в вызывающей функции может остаться неизменным, поскольку p передаётся в функцию как формальный параметр. Строго говоря, значение освобождённого указателя недействительно, так как он больше не указывает на выделенную область памяти. Любое его использование в этот момент может вызвать проблемы.
Разница между malloc и calloc
Для области памяти, выделенной с помощью malloc, если она ранее не использовалась, каждый бит в ней может быть равен 0; наоборот, если эта часть памяти ранее выделялась, освобождалась и выделялась повторно, она может содержать всевозможные оставшиеся данные. Иными словами, программа, использующая malloc(), может работать нормально вначале (когда пространство памяти ещё не было повторно выделено), но через некоторое время (после повторного выделения пространства памяти) могут возникнуть проблемы. Поэтому её необходимо инициализировать перед использованием (можно использовать memset для инициализации нулями). Однако пространство, выделенное вызовом calloc(), уже инициализировано нулями в момент выделения. При выборе между calloc() и malloc() нужно учитывать, следует ли инициализировать выделяемое пространство памяти, и затем выбирать соответствующую функцию.
Конкретный способ построения
Возьмём в качестве примера трёхмерный целочисленный массив array[n1][n2][n3].
Сначала следуем принципу выделения памяти слой за слоем от внешнего слоя к внутреннему:
Самый внешний указатель — array, это трёхмерный указатель. То, на что он указывает, — array[], который является двумерным указателем. Поэтому для array
выделение памяти должно быть:
array=(int***)calloc(n1,sizeof(int**));
Следующий слой указателя — array[], это двумерный указатель. То, на что он указывает, — array[][], который является одномерным указателем. Поэтому для array[]
выделение памяти должно быть:
for(i=0;i<n1;i++)
{
array[i]=(int**)calloc(n2,sizeof(int*));
}
Самый внутренний указатель — array[][], это одномерный указатель. То, на что он указывает, — array[][][], который является целочисленной константой. Поэтому чтобы выделить память для array[][], следует:
for(i=0;i<n1;i++)
{
for(j=0;j<n2;j++)
{
array[i][j]=(int*)calloc(n3,sizeof(int));
}
}
Конечно, можно объединить их вместе так:
int i,j,k;
int n1,n2,n3;
int ***array;
scanf("%d%d%d",&n1,&n2,&n3);
array=(int***)calloc(n1,sizeof(int**));
for(i=0;i<n1;i++)
{
array[i]=(int**)calloc(n2,sizeof(int*));
for(j=0;j<n2;j++)
{
array[i][j]=(int*)calloc(n3,sizeof(int));
for(k=0;k<n3;k++)
{
array[i][j][k]=i+j+k+1;
}
}
}
Наконец, не забудьте освободить эту память. При этом следует придерживаться принципа освобождения слой за слоем от внутреннего слоя к внешнему.
Что касается процесса анализа, обратитесь к решению выше; здесь я не буду вдаваться в подробности. Приведу лишь код:
for(i=0;i<n1;i++)
{
for(j=0;j<n2;j++)
{
free(array[i][j]);//释放第三维指针
}
}
for(i=0;i<n1;i++)
{
free(array[i]);//释放第二维指针
}
free(array);//释放第一维指针
Процесс создания других размерностей, например четырёхмерной, во многом такой же, поэтому здесь я не буду вдаваться в подробности.