Выделение памяти для программы в с. Динамическая память. Каким образом выделить память оператором new с перехватом критической ситуации, при которой память может не выделиться? Исключительная ситуация bad_alloc. Пример

Динамическое выделение памяти

Основные проблемы применения

Нулевой указатель

Нулевой указатель − это указатель, хранящий специальное значение, используемое для того, чтобы показать, что данная переменная-указатель не ссылается (не указывает) ни на какой объект. В различных языках программирования представлен различными константами.

·В языках C# и Java: null

·В языках Си и C++: 0 или макрос NULL. Кроме того, в стандарте C++11 для обозначения нулевого указателя предложено новое ключевое слово nullptr

·В языках Паскаль и Ruby: nil

·В языке Компонентный Паскаль:NIL

·В языке Python: None

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

Для решения части проблем есть методы предохранения и страховки:

Изучив указатели в языке Си, мы открыли для себя возможности динамического выделения памяти. Что это значит? Это значит то, что при динамическом выделении памяти, память резервируется не на этапе компиляции а на этапе выполнения программы. И это дает нам возможность выделять память более эффективно, в основном это касается массивов. С динамическим выделением память, нам нет необходимости заранее задавать размер массива, тем более, что не всегда известно, какой размер должен быть у массива. Далее рассмотрим каким же образом можно выделять память.

Функция malloc() определена в заголовочном файле stdlib.h, она используется для инициализации указателей необходимым объемом памяти. Память выделяется из сектора оперативной памяти доступного для любых программ, выполняемых на данной машине. Аргументом функции malloc() является количество байт памяти, которую необходимо выделить, возвращает функция - указатель на выделенный блок в памяти. Функция malloc() работает также как и любая другая функция, ничего нового.

Так как различные типы данных имеют разные требования к памяти, мы как-то должны научиться получить размер в байтах для данных разного типа. Например, нам нужен участок памяти под массив значений типа int - это один размер памяти, а если нам нужно выделить память под массив того же размера, но уже типа char - это другой размер. Поэтому нужно как-то вычислять размер памяти. Это может быть сделано с помощью операции sizeof(), которая принимает выражение и возвращает его размер. Например, sizeof(int) вернет количество байтов, необходимых для хранения значения типа int. Рассмотрим пример:


Яндекс.Директ


#include int *ptrVar = malloc(sizeof(int));

В этом примере, встроке 3 указателю ptrVar присваивается адрес на участок памяти, размер которого соответствует типу данных int. Автоматически, этот участок памяти становится недоступным для других программ. А это значит, что после того, как выделенная память станет ненужной, её нужно явно высвободить. Если же память не будет явно высвобождена, то по завершению работы программы, память так и не освободится для операционной системы, это называется утечкой памяти. Также можно определять размер выделяемой памяти, которую нужно выделить передавая пустой указатель, вот пример:



Как видите, в такой записи есть одна очень сильная сторона, мы не должны вызывать функцию malloc() с использованиемsizeof(float). Вместо этого мы передали в malloc() указатель на тип float, в таком случае, размер выделяемой памяти автоматически определится сам!

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


float *ptrVar; /* . . . сто строк кода */ . . . ptrVar = malloc(sizeof(*ptrVar));

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

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

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

· Память под каждую из глобальных и статических (объявленных со спецификатором static) переменных выделяется до начала выполнения программы в соответствии с описанием типа. От начала до конца выполнения программы данные переменные связаны с выделенной для них областью памяти. Таким образом, они имеют глобальное время жизни, при этом область видимости у них различная.

· Для локальных переменных, объявленных внутри какого-либо блока и не имеющих спецификатора static, память выделяется другим способом. До начала выполнения программы (при её загрузке) выделяется довольно объёмная область памяти, называемая стеком (иногда используют термины стек программы или стек вызовов , чтобы сделать различие между стеком как абстрактным типом данных). Размер стека зависит от среды разработки, например, в MS Visual C++ по умолчанию под стек выделяется 1 мегабайт (это значение поддаётся настройке). В процессе выполнения программы при входе в определённый блок выделяется память в стеке для локализованных в блоке переменных (в соответствии с описанием их типа), при выходе из блока эта память освобождается. Данные процессы выполняются автоматически, поэтому локальные переменные в С++ часто называют автоматическими .

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

Использование термина "стек" объяснить легко – при принятом подходе к выделению и освобождению памяти переменные, которые помещаются в стек последними (это переменные, локализованные в самом глубоко вложенном блоке), удаляются из него первыми. То есть, выделение и освобождение памяти происходит по принципу LIFO (LAST IN – FIRST OUT, последним пришёл – первым вышел). Это и есть принцип работы стека. Стек как динамическую структуру данных и его возможную реализацию мы рассмотрим в следующем разделе.



Во многих случаях статическое выделение памяти ведет к ее неэффективному использованию (особенно это характерно для массивов больших размеров), т. к. не всегда выделенная статически область памяти реально заполняется данными. Поэтому в С++, как и во многих языках, есть удобные средства динамического формирования переменных. Суть динамического выделения памяти заключается в том, что память выделяется (захватывается) по запросу из программы и освобождается также по запросу. При этом размер памяти может определяться типом переменной или явно указываться в запросе. Такие переменные называются динамическими . Возможности создания и использования динамических переменных тесно связаны с механизмом указателей.

Суммируя всё сказанное выше, можно представить следующую схему распределения памяти в процессе исполнения программы (рисунок 2.1). Расположение областей друг относительно друга на рисунке довольно условное, т.к. детали выделения памяти берёт на себя операционная система.

Рисунок 2.1 – схема распределения памяти

В заключение этого раздела коснёмся одной болезненной проблемы в процессе работы со стеком – возможности его переполнения (эта аварийная ситуация обычно называется Stack Overflow ). Причина, породившая проблему, понятна – ограниченный объём памяти, которая выделяется под стек при загрузке программы. Наиболее вероятные ситуации для переполнения стека – локальные массивы больших размеров и глубокая вложенность рекурсивных вызовов функций (обычно возникает при неаккуратном программировании рекурсивных функций, допустим, забыта какая-либо терминальная ветвь).



Для того, чтобы лучше понять проблему переполнения стека, советуем провести такой нехитрый эксперимент. В функции main объявите массив целых чисел размером, допустим, на миллион элементов. Программа скомпилируется, но при её запуске возникнет ошибка переполнения стека. Теперь добавьте в начало описания массива спецификатор static (или вынесите описание массива из функции main ) – программа заработает!

Ничего чудесного в этом нет – просто теперь массив помещается не в стек, а в область глобальных и статических переменных. Размер памяти для этой области определяет компилятор – если программа скомпилировалась, значит, она будет работать.

Тем не менее, объявлять в программе статически формируемые массивы огромных размеров, как правило, нет необходимости. В большинстве случаев более эффективным и гибким способом будет динамическое выделение памяти для таких данных.

Динамическое выделение памяти необходимо для эффективного использования памяти компьютера. Например, мы написали какую-то программку, которая обрабатывает массив. При написании данной программы необходимо было объявить массив, то есть задать ему фиксированный размер (к примеру, от 0 до 100 элементов). Тогда данная программа будет не универсальной, ведь может обрабатывать массив размером не более 100 элементов. А если нам понадобятся всего 20 элементов, но в памяти выделится место под 100 элементов, ведь объявление массива было статическим, а такое использование памяти крайне не эффективно.

В С++ операции new и delete предназначены для динамического распределения памяти компьютера. Операция new выделяет память из области свободной памяти, а операция delete высвобождает выделенную память. Выделяемая память, после её использования должна высвобождаться, поэтому операции new и delete используются парами. Даже если не высвобождать память явно, то она освободится ресурсами ОС по завершению работы программы. Рекомендую все-таки не забывать про операцию delete .

// пример использования операции new int *ptrvalue = new int; //где ptrvalue – указатель на выделенный участок памяти типа int //new – операция выделения свободной памяти под создаваемый объект.

Операция new создает объект заданного типа, выделяет ему память и возвращает указатель правильного типа на данный участок памяти. Если память невозможно выделить, например, в случае отсутствия свободных участков, то возвращается нулевой указатель, то есть указатель вернет значение 0. Выделение памяти возможно под любой тип данных: int , float ,double , char и т. д.

// пример использования операции delete: delete ptrvalue; // где ptrvalue – указатель на выделенный участок памяти типа int // delete – операция высвобождения памяти

Разработаем программу, в которой будет создаваться динамическая переменная.

// new_delete.cpp: определяет точку входа для консольного приложения. #include "stdafx.h" #include << "ptrvalue = " << *ptrvalue << endl; delete ptrvalue; // высвобождение памяти system("pause"); return 0; }

// код Code::Blocks

// код Dev-C++

// new_delete.cpp: определяет точку входа для консольного приложения. #include using namespace std; int main(int argc, char* argv) { int *ptrvalue = new int; // динамическое выделение памяти под объект типа int *ptrvalue = 9; // инициализация объекта через указатель //int *ptrvalue = new int (9); инициализация может выполнятся сразу при объявлении динамического объекта cout << "ptrvalue = " << *ptrvalue << endl; delete ptrvalue; // высвобождение памяти return 0; }

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

Ptrvalue = 9 Для продолжения нажмите любую клавишу. . .

Рисунок 1 — Динамическая переменная

Создание динамических массивов

Как было сказано раньше, массивы также могут быть динамическими. Чаще всего операции new и delete применяются для создания динамических массивов, а не для создания динамических переменных. Рассмотрим фрагмент кода создания одномерного динамического массива.

// объявление одномерного динамического массива на 10 элементов: float *ptrarray = new float ; // где ptrarray – указатель на выделенный участок памяти под массив вещественных чисел типа float // в квадратных скобочках указываем размер массива

После того как динамический массив стал ненужным, нужно освободить участок памяти, который под него выделялся.

// высвобождение памяти отводимой под одномерный динамический массив: delete ptrarray;

После оператора delete ставятся квадратные скобочки, которые говорят о том, что высвобождается участок памяти, отводимый под одномерный массив. Разработаем программу, в которой создадим одномерный динамический массив, заполненный случайными числами.

// new_delete_array.cpp: определяет точку входа для консольного приложения. #include "stdafx.h" #include // в заголовочном файле // в заголовочном файле < 10; count++) ptrarray = (rand() % 10 + 1) / float((rand() % 10 + 1)); //заполнение массива случайными числами с масштабированием от 1 до 10 cout << "array = "; for (int count = 0; count < 10; count++) cout << setprecision(2) << ptrarray << " "; delete ptrarray; // высвобождение памяти cout << endl; system("pause"); return 0; }

// код Code::Blocks

// код Dev-C++

// new_delete_array.cpp: определяет точку входа для консольного приложения. #include // в заголовочном файле содержится прототип функции time() #include // в заголовочном файле содержится прототип функции setprecision() #include #include using namespace std; int main(int argc, char* argv) { srand(time(0)); // генерация случайных чисел float *ptrarray = new float ; // создание динамического массива вещественных чисел на десять элементов for (int count = 0; count < 10; count++) ptrarray = (rand() % 10 + 1) / float((rand() % 10 + 1)); //заполнение массива случайными числами с масштабированием от 1 до 10 cout << "array = "; for (int count = 0; count < 10; count++) cout << setprecision(2) << ptrarray << " "; delete ptrarray; // высвобождение памяти cout << endl; system("pause"); return 0; }

Созданный одномерный динамический массив заполняется случайными вещественными числами, полученными c помощью функций генерации случайных чисел, причём числа генерируются в интервале от 1 до 10, интервал задается так — rand() % 10 + 1 . Чтобы получить случайные вещественные числа, выполняется операция деления, с использованием явного приведения к вещественному типу знаменателя — float((rand() % 10 + 1)) . Чтобы показать только два знака после запятой используем функцию setprecision(2) , прототип данной функции находится в заголовочном файле . Функция time(0) засевает генератор случайных чисел временным значением, таким образом, получается, воспроизводить случайность возникновения чисел (см. Рисунок 2).

Array = 0.8 0.25 0.86 0.5 2.2 10 1.2 0.33 0.89 3.5 Для продолжения нажмите любую клавишу. . .

Рисунок 2 — Динамический массив в С++

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

Как создавать и работать с одномерными динамическими массивами мы научились. Теперь рассмотрим фрагмент кода, в котором показано, как объявляется двумерный динамический массив.

// объявление двумерного динамического массива на 10 элементов: float **ptrarray = new float* ; // две строки в массиве for (int count = 0; count < 2; count++) ptrarray = new float ; // и пять столбцов // где ptrarray – массив указателей на выделенный участок памяти под массив вещественных чисел типа float

Сначала объявляется указатель второго порядка float **ptrarray , который ссылается на массив указателей float* ,где размер массива равен двум. После чего в цикле for каждой строке массива объявленного в строке 2 выделяется память под пять элементов. В результате получается двумерный динамический массив ptrarray .Рассмотрим пример высвобождения памяти отводимой под двумерный динамический массив.

// высвобождение памяти отводимой под двумерный динамический массив: for (int count = 0; count < 2; count++) delete ptrarray; // где 2 – количество строк в массиве

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

// new_delete_array2.cpp: определяет точку входа для консольного приложения. #include "stdafx.h" #include #include #include < 2; count++) ptrarray = new float ; // и пять столбцов // заполнение массива for (int count_row = 0; count_row < 2; count_row++) for (int count_column = 0; count_column < 5; count_column++) ptrarray = (rand() % 10 + 1) / float((rand() % 10 + 1)); //заполнение массива случайными числами с масштабированием от 1 до 10 // вывод массива for (int count_row = 0; count_row < 2; count_row++) { for (int count_column = 0; count_column < 5; count_column++) cout << setw(4) <

// код Code::Blocks

// код Dev-C++

// new_delete_array2.cpp: определяет точку входа для консольного приложения. #include #include #include #include using namespace std; int main(int argc, char* argv) { srand(time(0)); // генерация случайных чисел // динамическое создание двумерного массива вещественных чисел на десять элементов float **ptrarray = new float* ; // две строки в массиве for (int count = 0; count < 2; count++) ptrarray = new float ; // и пять столбцов // заполнение массива for (int count_row = 0; count_row < 2; count_row++) for (int count_column = 0; count_column < 5; count_column++) ptrarray = (rand() % 10 + 1) / float((rand() % 10 + 1)); //заполнение массива случайными числами с масштабированием от 1 до 10 // вывод массива for (int count_row = 0; count_row < 2; count_row++) { for (int count_column = 0; count_column < 5; count_column++) cout << setw(4) <

При выводе массива была использована функция setw() , если вы не забыли, то она отводит место заданного размера под выводимые данные. В нашем случае, под каждый элемент массива по четыре позиции, это позволяет выровнять, по столбцам, числа разной длинны (см. Рисунок 3).

2.7 10 0.33 3 1.4 6 0.67 0.86 1.2 0.44 Для продолжения нажмите любую клавишу. . .

Рисунок 3 — Динамический массив в С++

Динамическое и статическое выделение памяти. Преимущества и недостатки. Выделение памяти для одиночных переменных операторами new и delete . Возможные критические ситуации при выделении памяти. Инициализация при выделении памяти

1. Динамическое и статическое (фиксированное) выделение памяти. Главные различия

Для работы с массивами информации, программы должны выделять память для этих массивов. Для выделения памяти под массивы переменных используются соответствующие операторы, функции и т.п.. В языке программирования C++ выделяют следующие способы выделения памяти:

1. Статическое (фиксированное ) выделение памяти. В этом случае память выделяется только один раз во время компиляции. Размер выделенной памяти есть фиксированным и неизменным до конца выполнения программы. Примером такого выделения может служить объявление массива из 10 целых чисел:

int M; // память для массива выделяется один раз, размер памяти фиксированный

2. Динамическое выделение памяти. В этом случае используется комбинация операторов new и delete . Оператор new выделяет память для переменной (массива) в специальной области памяти, которая называется «куча» (heap). Оператор delete освобождает выделенную память. Каждому оператору new должен соответствовать свой оператор delete .

2. Преимущества и недостатки использования динамического и статического способов выделения памяти

Динамическое выделение памяти по сравнению со статическим выделением памяти дает следующие преимущества:

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

Преимущества статического способа выделения памяти:

  • статическое (фиксированное) выделение памяти лучше использовать, когда размер массива информации заведомо известен и есть неизменным на протяжении выполнения всей программы;
  • статическое выделение памяти не требует дополнительных операций освобождения с помощью оператора delete . Отсюда вытекает уменьшение ошибок программирования. Каждому оператору new должен соответствовать свой оператор delete ;
  • естественность (натуральность) представления программного кода, который оперирует статическими массивами.

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

3. Как выделить память оператором new для одиночной переменной? Общая форма.

Общая форма выделения памяти для одиночной переменной оператором new имеет следующий вид:

ptrName = new type;
  • ptrName – имя переменной (указателя), которая будет указывать на выделенную память;
  • type – тип переменной. Размер памяти выделяется достаточный для помещения в нее значения переменной данного типа type .
4. Как освободить память, выделенную под одиночную переменную оператором delete ? Общая форма

Если память для переменной выделена оператором new, то после завершения использования переменной, эту память нужно освободить оператором delete . В языке C++ это есть обязательным условием. Если не освободить память, то память останется выделенной (занятой), но использовать ее не сможет ни одна программа. В данном случае произойдет «утечка памяти» (memory leak).

В языках программирования Java, C# освобождать память после выделения не нужно. Этим занимается «сборщик мусора» (garbage collector ).

Общая форма оператора delete для одиночной переменной:

delete ptrName;

где ptrName – имя указателя, для которого была раньше выделена память оператором new . После выполнения оператора delete указатель ptrName указывает на произвольный участок памяти, который не является зарезервированным (выделенным).

5. Примеры выделения (new ) и освобождения (delete ) памяти для указателей базовых типов

В примерах демонстрируется использование операторов new и delete . Примеры имеют упрощенный вид.

Пример 1. Указатель на тип int . Простейший пример

// выделение памяти оператором new int * p; // указатель на int p = new int ; // выделить память для указателя *p = 25; // записать значения в память // использование памяти, выделенной для указателя int d; d = *p; // d = 25 // освободить память, выделенную для указателя - обязательно delete p;

Пример 2. Указатель на тип double

// выделение памяти для указателя на double double * pd = NULL ; pd = new double ; // выделить память if (pd!=NULL ) { *pd = 10.89; // записать значения double d = *pd; // d = 10.89 - использование в программе // освободить память delete pd; }
6. Что такое «утечка памяти» (memory leak )?

«Утечка памяти » – это когда память для переменной выделяется оператором new , а по окончании работы программы она не освобождается оператором delete . В этом случае память в системе остается занятой, хотя потребности в ее использовании уже нет, поскольку программа, которая ее использовала, уже давно завершила свою работу.

«Утечка памяти» есть типичной ошибкой программиста. Если «утечка памяти» повторяется многократно, то возможная ситуация, когда будет «занята» вся доступная память в компьютере. Это приведет к непредсказуемым последствиям работы операционной системы.

7. Каким образом выделить память оператором new с перехватом критической ситуации, при которой память может не выделиться? Исключительная ситуация bad_alloc . Пример

При использовании оператора new возможна ситуация, когда память не выделится. Память может не выделиться в следующих ситуациях:

  • если отсутствует свободная память;
  • размер свободной памяти меньше чем тот, который был задан в операторе new .

В этом случае генерируется исключительная ситуация bad_alloc . Программа может перехватить эту ситуацию и соответствующим образом обработать ее.

Пример. В примере учитывается ситуация, когда память может не выделиться оператором new . В таком случае осуществляется попытка выделить память. Если попытка удачная, то работа программы продолжается. Если попытка завершилась неудачей, то происходит выход из функции с кодом -1.

int main() { // объявить массив указателей на float float * ptrArray; try { // попробовать выделить память для 10 элементов типа float ptrArray = new float ; } catch (bad_alloc ba) { cout << << endl; cout << ba.what() << endl; return -1; // выход из функции } // если все в порядке, то использовать массив for (int i = 0; i < 10; i++) ptrArray[i] = i * i + 3; int d = ptrArray; cout << d << endl; delete ptrArray; // освободить память, выделенную под массив return 0; }
8. Выделение памяти для переменной с одновременной инициализацией. Общая форма. Пример

Оператор выделения памяти new для одиночной переменной допускает одновременную инициализацию значением этой переменной.

В общем, выделение памяти для переменной с одновременной инициализацией имеет вид

ptrName = new type(value )
  • ptrName – имя переменной-указателя, для которой выделяется память;
  • type – тип на который указывает указатель ptrName ;
  • value – значение, которое устанавливается для выделенного участка памяти (значение по указателю).

Пример. Выделение памяти для переменных с одновременной инициализацией. Ниже приводится функция main() для консольного приложения. Продемонстрировано выделение памяти с одновременной инициализацией. Также учитывается ситуация, когда попытка выделить память завершается неудачей (критическая ситуация bad_alloc ).

#include "stdafx.h" #include using namespace std; int main() { // выделение памяти с одновременной инициализацией float * pF; int * pI; char * pC; try { // попробовать выделить память для переменных с одновременной инициализацией pF = new float (3.88); // *pF = 3.88 pI = new int (250); // *pI = 250 pC = new char ("M" ); // *pC = "M" } catch (bad_alloc ba) { cout << "Исключительная ситуация. Память не выделена" << endl; cout << ba.what() << endl; return -1; // выход из функции } // если память выделена, то использование указателей pF, pI, pC float f = *pF; // f = 3.88 int i = *pI; // i = 250; char c; c = *pC; // c = "M" // вывести инициализированные значения cout << "*pF = " << f<< endl; cout << "*pI = " << i << endl; cout << "*pC = " << c << endl; // освободить память, выделенную ранее для указателей delete pF; delete pI; delete pC; return 0; }

    хранит глобальные переменные и константы;

    размер определяется при компиляции.

    Стек (stack)

    хранит локальные переменные, аргументы функций и промежуточные значения вычислений;

    размер определяется при запуске программы (обычно выделяется 4 Мб).

    Куча (heap)

    динамически распределяемая память;

    ОС выделяет память по частям (по мере необходимости).

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

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

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

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

    Выделение (захват памяти) : void *malloc(size_t size);

В качестве входного параметра функция принимает размер памяти, которую требуется выделить. Возвращаемым значением является указатель на выделенный в куче участок памяти. Если ОС не смогла выделить память (например, памяти не хватило), то malloc возвращает 0.

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

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

Пример: // выделения памяти под 1 000 элементов типа int

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

if (p==NULL) cout<< "\n память не выделена";

free (p); // возврат памяти в кучу

2. Выделение (захват памяти) : void *calloc(size_t nmemb, size_t size);

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

3. Изменение размера памяти:void *realloc(void *ptr, size_t size);

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

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

    Указатель (на участок динамической памяти) определен как локальный объект автоматической памяти. В этом случае выделенная память будет недоступна при выходе за пределы блока локализации указателя, и ее нужно освободить перед выходом из блока.

{ int* p= (int *) calloc(n, sizeof(int))

free (p); // освобождение дин. памяти

    Указатель определен как локальный объект статической памяти. Динамическая память, выделенная однократно в блоке, доступна через указатель при каждом повторном входе в блок. Память нужно освободить только по окончании ее использования.

{static int* p = (int *) calloc(n, sizeof(int));

p= (int *) calloc(n, sizeof(int));

f(50); //выделение дин. памяти с последующим освобождением

f1(100); //выделение дин. памяти (первое обращение)

f1(100); //работа с дин. памятью

f1 (0); // освобождение дин. памяти

    Указатель является глобальным объектом по отношению к блоку. Динамическая память доступна во всех блоках, где "виден" указатель. Память нужно освободить только по окончании ее использования

int* pG; //рабочий указатель для дин. памяти (глобальная переменная)

void init (int size)

for (i=0; i< size; i++) //цикл ввода чисел

{ printf("x[%d]=",i);

scanf("%d", &pG[i]);

int sum (int size)

for (i=0; i< size; i++) //цикл суммирования

// выделение памяти

pG= (int *) calloc(n, sizeof(int));

//работа с дин.памятью

printf(\ns=%d\n”,sum(n));

free (pG); pG=NULL; // освобождение памяти

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

В С++ есть свой механизм выделения и освобождения памяти - это функции new и delete. Пример использования new : int * p = new int; // выделение памяти под 1000 эл-тов Т.е. при использовании функции new не нужно приводить указатель и не нужно использовать sizeof(). Освобождение выделенной при помощи new памяти осуществляется посредством следующего вызова: delete p; Если требуется выделить память под один элемент, то можно использовать int * q = new int; или int * q = new int(10); // выделенный int проинициализируется значением 10 в этом случае удаление будет выглядеть следующим образом: delete q;