Указатели

advertisement
Указатели
Основные понятия
Указатель — это величина, предоставляющая косвенный доступ к элементам известного типа.
Указатели являются прообразами косвенных адресов в машинных командах. Главное отличие —
указатель указывает только на объекты определенного типа Т.
Указатели обеспечивают:
‰. Повышение эффективности. Вместо копирования или пересылки в памяти большой структуры
данных можно скопировать или переслать только указатель на эту структуру.
‰. Динамические структуры. С помощью записей и указателей можно реализовать структуры
данных, которые растут и сжимаются в период выполнения программы.
В языке Pascal указательный тип записывается в форме
type<Имя_указательного_типа> = ↑<Имя_типа>;
Например, объявление типа link, который является указателем на тип cell,имеет вид:
typelink = ↑cell;
Все указательные типы ↑Т имеют одинаковый формат размещения, вне зависимости от типа Т, на
который они указывают. Объяснение достаточно простое: значениями любого указательного типа
являются адреса, а адреса, в общем случае, не зависят от типа указываемого объекта. Поэтому формат
указателя известен, даже если тип Т еще не объявлен.
ПРИМЕЧАНИЕ
Размер (формат) адреса зависит только от величины адресного пространства компьютера и
специфики его организации.
Операции над указателями в языке Pascal
Базовая операция — это разыменование, то есть переход от указателя к значению указываемого
объекта. В языке Pascal разыменование обозначается как постфиксная стрелка (p↑).
ПРИМЕЧАНИЕ
В языке Pascal символ ↑ имеет двойное назначение: как префиксная операция создания
указательного типа и как постфиксная операция разыменования. Тип ↑Т используется для создания
указателей на объекты типа Т. Выражение p↑ обозначает объект, указываемый с помощью p.
В языке Pascal объект данных типа Т, доступ к которому должен обеспечиваться через указатель,
создается с помощью оператора new(p), где р — указатель на переменную типа Т. Такой объект
называют динамическим, поскольку создается он в период выполнения программы. Память под объект
выделяется из динамической области памяти по имени heap (куча). Объект существует до тех пор, пока
не произойдет явное освобождение памяти по оператору dispose (p).
Специальное значение указателя nil соответствует случаю, когда указатель ни на что не указывает.
Это значение может быть присвоено переменной любого указательного типа.
В языке Pascal возможны следующие операции над указателями:
1. Динамическое размещение в куче (heap). При выполнении new(p) в p заносится адрес нового
объекта типа Т, созданного в куче.
2. Разыменование. Выражение p↑ обозначает r-значение объекта данных, указываемого с помощью
р.
3. Присваивание. Разрешено между указателями одинакового типа p := q.
4. Проверка эквивалентности. Выражение эквивалентности p = q проверяет, содержат ли два
указателя одного типа адрес одного и того же объекта. Возможна проверка неэквивалентности.
5. Освобождение. Динамический объект данных существует до момента выполнения оператора
dispose(p), по которому память из-под него освобождается.
Динамические связные структуры данных
Необходимость работы с растущими структурами данных характерна для программ типа
компилятора, которые должны обрабатывать исходные тексты от нескольких строк до нескольких тысяч
строк.
Указатели
1
Растущие и сжимаемые (в ходе выполнения программы) структуры реализуются на основе записей
и указателей. Их создание возможно благодаря соблюдению принципа статического формата — размер
и формат памяти для каждого типа известны статически, перед выполнением программы.
Так как формат определяется в период компиляции, динамические структуры(растущие и
сжимаемые в период выполнения) реализуются с помощью элемента фиксированного размера.
Связная структура данных растет, когда элемент размещается и связывается с другими
элементами, структура данных сжимается, когда элемент удаляется.
Элементы и связи используются также для реализации таких структур данных, как связные списки
и деревья. Элемент в простом связном списке имеет вид, представленный на рис. 1.
Рисунок 1. Элемент связного списка
У него две секции: в одной секции (по имени info) содержится порция данных, в другой — адрес
соседнего элемента структуры.
Тип элемента может быть объявлен так:
typelink = ↑ cell;
cell = record
info : integer;
next :link
end;
Тип link используется для указания на cell, а тип cell — это запись с информационным полем и
полем-указателем на следующий элемент. Заметим, тип link(указательный тип) объявлен перед
объявлением типа cell и используется внутри объявления cell.
Пример. Допустим, что указательные переменные front и p имеют тип link.
Создадим динамическую структуру данных вида, изображенного на рис. 2.
Рис. 2. Динамическая структура данных
Эта задача решается следующей последовательностью операторов:
1. front : = nil; i : = 1;
2. new (p) ;
(*поместить в р адрес нового элемента *)
3. p↑. info : = i;
(*установить значение поля info элемента *)
4. p↑. next := front;
(*установить значение поля next*)
5. front := p;
(*скопировать адрес во front*)
Для добавления нового элемента надо выполнить i+1 (готовимся к занесению в него новой
информации) и снова перейти на вторую строку последовательности.
Логическая организация связной структуры не зависит от физического размещения ее элементов в
памяти. Нет необходимости, чтобы связанные элементы находились в смежных ячейках памяти. Они
могут быть где угодно. При сохранении связей ячейки могут даже перемещаться, не разрушая структуру
данных.
Указатели
2
Повисшие указатели и утечки памяти
Работа с динамическими объектами таит в себе повышенную опасность. Рассмотрим источники
этой опасности.
Повисший указатель — это указатель на память, которая используется для других целей.
Типичный случай — память уже перераспределена. К повисшему указателю приводит оператор
dispose(p). Повисшие указатели понижают безопасность программ.
Память, которая распределена, но недоступна, называется мусором (garbage).
Говорят, что программы, которые создают мусор, дают утечки памяти (memoryleaks). К утечкам
памяти могут привести присваивания между указателями. Утечка памяти может понизить
производительность, но не безопасность.
Предположим, что p и q — указатели на какие-то элементы (рис. 3).
Рис.3. Два указателя на объекты
Оператор присваивания p := q приводит к тому, что p и q указывают на один и тот же элемент (рис.
4).
Рис. 4. Указатель p получает значение указателя q
Элемент, на который ранее указывал p, по-прежнему остается в памяти, но теперь он недоступен.
Это утечка памяти.
Выполним теперь оператор dispose(p), освобождающий память под элемент, на который указывал
p (рис. 5).
Рис. 5. Освобождение памяти и «повисание» указателей
Теперь оба указателя становятся ≪безработными≫, поскольку хранят адрес уничтоженного
объекта. Хорошо это или плохо?
Представьте себе, что в недалеком будущем эта ячейка памяти задействуется под новую,
секретную информацию. Тогда безработные указатели смогут похитить все секреты!
Безопасность указателей в Паскале
Язык Паскаль проектировался в расчете на максимальную безопасность работы с этой особой
категорией — динамическими объектами. В чем их особенность? Любой динамический объект
создается в период выполнения программы, собственного имени не имеет, доступен только косвенно,
Указатели
3
через указатель. Ну а указатель —источник повышенной опасности, так как результат разыменования
повисшего указателя непредсказуем.
В этих условиях основой обеспечения безопасности считают разделение памяти на статическую и
динамическую (рис. 6).
Рис. 11.6. Разделение памяти на статическую и динамическую
Динамическая память — это единственное жизненное пространство для указателей. Указатели
могут ссылаться только на динамическую память. Указатели не могут использоваться для доступа к
статической памяти под обычные переменные.
Операции над указателями сохраняют разницу между статическими и динамическими
переменными. Результаты операций над указателями никогда не приводят к пересечению границы
между статической и динамической памятью. Иными словами, «граница на замке» и законопослушные
статические переменные избавлены от кошмаров повисших указателей. Да и мусор в статической
памяти (по вине указателей) появиться не может.
Напомним, у операций с указателями два недостатка:
1. Утечки памяти (это неприятность).
2. Повисшие указатели (это серьезная опасность). Способ устранения — присваивать указателю
(параметру оператора dispose) значение nil. Реализовать это можно как отдельный оператор или как
завершающую фазу выполнения оператора dispose.
Указатели как посредники
Очень часто указатели используют для косвенного обращения к большим структурам данных,
повышая скорость и сокращая затраты на организацию доступа.
Пример. Для работы с книжными индексами введем типы данных:
typeэлемент = record
термин :предст_термина;
страница :integer
end;
Указатели
4
typeпредст_термина = record
данные :array[0 .. 99] ofchar;
длина :integer
end;
Для работы с элементами введем указательный тип
typeу_элемент = ↑элемент;
и объявим два указателя этого типа
vare, f : у_элемент; (*выделение памяти под указатели *)
Выделение памяти под структуры производится с применением оператора new:
new (e); (*выделение памяти под структуру *)
new (f); (*выделение памяти под структуру *)
…
Далее в программе каждая из структур наполняется информацией, содержаниекоторой
иллюстрируют рис. 7 и 8.
Рис. 11.7. Содержание структуры, адресуемой указателем e
Рис. 11.8. Содержание структуры, адресуемой указателем f
Оценим эффективность созданной системы доступа к сложным структурам данных.
Перестановка указателей и перемещение данных
Перестановка указателей требует значительно меньше работы, чем перестановка записей, на
которые они ссылаются.
Рис. 9. Организация перекрестной адресации
На рис. 9 мы минимальными усилиями обеспечили перекрестную адресацию.
Рис.10. Повышение надежности доступа к важной информации
На рис. 10 показано альтернативное решение: мы усилили надежность доступа к какой-то очень
важной информации. Теперь эту запись обслуживают два указателя.
Указатели
5
Все эти действия выполняются очень быстро.
Рис. 11. Перемещение данных из одной области в другую область памяти
А вот на действие, которое иллюстрирует рис. 11, понадобится много времени. Объяснение
простое: через механизм разыменования здесь инициирован процесс действительной пересылки
большого количества элементов данных.
Массивы и указатели в языках С и С++
Операция разыменования указателя p в языке С обозначается так ∗ p. Это одноместная операция.
Применяя ее к указателю, получаем объект, на который указатель ссылается.
Объявления же указателей в языке С имеют вид:
int *pa;
float *pb;
Форма такого объявления задумывалась как мнемоническая — специально для облегчения его
понимания и использования. Первое объявление означает, что выражение *pa имеет тип int. Иными
словами, результат разыменования указателя pa имеет тип int.
После обработки этих объявлений указатель pa может хранить адреса целых переменных, а
указатель pb — адреса вещественных переменных.
Использование массивов и указателей в С тесно связано. Фактически имя массива А является
указателем на его начальный, нулевой элемент A[0]. Таким образом, использование указателей для
доступа к обычным массивам, размещенным в статической памяти, здесь считается обычным приемом.
Мало того, разрешена (и приветствуется) адресная арифметика, то есть манипулирование
адресами, находящимися в указателях. Рабочим инструментом адресной арифметики считается адресное
выражение.
Адресное выражение — это выражение, значением которого является адрес элемента массива (или
какой-то ячейки памяти). Так, адресное выражение A + 1(значение указателя А плюс единица)
указывает на элемент массива A[1], а A + iуказывает на A[i]. Все это приводит к очень интересным
возможностям.
Приведем иллюстрирующий пример. Если имеются следующие объявления
int i;
int vector [100];
int ∗ptr;
то после выполнения оператора присваивания
ptr = vector;
имя указателя ptr становится эквивалентно имени массива vector и можно сделать следующие
заключения:
‰. имя ptr[i] обозначает ту же ячейку массива, что и vector[i];
‰. разыменование адресного выражения ∗ (ptr + i) дает значение, которое одновременно является rзначением индексированной переменной vector [i].
Иначе говоря, указатель на массив можно индексировать так, словно он являетсяименем массива, а
имя массива может служить компонентом адресного выражения.
При применении адресной арифметики следует соблюдать предельную осторожность. Дело в том,
что удельный вес адресной единицы — это величина переменная.
Указатели
6
Она зависит от типа адресуемого элемента. Например, для типа char удельный вес равен единице,
а для типа float — четырем.
Указатель в языке С может обслуживать любую статическую переменную с именем. Речь идет о
том, что переменная помимо прямого имени может получить и косвенное имя — адрес, хранящийся в
указателе.
В этих условиях требуется средство для определения адреса любой переменной. Роль такого
средства играет операция взятия адреса, обозначаемая значком &. Применение этой операции к имени
переменной возвращает адрес переменной, который можно сохранить в некотором указателе.
Например, если объявлена целая переменная с именем х и указатель на целые переменные p
int х;
int *p;
то после выполнения оператора
p = &x; /* запись адреса переменной х в указатель p */
операторы
*p = *p + 1;
x = x + 1;
считаются эквивалентными.
ПРИМЕЧАНИЕ
*p в правой части оператора присваивания обозначает значение x, а в левой части — место
размещения (адрес) x.
Пример 1. Линейный поиск в массиве с использованием сигнальной метки x может быть описан
следующим фрагментом.
a[0] = x; /* запись сигнальной метки */
i = n; /* стартовое значение индекса (ищем с конца) */
while( a[i] != x )
--i;
return i;
Запись этого же фрагмента на основе указателей имеет вид:
a[0] = x;
р = а + n;
/* р — указатель на последний элемент массива */
while(*р != x )
--р;
return р – а;
/* р = а + i, поэтому, чтобы вернуть i, нужно вычесть а */
Теперь рассмотрим возможности языка С по представлению и обработке такой популярной
структуры данных, как символьные строки.
Вспомним, что строки в языке С представляются как массивы из символов.
Конец строки маркируется константой EOS, определяемой в виде '\0'.
Например, три символьные строки
маша
мыла
раму
хранятся в ≪ленте≫ памяти так, как это изображено на рис. 12.
Рис. 12. Лента памяти, хранящей три строки
Здесь подразумевается, что каждый символ занимает отдельную ячейку памяти.
Пример 2. Скопируем строку buffer в область памяти start, начиная с ячейкиstart[next]. Используем
два указателя p и q.
p = &start [next] ;
Указатели
7
q = buffer ;
for( ; ; )
{
*p = *q ;
if(*p = = EOS ) break ;
p ++ ;
q ++ ;
}
Прокомментируем первый оператор программного фрагмента. Когда имя массива (являющееся
указателем) связывается индексом, оно превращается в простое имя ячейки массива. Иначе говоря, от
имени start взять адрес нельзя (это указатель), а от имени start [next] — можно.
После двух первых операторов p указывает на место, куда должен быть скопирован первый
символ, а q указывает на первый элемент массива buffer.
Читаем for ( ; ; ) как «всегда». Операторы между фигурными скобками повторяются до момента
выполнения оператора break. Присваивание
*p = *q;
обеспечивает копирование одного символа.
Если это символ EOS (конец строки), тогда цикл завершается с помощью break.В противном
случае с помощью постфиксной операции ++ увеличиваются p и q.Они указывают на следующий
доступный элемент start и следующий копируемый символ соответственно.
Указатели языков С и С++ также могут адресовать функции, поддерживая возможность передавать
в одни функции другие функции (в качестве параметров). Указатели крайне важны и для двустороннего
связывания различных функций по данным.
Особую роль в С и С++ играют «бестиповые» указатели вида void *, обеспечивающие адресацию
объектов любых типов. Они очень удобны при решении таких задач операционной системы, как
управление памятью компьютера. Как правило, здесь идет речь о пересылке порций данных из одного
места в другое, невзирая на их форматы и внутреннюю структуру. Иными словами, перемещаться
должны не числа или строки, а некие байты какой-то информации. Внутреннее содержание и структура
«посылок» систему не интересует. Вот такими «неинтересующимися» почтальонами и служат
бестиповые указатели. Эти «адресные механизмы» даже при желании не смогли бы выяснить
содержание посылок, так как разыменовать бестиповой указатель просто нельзя. Конечно, после
прибытия на место назначения данные могут быть расшифрованы, но к этому бестиповые указатели
прямого отношения не имеют. Свое дело они уже сделали: сняли все запреты на пересылку.
Динамическое распределение памяти
В языке С для работы с динамическим объектами используют библиотечные функции malloc( ) и
free( ). Перед их применением к программе должен быть подключен заголовочный файл <stdlib.h>.
Функция malloc( ) выделяет память, а free( ) — освобождает ее. Это значит, что при каждом запросе
функция malloc( ) выделяет требуемую область памяти в куче, afree( ) освобождает ее, то есть
возвращает область в пул памяти.
Заголовок функции malloc( ) имеет вид:
void *malloc(size_tколичество_байтов);
Здесь количество_байтов — это размер памяти, необходимой для размещения динамического
объекта. Тип size_t задан в <stdlib.h> как некоторый целый без знака. Функция malloc() возвращает
указатель вида void*, поэтому его можно присвоить указателю любого типа. В случае успеха malloc( )
возвращает указатель на первый байт области памяти в куче. Если же в динамической распределяемой
области памяти недостаточно, то память не выделяется и malloc( ) возвращает нуль.
Для выделения 256 байтов следует записать:
char*ptr;
ptr = malloc(256); /∗ выделение 256 байтов ∗ /
Указатели
8
В итоге присваивания указатель ptr содержит адрес выделенной области памяти.
Поскольку динамически распределяемая область памяти не бесконечна, следуе тпроверять
успешность размещения динамического объекта, например, следующим программным кодом:
p = malloc(128);
if(!p) {
printf("Памяти не хватает!\n");
exit(1);
}
Функция free( ) выполняет противоположное действие: она возвращает в пул область памяти,
выделенную ранее по функции malloc( ). Иначе говоря, она освобождает область памяти, которая может
быть вновь использована функцией malloc( ). Функция free( ) имеет следующий заголовок:
void free(void *p)
Здесь р — указатель на область памяти, выделенную перед этим функцией malloc( ).
ВНИМАНИЕ
Вызов функции free( ) с ошибочным аргументом мгновенно разрушит всю систему
динамического распределения памяти.
В языке С++ для работы с динамическими объектами существуют операторы new и delete[].
Приведем пример:
int *ptr1;
int *ptr2 = new int[256]; // Размещение динамического массива
ptr1 = ptr2;
delete[] ptr2; // Удаление динамического массива
Ничего «радостного» мы не показали. В результате этих манипуляций оба указателя стали
повисшими, со всеми вытекающими отсюда последствиями.
Гибкость указателей в языке С
В отличие от языка Pascal аппарат указателей в С ориентирован не на обеспечение максимальной
безопасности, а на предоставление программисту максимальной гибкости. В этом смысле язык С
подобен острой бритве, которая хороша в умелых руках, но которой может сильно порезаться неумелый
новичок.
1. В С указатели используют для доступа ко всей памяти, то есть для доступа как к обычным
переменным (статическим, с именами), так и к безымянным переменным.
2. Выделение и освобождение динамической памяти не определяется средствами языка С (в
отличие от языка Pascal), здесь используются библиотечные функции malloc, free.
3. В С отсутствуют ограничения на вид и тип переменных, на которые могут указывать указатели.
Допускается проведение арифметических операций над указателями (адресами). В результате
возможны многочисленные утечки памяти, появление повисших указателей.
Перечислим операции с указателями в языке С:
1. Присваивание p = &a
2. Разыменование x = *p
3. Получение адреса указателя &p.
4. Увеличение указателя на единицу ++p (перемещение к адресу следующего элемента массива).
Заметим, что имеется в виду адресная единица, удельный вес которой может меняться (в зависимости
от типа адресуемого элемента). Например, если адресуются элементы, размещаемые в четырех ячейках
памяти, то, как показано на рис. 13, вес адресной единицы равен четырем.
Указатели
9
Рис. 13. Вес адресной единицы зависит от типа адресуемого объекта
5. Вычитание p2 – p1. Результат отображается в тех же единицах, что и размер данного типа
переменной. Если р2 и р1 указывают на элементы типа long, то p2 – p1 = 2 означает, что указываемые
переменные разделены двумя значениям типа long, а не двумя байтами.
Ссылочный тип
Переменная ссылочного типа похожа на указатель, но имеет одну важную особенность. Указатель
ссылается на адрес в памяти, а ссылка ссылается на объект или значение в памяти. Как следствие,
совершенно естественно выполнять арифметические действия над адресами, но противоестественно
делать то же самое со ссылками.
Язык C++ содержит специальную разновидность ссылочного типа, используемую
преимущественно в формальных параметрах из объявления функции. Переменная ссылочного типа в
языке C++ — это константный указатель, который всегда неявно разыменован. Поскольку ссылочная
переменная является константой, при определении она должна быть проинициализирована адресом
некоторой переменной. После инициализации ссылочная переменная не может становиться ссылкой на
какую-либо другую переменную. Неявное разыменование предостерегает от присваивания ссылочной
переменной другого адреса. Иными словами, имя ссылочной переменной считается псевдонимом той
переменной, которая использовалась при ее инициализации.
В определении имя переменной ссылочного типа предваряется символом амперсанда (&).
Например,
int sum = 0;
int &ref_sum = sum;
...
ref_sum = 150;
В этом фрагменте переменные sum и ref_sum считаются алиасами.
Если ссылочные типы C++ используются в качестве типов формальных параметров для функций,
то они обеспечивают два варианта взаимодействия между вызывающей и вызываемой функциями. В
языке C++ принят только один способ передачи параметров в функцию: передача по значению. Чтобы
снять это ограничение, применяют указатели. Но указатели требуют явного разыменования, которое
понижает читабельность и безопасность кода. В отличие от указателей ссылочные параметры
записываются в вызываемой функции точно так же, как и остальные параметры. В вызывающей
функции тоже не требуется особая форма определения того фактического параметра, которому
соответствует ссылочный формальный параметр. В свою очередь, компилятор для ссылочного
параметра передает адрес, а не значение.
В целях повышения безопасности создатели языка Java отказались от указателей в стиле C++. В
отличие от ссылочных переменных C++, ссылочные переменные в Java могут применяться для ссылок
на экземпляры различных классов, они не являются константами. Экземпляры всех классов в Java
указываются ссылочными переменными. По сути, в языке Java используются только ссылочные
переменные.
В следующем фрагменте String — это стандартный класс языка Java:
Stringstr;
...
Указатели
10
str = "Это строка в стиле языка Java";
В этом фрагменте str является ссылкой на экземпляр (объект) класса String.Изначально она
установлена в null. Последующее присваивание превращает str в ссылку на объект класса String,
содержащий строку "Это строка в стиле языка Java".
Поскольку экземпляры классов в Java удаляются из кучи неявно (здесь нет явной операции
удаления), в этом языке повисшие ссылки невозможны. В C# включены как ссылки в стиле Java, так и
указатели в стиле C++. Однако применение указателей настоятельно не рекомендуется. Любая
подпрограмма, использующая указатели, должна иметь модификатор unsafe (небезопасно). Объекты,
указываемые ссылками, удаляются неявно, однако к объектам, доступным через указатели, механизм
неявного удаления не применяется. Указатели включены в C# с единственной целью: обеспечить
взаимодействие программ на C# с программами на C и C++.
В объектно-ориентированных языках Smalltalk, Python, Ruby и Lua все переменные являются
ссылками. Все они неявно разыменованы. Более того, непосредственные значения этих переменных
(адреса) просто недоступны.
Реализация указателей
Объект данных типа «указатель» реализуется областью памяти, в которой содержится адрес другой
области памяти. Это начальный адрес области памяти, размещающей объект данных, на который
ссылается указатель. Возможны два представления значений указателей в памяти:
‰. Абсолютный адрес. Значение указателя может быть истинным адресом области памяти,
отведенной для объекта данных.
‰. Относительный адрес. Значение указателя может быть представлено как смещение от базового
адреса некоторого более крупного блока памяти, называемого кучей, внутри которого размещается
объект данных.
Если в указателе содержится абсолютный адрес, то объект данных, созданный операцией new,
может быть размещен в любой области памяти компьютера. Обычно эта область располагается внутри
общей кучи. Выбор нужного объекта с помощью абсолютного адреса весьма эффективен, поскольку
обеспечивает непосредственный доступ к объекту данных с помощью аппаратных средств. Правда, при
абсолютной адресации управление памятью усложняется, так как ни один объект данных не может быть
перемещен в памяти, пока где-то существует на него указатель. Восстановление памяти, отведенной под
объект данных, после того как он удаляется в ходе сбора мусора, также трудоемко, поскольку для
каждого объекта данных освобождаемая память восстанавливается индивидуально, и блоки памяти
должны быть возвращены обратно в общий пул свободного пространства памяти.
Если в указателях находятся относительные адреса, то предварительно назначаются блоки памяти,
в пространстве которых и выделяются фрагменты памяти под создаваемые (с помощью операции new)
объекты данных. Для каждого типа данных может формироваться своя отдельная область памяти.
Впрочем, возможно создание и единой области для объектов всех типов. В любом случае к каждой из
областей применяются методы управления кучей. Если под объекты каждого типа отводится своя
область памяти, то операция new располагает новые объекты в блоках памяти фиксированного размера,
что упрощает процесс управления памятью. Выбор объектов данных при использовании относительной
адресации подразумевает увеличение затрат по сравнению с абсолютной адресацией, поскольку для
получения абсолютного адреса к каждому относительному адресу нужно добавлять базовый адрес
области памяти, хранящей искомый объект. Преимущество относительной адресации заключается в
возможности перемещения всей области в любой момент вычислений, без изменения значений
указателя. Кроме того, вся выделяемая область памяти может рассматриваться как некий объект
данных, который создается при входе в процедуру и используется для размещения объектов процедуры
при помощи операции new, а затем удаляется при выходе из процедуры. При этом после удаления
отдельного объекта не требуется реконструировать выделенную область, поскольку вся выделенная под
такую область память восстанавливается при завершении работы процедуры.
Указатели
11
Если каждый указатель ссылается на объекты только одного типа, то становится возможным
статический контроль типов. Если же убрать это ограничение, то вовремя компиляции невозможно
выяснить, на объект какого типа будет ссылаться указатель в ходе вычислений, поэтому придется
осуществлять динамический контроль типов.
Специфика реализации указателей во многом определяется механизмом выделения памяти под
новые объекты. Обычно применяют систему управления динамической памятью heap. В языке С, как
правило, используется система управления памятью на основе стека, поэтому добавление функции
динамического выделения памяти malloc( ) требует существенного расширения всей структуры
управления памятью в период вычислений.
Указатели
12
Download