Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
201112.docx
Скачиваний:
0
Добавлен:
01.03.2025
Размер:
1.95 Mб
Скачать

Int tacos[10]; // статическое связывание, размер фиксирован во время компиляции

Для создания массива с динамическим связыванием (динамического массива) используется операция new [ ]. Память для этого массива выделяется в соответствии с размером, указанным во время выполнения программы. Когда работа с таким массивом завершена, выделенная ему память освобождается с помощью операции

delete[]:

int size; cin >> size;

int * pz = new int [size]; // динамическое связывание, размер

// устанавливается во время выполнения

delete [] pz; // освобождение памяти по окончании работы с массивом

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

Давайте рассмотрим пример, в котором используются операции new и delete для управления сохранением строкового ввода с клавиатуры. В листинге 4.22 определяется функция getname (), которая возвращает указатель на входную строку. Эта функция читает ввод в большой временный массив, а затем использует new [ ] с указанием соответствующего размера, чтобы выделить фрагмент памяти в точности такого размера, который позволит вместить входную строку. После этого функция возвращает указатель на этот блок. Такой подход может сэкономить огромный объем памяти в программе, читающей большое количество строк. (В реальности было бы проще воспользоваться классом string, в котором операции new и delete применяются внутренне.)

Предположим, что ваша программа должна прочитать 1000 строк, самая длинная из которых может составлять 79 символов, но большинство строк значительно короче. Если вы решите использовать массивы char для хранения строк, то вам понадобится 1000 массивов по 80 символов каждый, т.е. 80 000 байт, причем большая часть этого блока памяти останется неиспользованной. В качестве альтернативы можно создать массив из 1000 указателей на char и применить new для выделения ровно такого объема памяти, сколько необходимо для каждой строки. Это может сэкономить десятки тысяч байт. Вместо того чтобы создавать большой массив для каждой строки, вы выделяете память, достаточную для размещения ввода. И более того, вы можете использовать new для выделения памяти лишь для стольких указателей, сколько будет входных строк в действительности. Да, это несколько амбициозно для начала. Даже массив из 1000 указателей — довольно амбициозное решение для настоящего момента, но в листинге 4.22 демонстрируется этот прием. К тому же, чтобы проиллюстрировать работу операции delete, программа использует ее для освобождения выделенной памяти.

// delete.cpp -- using the delete operator

#include <iostream>

#include <cstring> // or string.h

using namespace std;

char * getname(void); // function prototype

Int main()

{

char * name; // create pointer but no storage

name = getname(); // assign address of string to name

cout << name << " at " << (int *) name << "\n";

delete [] name; // memory freed

name = getname(); // reuse freed memory

cout << name << " at " << (int *) name << "\n";

delete [] name; // memory freed again

cin.get();

cin.get();

return 0;

}

char * getname() // return pointer to new string

{

char temp[80]; // temporary storage

cout << "Enter last name: ";

cin >> temp;

char * pn = new char[strlen(temp) + 1];

strcpy(pn, temp); // copy string into smaller space

return pn; // temp lost when function ends

}

Рассмотрим функцию getname (), представленную в листинге 4.22. Она использует cin для размещения введенного слова в массив temp. Далее она обращается к new для выделения памяти, достаточной, чтобы вместить это слово. С учетом нулевого символа программе требуется сохранить в строке strlen (temp) + 1 символов, поэтому именно это значение передается new. После получения пространства памяти getname () вызывает стандартную библиотечную функцию strcpy (), чтобы скопировать строку temp в выделенный блок памяти. Функция не проверяет, поместится ли строка, но getname () гарантирует выделение блока памяти подходящего размера. В конце функция возвращает ps — адрес копии строки.

Внутри main () возвращенное значение (адрес) присваивается указателю name. Этот указатель объявлен в main (), но указывает на блок памяти, выделенный в функции getname (). Затем программа печатает строку и ее адрес.

Далее, после освобождения блока, на который указывает name, функция main () вызывает getname () второй раз. C++ не гарантирует, что только что освобожденная память будет выделена при следующем вызове new, и, как видно из вывода программы, это и не происходит.

Обратите внимание, что в рассматриваемом примере getname () выделяет память, a main () освобождает ее. Обычно это не слишком хорошая идея — размещать new и delete в разных функциях, потому что в таком случае очень легко забыть вызвать delete. В этом примере мы разделили эти две операции просто для того, чтобы продемонстрировать, что подобное возможно.

Автоматическое, статическое и динамическое хранилище

В C++ предлагаются три способа управления памятью для данных — в зависимости от метода ее выделения: автоматическое хранилище, статическое хранилище и динамическое хранилище, иногда называемое свободным хранилищем или кучей. Объекты данных, выделенные этими тремя способами, отличаются друг от друга тем, насколько долго они существуют. Рассмотрим кратко каждый из них. (В C++11 добавляется четвертая форма, которая называется хранилищем потока)

Автоматическое хранилище

Обычные переменные, объявленные внутри функции, используют автоматическое хранилище и называются автоматическими переменными. Этот термин означает, что они создаются автоматически при вызове содержащей их функции и уничтожаются при ее завершении. Например, массив temp в листинге 4.22 существует только во время работы функции getname (). Когда управление программой возвращается main (), то память, используемая temp, освобождается автоматически. Если бы getname () возвращала указатель на temp, то указатель name в main () остался бы установленным на адрес памяти, которая скоро может быть использована повторно. Вот почему внутри getname () необходимо вызывать new. На самом деле автоматические значения являются локальными по отношению к блоку, в котором они объявлены.

Блок — это раздел кода, ограниченный фигурными скобками. До сих пор все наши блоки были целыми функциями. Но можно иметь блоки и внутри функций. Если вы объявите переменную внутри одного из таких блоков, она будет существовать только в то время, когда программа выполняет операторы, содержащиеся внутри этого блока.

Автоматические переменные обычно хранятся в стеке. Это значит, что когда выполнение программы входит в блок кода, его переменные последовательно добавляются к стеку в памяти и затем освобождаются в обратном порядке, когда выполнение покидает данный блок. (Этот процесс называется UFO (last-in, first-out — “последним пришел — первым ушел”.) Таким образом, по мере продвижения выполнения стек растет и уменьшается.

Статическое хранилище

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

static double fee = 56.50;

Согласно правилам языка С стандарта K&R, вы можете инициализировать только статические массивы и структуры, в то время как C++ Release 2.0 (и более поздние), а также ANSI С позволяют также инициализировать автоматические массивы и структуры. Однако, как вы, возможно, обнаружите, в некоторых реализациях C++ до сих пор не реализована инициализация таких массивов и структур.

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

Динамическое хранилище

Операции new и delete предлагают более гибкий подход, нежели использование автоматических и статических переменных. Они управляют пулом памяти, который в C++ называется свободным хранилищем или кучей. Этот пул отделен от области памяти, используемой статическими и автоматическими переменными. Как было показано в листинге 4.22, операции new и delete позволяют выделять память в одной функции и освобождать в другой. Таким образом, время жизни данных при этом не привязывается жестко к времени жизни программы или функции.

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

Стеки, кучи и утечка памяти

Что произойдет, если не вызвать delete после создания переменной с помощью операции new в свободном хранилище (куче)? Если не вызвать delete, то переменная или конструкция, динамически выделенная в области свободного хранилища, останется там, даже при условии, что память, содержащая указатель, будет освобождена в соответствии с правилами видимости и временем жизни объекта. По сути, после этого у вас не будет никакой возможности получить доступ к такой конструкции, находящейся в области свободного хранилища, поскольку уже не будет существовать указателя, который помнит ее адрес. В этом случае вы получите утечку памяти. Такая память остается недоступной на протяжении всего сеанса работы программы. Она была выделена, но не может быть освобождена. В крайних случаях (хотя и нечастых), утечки памяти могут привести к тому, что они поглотят всю память, выделенную программе, что вызовет ее аварийное завершение с сообщением об ошибке переполнения памяти. Вдобавок такие утечки могут негативно повлиять на некоторые операционные системы и другие приложения, использующие то же самое пространство памяти, приводя к их сбоям.

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

На заметку!

Указатели — одно из наиболее мощных средств C++. Однако они также и наиболее опасны, потому что открывают возможность недружественных к компьютеру действий, таких как использование неинициализированных указателей для доступа к памяти либо попыток освобождения одного и того же блока дважды. Более того, до тех пор, пока вы не привыкнете в нотации указателей и к самой концепции указателей на практике, они будут приводить к путанице. Но поскольку указатели — важнейшая часть программирования на C++, они постоянно будут присутствовать во всех дальнейших обсуждениях. К теме указателей мы еще будем обращаться не раз. Мы надеемся, что каждое объяснение поможет вам чувствовать себя все более уверенно при работе с указателями.

12 Завести массив из 20. Заполнить случайными числами от 1 до 100. Распечатать массив.Найти min и max значение, поменять ячейки min и max местами. Распечатать новый массив.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]