Смекни!
smekni.com

жения его нулевого элемента, то присваивание PA=&A[0] можно

записать как

PA = A

Еще более удивительным, по крайней мере на первый взг-

ляд, кажется тот факт, что ссылку на A[I] можно записать в

виде *(A+I). При анализировании выражения A[I] в языке “C”

оно немедленно преобразуется к виду *(A+I); эти две формы

совершенно эквивалентны. Если применить операцию & к обеим

частям такого соотношения эквивалентности, то мы получим,

что &A[I] и A+I тоже идентичны: A+I - адрес I-го элемента от

начала A. С другой стороны, если PA является указателем, то

в выражениях его можно использовать с индексом: PA[I] иден-

тично *(PA+I). Короче, любое выражение, включающее массивы и

индексы, может быть записано через указатели и смещения и

наоборот, причем даже в одном и том же утверждении.

· 104 -

Имеется одно различие между именем массива и указателем,

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

ной, так что операции PA=A и PA++ имеют смысл. Но имя масси-

ва является константой, а не переменной: конструкции типа

A=PA или A++,или P=&A будут незаконными.

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

ей передается местоположение начала этого массива. Внутри

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

ременной, как и любая другая, так что имя массива в качестве

аргумента действительно является указателем, т.е. Перемен-

ной, содержащей адрес. мы можем использовать это обстоятель-

ство для написания нового варианта функции STRLEN, вычисляю-

щей длину строки.

STRLEN(S) /* RETURN LENGTH OF STRING S */

CHAR *S;

{

INT N;

FOR (N = 0; *S != '\0'; S++)

N++;

RETURN(N);

}

Операция увеличения S совершенно законна, поскольку эта

переменная является указателем; S++ никак не влияет на сим-

вольную строку в обратившейся к STRLEN функции, а только

увеличивает локальную для функции STRLEN копию адреса. Опи-

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

CHAR S[];

CHAR *S;

совершенно эквивалентны; какой вид описания следует предпо-

честь, определяется в значительной степени тем, какие выра-

жения будут использованы при написании функции. Если функции

передается имя массива, то в зависимости от того, что удоб-

нее, можно полагать, что функция оперирует либо с массивом,

либо с указателем, и действовать далее соответвующим обра-

зом. Можно даже использовать оба вида операций, если это ка-

жется уместным и ясным.

Можно передать функции часть массива, если задать в ка-

честве аргумента указатель начала подмассива. Например, если

A - массив, то как

F(&A[2])

как и

F(A+2)

·
105 -

передают функции F адрес элемента A[2], потому что и &A[2],

и A+2 являются указательными выражениями, ссылающимися на

третий элемент A. внутри функции F описания аргументов могут

присутствовать в виде:

F(ARR)

INT ARR[];

{

...

}

или

F(ARR)

INT *ARR;

{

...

}

Что касается функции F, то тот факт, что ее аргумент в дейс-

твительности ссылается к части большего массива,не имеет для

нее никаких последствий.

5.4. Адресная арифметика

Если P является указателем, то каков бы ни был сорт

объекта, на который он указывает, операция P++ увеличивает P

так, что он указывает на следующий элемент набора этих

объектов, а операция P +=I увеличивает P так, чтобы он ука-

зывал на элемент, отстоящий на I элементов от текущего эле-

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

простые и самые распространенные формы арифметики указателей

или адресной арифметики.

Язык “C” последователен и постоянен в своем подходе к

адресной арифметике; объединение в одно целое указателей,

массивов и адресной арифметики является одной из наиболее

сильных сторон языка. Давайте проиллюстрируем некоторые из

соответствующих возможностей языка на примере элементарной

(но полезной, несмотря на свою простоту) программы распреде-

ления памяти. Имеются две функции: функция ALLOC(N) возвра-

щает в качестве своего значения указатель P, который указы-

вает на первую из N последовательных символьных позиций, ко-

торые могут быть использованы вызывающей функцию ALLOC прог-

раммой для хранения символов; функция FREE(P) освобождает

приобретенную таким образом память, так что ее в дальнейшем

можно снова использовать. программа является “элементарной”,

потому что обращения к FREE должны производиться в порядке,

обратном тому, в котором производились обращения к ALLOC.

Таким образом, управляемая функциями ALLOC и FREE память яв-

ляется стеком или списком, в котором последний вводимый эле-

мент извлекается первым. Стандартная библиотека языка “C”

содержит аналогичные функции, не имеющие таких ограничений,

· 106 -

и, кроме того, в главе 8 мы приведем улучшенные варианты.

Между тем, однако, для многих приложений нужна только триви-

альная функция ALLOC для распределения небольших участков

памяти неизвестных заранее размеров в непредсказуемые момен-

ты времени.

Простейшая реализация состоит в том, чтобы функция раз-

давала отрезки большого символьного массива, которому мы

присвоили имя ALLOCBUF. Этот массив является собственностью

функций ALLOC и FREE. Так как они работают с указателями, а

не с индексами массива, никакой другой функции не нужно

знать имя этого массива. Он может быть описан как внешний

статический, т.е. Он будет локальным по отношению к исходно-

му файлу, содержащему ALLOC и FREE, и невидимым за его пре-

делами. При практической реализации этот массив может даже

не иметь имени; вместо этого он может быть получен в резуль-

тате запроса к операционной системе на указатель некоторого

неименованного блока памяти.

Другой необходимой информацией является то, какая часть

массива ALLOCBUF уже использована. Мы пользуемся указателем

первого свободного элемента, названным ALLOCP. Когда к функ-

ции ALLOC обращаются за выделением N символов, то она прове-

ряет, достаточно ли осталось для этого места в ALLOCBUF. Ес-

ли достаточно, то ALLOC возвращает текущее значение ALLOCP

(т.е. Начало свободного блока), затем увеличивает его на N,

с тем чтобы он указывал на следующую свободную область. Фун-

кция FREE(P) просто полагает ALLOCP равным P при условии,

что P указывает на позицию внутри ALLOCBUF.

DEFINE NULL 0 /* POINTER VALUE FOR ERROR REPORT */

DEFINE ALLOCSIZE 1000 /* SIZE OF AVAILABLE SPACE */

TATIC CHAR ALLOCBUF[ALLOCSIZE];/* STORAGE FOR ALLOC */

TATIC CHAR ALLOCP = ALLOCBUF; / NEXT FREE POSITION */

HAR ALLOC(N) / RETURN POINTER TO N CHARACTERS */ INT N;

(

IF (ALLOCP + N <= ALLOCBUF + ALLOCSIZE) {

ALLOCP += N;

RETURN(ALLOCP - N); /* OLD P */

} ELSE /* NOT ENOUGH ROOM */

RETURN(NULL);

)

REE(P) /* FREE STORAGE POINTED BY P */

HAR *P;

(

IF (P >= ALLOCBUF && P < ALLOCBUF + ALLOCSIZE)

ALLOCP = P;

)


· 107 -

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

жет быть инициализирован точно так же, как и любая другая

переменная, хотя обычно единственными осмысленными значения-

ми являются NULL (это обсуждается ниже) или выражение, вклю-

чающее адреса ранее определенных данных соответствующего ти-

па. Описание

STATIC CHAR *ALLOCP = ALLOCBUF;

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

его так, чтобы он указывал на ALLOCBUF, т.е. На первую сво-

бодную позицию при начале работы программы. Так как имя мас-

сива является адресом его нулевого элемента, то это можно

было бы записать в виде

STATIC CHAR *ALLOCP = &ALLOCBUF[0];

используйте ту запись, которая вам кажется более естествен-

ной. С помощью проверки

IF (ALLOCP + N <= ALLOCBUF + ALLOCSIZE)

выясняется, осталось ли достаточно места, чтобы удовлетво-

рить запрос на N символов. Если достаточно, то новое значе-

ние ALLOCP не будет указывать дальше, чем на последнюю пози-

цию ALLOCBUF. Если запрос может быть удовлетворен, то ALLOC

возвращает обычный указатель (обратите внимание на описание

самой функции). Если же нет, то ALLOC должна вернуть некото-

рый признак, говорящий о том, что больше места не осталось.

В языке “C” гарантируется, что ни один правильный указатель

данных не может иметь значение нуль, так что возвращение ну-

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

в данном случае об отсутствии места. Мы, однако, вместо нуля

пишем NULL, с тем чтобы более ясно показать, что это специ-

альное значение указателя. Вообще говоря, целые не могут ос-

мысленно присваиваться указателям, а нуль - это особый слу-

чай.

Проверки вида

IF (ALLOCP + N <= ALLOCBUF + ALOOCSIZE)

и

IF (P >= ALLOCBUF && P < ALLOCBUF + ALLOCSIZE)

демонстрируют несколько важных аспектов арифметики указате-

лей. Во-первых , при определенных условиях указатели можно

сравнивать. Если P и Q указывают на элементы одного и того

же массива, то такие отношения, как <, >= и т.д., работают

надлежащим образом. Например,

P < Q

· 108 -

истинно, если P указывает на более ранний элемент массива,

чем Q. Отношения == и != тоже работают. Любой указатель мож-

но осмысленным образом сравнить на равенство или неравенство

с NULL. Но ни за что нельзя ручаться, если вы используете

сравнения при работе с указателями, указывающими на разные

массивы. Если вам повезет, то на всех машинах вы получите

очевидную бессмыслицу. Если же нет, то ваша программа будет

правильно работать на одной машине и давать непостижимые ре-

зультаты на другой.

Во-вторых, как мы уже видели, указатель и целое можно

складывать и вычитать. Конструкция

P + N

подразумевает N-ый объект за тем, на который P указывает в

настоящий момент. Это справедливо независимо от того, на ка-

кой вид объектов P должен указывать; компилятор сам масшта-

бирует N в соответствии с определяемым из описания P разме-

ром объектов, указываемых с помощью P. например, на PDP-11

масштабирующий множитель равен 1 для CHAR, 2 для INT и

SHORT, 4 для LONG и FLOAT и 8 для DOUBLE.

Вычитание указателей тоже возможно: если P и Q указывают

на элементы одного и того же массива, то P-Q - количество