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


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

1?5?1? Объявления указателей и приведение типов
Указатель представляет собой переменную, содержащую адрес другой переменной. Объявление указателя всегда должно устанавливать его на некоторый конкретный тип, даже если этот тип void (что фактически означает указатель на любой тип).
Если type есть любой предопределенный или опре-деленный пользователем тип, включая void, то объявление
type *ptr;
объявит ptr как «указатель на тип type». К объявленному таким образом объекту ptr применимы все правила, связанные с контекстом, продолжительностью жизни и видимостью.
Указатель со значением NULL это адрес, гарантированно отличный от любого допустимого указателя, используемого в программе. Объявление
void *vptr;
объявляет, что vptr — это родовой указатель, которому может быть присвоено любое значение «указатель на тип type» без выдачи компилятором сообщений. Без правильного приведения типов между «указателем на тип type1» и «указателем на тип type2», где type1 и type2 это различные типы, присвоение может вызвать предупреждение или ошибку компилятора. Если type1 это указатель на void, приведения типов не требуется. Если type2 это указатель на тип void, то в С приведение не нужно.
Ограничения присвоения также существуют относительно указателей разных размеров (near, far и huge). Можно присвоить меньший указатель большему, не вызвав ошибки, но нельзя выполнить обратную операцию, не выполнив явную опе-рацию приведения. Например,
char near *ncp;
char far *fcp;
fcp = ncp; // допустимо
ncp = fcp; // недопустимо
ncp = (char near*)fcp; // теперь допустимо

1?5?2? Операции с указателями
Получить значение объекта, который адресуется указателем, можно при помощи операции * :
int * s; //s не определено
int b, a = 10; //b не определено
s = &a; //s указывает на а
b = *s; //b = 10
Внутренние арифметические операции с указателями зависят от действующей модели памяти и наличия переопределяющих модификаторов указателя. Разность между двумя значениями указателей имеет смысл только в том случае, если оба они указывают на один массив.
Арифметические операции с указателями ограничены сложением, вычитанием и сравнением. Арифметические операции с указателями объектов типа «указатель на тип type» автоматически учитывают размер этого типа, то есть число байт, необходимое для хранения в памяти объекта данного типа.
При выполнении арифметических операций с указателями предполагается, что указатель указывает на массив объектов. Таким образом, если указатель объявлен как указатель на type, то прибавление к нему целочисленного значения перемещает указатель на соответствующее количество объектов type. Если type имеет размер 10 байтов, то прибавление целого числа 5 к указателю этого типа перемещает указатель в памяти на 50 байт. Разность представляет собой число элементов массива, разделяющих два значения указателей. Например, если ptr1 указывает на третий элемент массива, а ptr2 на десятый, то результатом выполнения вычитания ptr2 — ptr1 будет 7.
Когда с «указателем на тип type» выполняется операция сложения или вычитания целого числа, то результат также будет «указателем на тип type». Если type не является массивом, то операнд указателя будет рассматриваться как указатель на первый элемент «массива типа type» длиной sizeof(type).
Конечно, такого элемента, как «указатель на следующий за последним элемент», однако указатель может принимать это значение. Если P указывает на последний элемент массива, то значение P+1 допустимо, но P+1 неопределено. Если P указывает на элемент за последним элементом массива, то допустимо значение P-1, когда указатель установлен на последний элемент массива. Однако использование указателя на элемент вне массива ведет к непредсказуемым результатам работы программы.
Для информации: P+n можно представить себе как перемещение указателя на (n*sizeof(type)) байт вперед, пока указатель остается в допустимых границах (не далее первого за концом массива элемента). Контроль за допустимыми значениями указателей возлагается на программиста.

1?5?3? Преобразования указателей
Указатели одного типа могут быть преобразованы в указатели другого типа при помощи следующего механизма приведения типов:
char *str
int *ip
str = (char*)ip;
В более общем виде, приведение (type*) преобразует указатель в тип «указатель на тип type».
1?5?4? Операции new и delete
Операции new и delete выполняют динамическое распределение и отмену распределения памяти, аналогично, но с более высоким приоритетом, нежели стандартные библиотечные функции семейства malloc и free.
Упрощенный синтаксис:
указатель-имени = new имя <инициализатор-имени>;
…
delete указатель-имени;

Имя может быть любого типа, кроме «функция, возвра-щающая…»(однако, указатели функций здесь допустимы). new пыта-ется создать объект с типом «имени», распределив
(при возможности) sizeof(имя) байт в свободной области памяти (которую также называют «кучей»). Продолжительность существования в памяти данного объекта — от точки его создания и до тех пор, пока операция delete не отменит распределенную для него память, либо до конца работы программы.
В случае успешного завершения new возвращает указатель нового объекта. Пустой указатель означает неудачное завершение операции (например, недостаточный объем или слишком большая фрагментированность кучи). Как и в случае malloc, прежде чем пытаться обращаться к новому объекту, следует проверить указатель на наличие пустого значения. Возвращаемый указатель будет иметь правильный тип, «указатель имени», без необходимости явного приведения типов.
name *nameptr // name может иметь любой тип,
//кроме функции
…
if (!(nameptr = new name)) {
errmsg(«Недостаточно памяти для name»);
exit (1);
}
// использование *nameptr для инициализации объекта
//new name
…
delete nameptr; // удаление name и отмена
//распределения sizeof(name)
//байтов памяти
new, будучи ключевым словом, не нуждается в прототипе.

1?5?5? Операция new с массивами
Если «имя» это массив, то возвращаемый new указатель указывает на первый элемент массива. При создании с помощью new многомерных массивов следует указывать все размерности массива:
mat_ptr = new int[3][10][12]; // так можно
mat_ptr = new int[3][][12]; // нельзя
…
delete [] mat_ptr; //освободить память, занятую
//массивом, на который указывает mat_ptr
Существует глобальная переменная _new_handler, представляющая собой указатель на функцию типа void _new_handler(void). Если new не может выделить память, она проверяет _new_handler. Если _new_handler == NULL, то возвращается 0, иначе вызывается функция, на которую указывает _new_handler.
Пример:
void MyNewHandler(void)
{
puts(«Out of memory.»);
// здесь выполняются необходимые действия по
// завершению программы
exit(1);
}
_new_handler = &MyNewHandler;

1?5?6? Инициализаторы с операцией new
Другим преимуществом операции new по сравнению с malloc является возможность инициализации. При отсутствии явных инициализаторов объект, создаваемый new, содержит непредсказуемые данные («мусор»). Объекты, распределяемые new, за исключением массивов, могут инициализироваться соответствующим выражением в скобках:
int_ptr = new int(3);

Загрузка...