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

Где выделяется память под массив

  • автор:

Как выделить память под массив внутри функции

Но собственно так как он без указания размера, это не правильно. В функции надо выделить под него память, в зависимости от введенного n.

void input(int *a, int *n) < char temp; if ((scanf("%d%c", n, &temp) == 1) || (temp == '\n')) < a = (int*)malloc((*n) * sizeof(int)); 

Как это сделать правильно? Чтобы массив вернулся в инт.
Отслеживать
219k 15 15 золотых знаков 119 119 серебряных знаков 230 230 бронзовых знаков
задан 31 авг 2022 в 3:29

1 ответ 1

Сортировка: Сброс на вариант по умолчанию

Вообще-то в int массив никак не вернуть, потому что массив — это по сути указатель на первый элемент, а размеры int и указателя могут и не совпадать.

Далее, при передаче аргумента в функцию по значению создается копия, так что даже если бы ваш input работал, переданная переменная-указатель не изменялась бы. Странно, что при этом n вы передаете правильно.

И — по мелочи — если у вас в самом деле С, то в = (int*)malloc приведение (int*) не нужно.

int* input(int *n) < if (scanf("%d", n) == 1) < return malloc((*n) * sizeof(int)); >else return NULL; > int main()

Динамическая память

При создании массива с фиксированными размерами под него выделяется определенная память. Например, пусть у нас будет массив с пятью элементами:

double numbers[5] = ;

Для такого массива выделяется память 5 * 8 (размер типа double) = 40 байт. Таким образом, мы точно знаем, сколько в массиве элементов и сколько он занимает памяти. Однако это не всегда удобно. Иногда бывает необходимо, чтобы количество элементов и соответственно размер выделяемой памяти для массива определялись динамически в зависимости от некоторых условий. Например, пользователь сам может вводить размер массива. И в этом случае для создания массива мы можем использовать динамическое выделение памяти.

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

    malloc() . Имеет прототип

void *malloc(unsigned s);
void *calloc(unsigned n, unsigned m);
void *realloc(void *bl, unsigned ns);
void *free(void *bl);

malloc

Функция malloc() выделяет память длиной для определенного количества байт и возвращает указатель на начало выделенной памяти. Через полученный указатель мы можем помещать данные в выделенную память. Рассмотрим простой пример:

#include #include // для подключения функции malloc int main(void) < int *ptr = malloc(sizeof(int)); // выделяем память для одного int *ptr = 24; // помещаем значение в выделенную память printf("%d \n", *ptr); free(ptr); >

Здесь с помощью функции malloc выделяется память для одного объекта int . Чтобы узнать, сколько байтов надо выделить, передаем в функцию malloc размер типа int на текущей и в результате получаем указатель ptr , который указывает на выделенную память

int *ptr = malloc(sizeof(int));

То есть поскольку int на большинстве архитектур занимает 4 байта, то в большинстве случаев будет выделяться память объемом в 4 байта. Стоит отметить, что мы также могли бы получить размер через разыменование указателя:

int *ptr = malloc(sizeof *ptr);

Для универсальности возвращаемого значения в качестве результата функция malloc() (как и calloc() и realloc() ) возвращает указатель типа void * . Но в нашем случае создается массив типа int, для управления которым используется указатель типа int * , поэтому выполняется неявное приведение результата функции malloc к типу int * .

Далее через этот указатель с помощью операции разыменования помещаем в выделенный участок памяти число 24:

*ptr = 24;

В дальнейшем, используя указатель, можно получить значение из выделенного участка памяти:

printf("%d \n", *ptr);

После завершения работы освобождаем память, передавая указатель в функцию free() :

free(ptr);

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

#include #include // для подключения функции malloc int main(void) < int *ptr = malloc(sizeof(int)); // выделяем память для одного int if(ptr != NULL) < *ptr = 24; // помещаем значение в выделенную память printf("%d \n", *ptr); >free(ptr); >

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

free(ptr); ptr = NULL;
Выделение памяти для массива

Подобным образом можно выделять память и под набор объектов. Например, выделим память для массива из 4-х чисел int:

#include #include int main(void) < int n = 4; int *ptr = malloc(n * sizeof(int)); // выделяем память для 4-х чисел int if(ptr) < // помещаем значения в выделенную память ptr[0] = 1; ptr[1] = 2; ptr[2] = 3; ptr[3] = 5; // получаем значения for(int i = 0; i < n; i++) < printf("%d", ptr[i]); >> free(ptr); >
Выделение памяти для структуры

Аналогичным образом можно выделять память для одной или набора структур:

#include #include struct person < char* name; int age; >; int main(void) < // выделяем память для одной структуры person struct person *ptr = malloc(sizeof(struct person)); if(ptr) < // помещаем значения в выделенную память ptr->name = "Tom"; ptr->age = 38; // получаем значения printf("%s : %d", ptr->name, ptr->age); // Tom : 38 > free(ptr); return 0; >

calloc

Функция calloc() имеет прототип

void *calloc(unsigned n, unsigned m);

Она выделяет память для n элементов по m байт каждый и возвращает указатель на начало выделенной памяти. В случае неудачного выполнения возвращает NULL

В отличие от функции malloc() она инициализирует все выделенные байты памяти нулями. Например, выделим память для одного объекта int :

#include #include int main(void) < // выделяем память для одного объекта int int *ptr = calloc(1, sizeof(int)); if(ptr) < // получаем значение по умолчанию - 0 printf("Initial value: %d", *ptr); // Initial value: 0 // устанавливаем новое значение *ptr = 15; // получаем новое значение printf("New value: %d", *ptr); // New value: 15 >free(ptr); return 0; >
Initial value: 0 New value: 15

Подобным образом можно выделить память и для других объектов. Например, выделим память для массива из 4-х объектов int :

#include #include int main(void) < // выделяем память для 4-х объектов int int n = 4; int *ptr = calloc(n, sizeof(int)); if(ptr) < // устанавливаем значения ptr[0] = 1; ptr[1] = 2; ptr[2] = 3; ptr[3] = 5; // получаем значения for(int i = 0; i < n; i++) < printf("%d", ptr[i]); >> free(ptr); >

realloc

Функция realloc() позволяет изменить размер памяти, ранее выделенной с помощью функций malloc() b calloc() . Имеет прототип

void *realloc(void *bl, unsigned ns);

Первый параметр представляет указатель на ранее выделенный блок памяти. А второй параметр представляет новый размер блока памяти в байтах.

Если указатель bl имеет значение NULL , то есть память не выделялась, то действие функции аналогично действию malloc

Рассмотрим небольшой пример:

#include #include int main(void) < // выделяем память для 1-го объекта int int size = sizeof(int); int *ptr = malloc(size); if(ptr) < // отображаем адрес и размер памяти printf("Addresss: %p \t Size: %d\n", (void*)ptr, size); >// расширяем память до размера 4-х объектов int size = 4 * sizeof(int); int *ptr_new = realloc(ptr, size); // если выделение памяти прошло успещно if(ptr_new) < printf("Reallocation\n"); // заново отображаем адрес и размер памяти printf("Addresss: %p \t Size: %d\n", (void*)ptr_new, size); free(ptr_new); // освобождаем новый указатель >else < free(ptr); // освобождаем старый указатель >>

Здесь сначала выделяем память для одного объекта int с помощью функции malloc.

int size = sizeof(int); int *ptr = malloc(size);

Если память успешно выделена, то выводим на консоль адрес и размер выделенного блока памяти. Затем с помощью функции realloc расширяем память до 4 объектов int

size = 4 * sizeof(int); int *ptr_new = realloc(ptr, size);

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

Консольный вывод в моем случае

Addresss: 0000018B078A82F0 Allocated: 4 Reallocation Addresss: 0000018B078A82F0 Allocated: 16

Стоит отметить, что нам необязательно создавать новый указатель, мы можем присвоить значение старому указателю:

#include #include int main(void) < int size = sizeof(int); int *ptr = malloc(size); if(ptr) < printf("Addresss: %p \t Allocated: %d\n", (void*)ptr, size); >size = 4 * sizeof(int); ptr = realloc(ptr, size); // используем старый указатель if(ptr) < printf("Reallocation\n"); printf("Addresss: %p \t Allocated: %d\n", (void*)ptr, size); >free(ptr); >

Где выделяется память под массив

Для работы с динамической памятью в языке С используются следующие функции: malloc, calloc, free, realloc.
Рассмотрим их подробнее.

void *malloc(size_t size);

В качестве входного параметра функция принимает размер памяти, которую требуется выделить. Возвращаемым значением является указатель на выделенный в куче участок памяти.
Для выделения памяти под 1 000 000 int`ов необходимо выполнить следующий код:

int * p = malloc(1000000*sizeof(int));

В языке С++ потребуется небольшая модификация данной кода (из-за того, что в С++ нет неявного приведения указателей):

int * p = (int *) malloc(1000000*sizeof(int));

Если ОС не смогла выделить память (например, памяти не хватило), то malloc возвращает 0.

После окончания работы с выделенной динамически памятью нужно освободить ее. Для этой цели используется функция free, которая возвращает память под управление ОС.

void free(void *ptr);

В качестве входного параметра в free нужно передать указатель, значение которого полученно из функции malloc. Вызов free на указателях полученных не из malloc (например, free(p+10)) приведет к неопределенному поведению. Это связанно с тем, что при выделении памяти при помощи malloc в ячейки перед той, на которую указывает возвращаемый функцией указатель операционная система записывает служебную информацию (см. рис.). При вызове free(p+10) информация находящаяся перед ячейкой (p+10) будет трактоваться как служебная.

void *calloc(size_t nmemb, size_t size);

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

int * q = (int *) calloc(1000000, sizeof(int))

q будет указывать на начало массива из миллиона int`ов инициализированных нулями.

void *realloc(void *ptr, size_t size);

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

Какие бывают ошибки:

1. Потеря памяти

int * p = (int *) malloc(100);
p = (int *) malloc(200); // потерян указатель на первые 100 int`ов, которые теперь нельзя отдать обратно ОС

2.Повторное освобождение выделенной памяти

free(p); … free(p); // неопределенное поведение

free(p);
p = 0;

free(p); // отработает без ошибок

Работа с динамической памятью в С++

В С++ есть свой механизм выделения и освобождения памяти — это функции new и delete.

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

int * p = new int[1000000]; // выделение памяти под 1000000 int`ов

Т.е. при использовании функции new не нужно приводить указатель и не нужно использовать sizeof().
Освобождение выделенной при помощи new памяти осуществляется посредством следующего вызова:

Если требуется выделить память под один элемент, то можно использовать

int * q = new int;

int * q = new int(10); // выделенный int проинциализируется значением 10

в этом случае удаление будет выглядеть следующим образом:

  1. При динамическом выделении памяти в ней помимо значения указанного типа будет храниться служебная информация ОС и С/С++. Таким образом потребуется гораздо больше памяти, чем при хранении необходимых данных на стеке.
  2. Если в памяти хранить большое количество маленьких кусочков, то она будет сильно фрагментирована и большой массив данных может не поместиться.

Многомерные массивы.

new позволяет выделять только одномерные массивы, поэтому для работы с многомерными массивами необходимо воспринимать их как массив указателей на другие массивы.
Для примера рассмотрим задачу выделения динамической памяти под массив чисел размера n на m.

1ый способ

На первом шаге выделяется указатель на массив указателей, а на втором шаге, в цикле каждому указателю из массива выделяется массив чисел в памяти:

int ** a = new int*[n];
for (int i = 0; i != n; ++i)
a[i] = new int[m];

Однако, этот способ плох тем, что в нём требуется n+1 выделение памяти, а это достаточно дорогая по времени операция.

2ой способ

На первом шаге выделение массива указателей и массива чисел размером n на m. На втором шаге каждому указателю из массива ставится в соответствие строка в массиве чисел.

int ** a = new int*[n];
a[0] = new int[n*m];
for (int i = 1; i != n; ++i)
a[i] = a[0] + i*m;

В данном случае требуется всего 2 выделения памяти.

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

for (int i = 0; i != n; ++i)
delete [] a[i];
delete [] a;

delete [] a[0];
delete [] a;

Таким образом, второй способ опять же требует гораздо меньше вызовов функции delete [], чем первый.

Где выделяется память под массив

Скачай курс
в приложении

Перейти в приложение
Открыть мобильную версию сайта

© 2013 — 2023. Stepik

Наши условия использования и конфиденциальности

Get it on Google Play

Public user contributions licensed under cc-wiki license with attribution required

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *