Почему или когда вам нужно динамически выделять память в C?
Динамическое распределение памяти - очень важная тема в программировании на языке C. Однако я не смог найти хорошее объяснение того, что это позволяет нам делать, или почему это требуется.
Нельзя ли просто объявлять переменные и структуры и никогда не использовать malloc()?
Как примечание, какая разница между:
ptr_one = (int *)malloc(sizeof(int));
и
int *ptr_one = malloc(sizeof(int));
Ответы
Ответ 1
Вам нужно использовать динамическую память, если:
- Вы не можете определить максимальный объем памяти для использования во время компиляции;
- Вы хотите выделить очень большой объект;
- Вы хотите создать структуры данных (контейнеры) без фиксированного верхнего размера;
Вы не всегда знаете, сколько памяти вам нужно будет отложить во время компиляции. Представьте себе обработку файла данных (например, временного ряда температур), где количество записей в файле не фиксировано. У вас может быть всего 10 записей или до 100000. Если вы хотите прочитать все эти данные в памяти для его обработки, вы не будете знать, сколько памяти выделяется, пока вы не прочитаете файл. Если файл структурирован так, что самым первым значением является количество записей, вы можете сделать что-то вроде этого:
size_t recs = 0;
double *temps = NULL;
FILE *fp = fopen ( filename, "r" );
if ( fp )
{
if ( fscanf( fp, "%zu", &recs ) == 1 )
{
temps = malloc( sizeof *temps * recs );
if ( temps )
{
// read contents of file into temps
}
}
}
Иногда вам нужно выделить очень большой объект, что-то вроде
int ginormous[1000][1000][1000];
Предполагая целое число в 4 байта, для этого массива потребуется 4 ГБ. К сожалению, кадры стека (где локальные переменные хранятся на большинстве архитектур), как правило, намного меньше этого, поэтому попытка выделить столько памяти может привести к ошибке во время выполнения (и, как правило, это делает). Динамический пул памяти (a.k.a. куча) обычно намного больше, чем стек, а тем более любой стек. так что для чего-то неприятного вам нужно написать что-то вроде
int (*ginormous)[1000][1000] = malloc( sizeof *ginormous * 1000 );
По-прежнему возможно, чтобы запрос вроде этого потерпел неудачу; если ваша куча достаточно размазана, у вас может не быть одного смежного блока, достаточно большого, чтобы передать запрос. Если необходимо, вы можете сделать поэтапное распределение; строки не обязательно будут смежными в памяти, но, скорее всего, вы сможете захватить всю необходимую память:
int ***ginormous = malloc( sizeof *ginormous * 1000 );
if ( ginormous )
{
for ( size_t i = 0; i < 1000; i++ )
{
ginormous[i] = malloc( sizeof *ginormous[i] * 1000 );
if ( ginormous[i] )
{
ginormous[i][j] = malloc ( sizeof *ginormous[i][j] * 1000 );
if ( ginormous[i][j] )
{
// initialize ginormous[i][j][k]
}
}
}
}
И, наконец, динамическая память позволяет создавать контейнеры, которые могут расти и уменьшаться при добавлении или удалении данных, таких как списки, деревья, очереди и т.д. Вы даже можете создать свой собственный реальный "строковый" тип данных, который может расти как вы добавляете к нему символы (аналогично типу string
в С++).
Ответ 2
Динамическое распределение требуется, если вы не знаете требования к наихудшему случаю для памяти. Тогда невозможно статически выделить необходимую память, потому что вы не знаете, сколько вам понадобится.
Даже если вы знаете требования к наихудшему случаю, может быть желательно использовать динамическое распределение памяти. Это позволяет более эффективно использовать системную память несколькими процессами. Все процессы могут статически выполнять свои требования к наихудшей памяти, но это ограничивает количество запущенных процессов в системе. Если никогда не случается, что все процессы используют наихудший случай одновременно, тогда системная память постоянно работает недостаточно, что является пустой тратой ресурсов.
Что касается вашего бокового вопроса, вы не должны передавать результат вызова malloc()
в C. Он может скрыть ошибку отсутствующего объявления (неявные объявления разрешены до C.99) и приводит к undefined. Всегда предпочитайте брать результат malloc()
без приведения. malloc()
объявляется как возвращаемый void *
, а в C всегда допускается преобразование между void *
и другим типом указателя (классификаторы типа модуля, такие как const
).
Ответ 3
Как примечание, какая разница между: ptr_one = (int *)malloc(sizeof(int))
и int *ptr_one = malloc(sizeof(int))
См. .
Во-первых, я знаю, что это, вероятно, смешной вопрос, так как динамическое распределение памяти является очень важной темой в программировании на языке C. Однако я не смог найти хорошее объяснение того, что это позволяет нам делать, или почему это требуется.
Пул памяти (или, чаще всего, куча) очень большой по сравнению со стеком. Рассмотрим эти два примера, почему полезно использовать пул памяти поверх стека:
1. Что делать, если вы определили массив и хотите, чтобы он сохранялся среди нескольких кадров стека? Конечно, вы могли бы объявить его глобальной переменной, и он будет храниться в разделе глобальных данных памяти, однако это будет загромождать, так как ваша программа становится все больше и больше. Кроме того, вы можете сохранить его в пуле памяти.
int *func( int k ) {
assert( k >= 1 );
int *ptr_block = malloc( sizeof( int ) * k );
if ( ptr_block == NULL ) exit( EXIT_FAILURE );
for ( int i = 0; i < k; i++ ) {
ptr_block[ i ] = i + 1;
}
return ptr_block; // Valid.
}
... это, однако, будет не работать, если вы определили свой массив в стеке. Причина состоит в том, что после того, как будет вставлен фрейм стека, все адреса памяти могут использоваться другим фреймом стека (и, следовательно, перезаписаны), тогда как использование памяти из пула памяти будет сохраняться до free
d пользователем (вами или клиентом).
2. Что делать, если вы хотите реализовать динамический массив для обработки прочтения произвольной большой последовательности чисел? Вы не сможете сделать это, определяя свой массив в стеке, вам нужно будет использовать пул памяти. Напомним, что он чрезвычайно распространен (и настоятельно рекомендуется, если вы явно не нуждаетесь в копировании структуры), чтобы передавать указатель на структуру, а не сама структура (поскольку они могут быть довольно большими). Рассмотрим эту небольшую реализацию динамического массива:
struct dyn_array {
int *arr;
int len;
int cap;
};
typedef struct dyn_array *DynArray;
void insert_item( int const item, DynArray dyn_arr ) {
// Checks pre conditions.
assert( dyn_arr != NULL );
// Checks if the capacity is equal to the length. If so, double.
if ( dyn_arr->cap == dyn_arr->len ) {
dyn_arr->cap *= 2;
DynArray new_dyn_arr = malloc( sizeof( int ) * dyn_arr->cap ); // [oo]
// ... copy, switch pointers and free...
}
// ... insert, increase length, etc.
}
... в строке [oo]
обратите внимание, что если это было определено в стеке, то после того, как этот стек стека выскочит, все адреса памяти для массива больше не будут выделены. Смысл, другой кадр стека (вероятно, следующий) будет использовать эти адреса памяти (или некоторые подмножества).
Примечание: Из моего фрагмента кода ptr_block
хранится в стеке: следовательно &ptr_block
- это адрес стека, однако значение ptr_block
находится где-то из пула памяти.