Если P является указателем, то каков бы ни был сортобъекта, на который он указывает, операция P++ увеличивает Pтак, что он указывает на следующий элемент набора этихобъектов, а операция P +=I увеличивает P так, чтобы он ука-зывал на элемент, отстоящий на I элементов от текущего эле-мента.эти и аналогичные конструкции представляют собой самыепростые и самые распространенные формы арифметики указателейили адресной арифметики. Язык "C" последователен и постоянен в своем подходе кадресной арифметике; объединение в одно целое указателей,массивов и адресной арифметики является одной из наиболеесильных сторон языка. Давайте проиллюстрируем некоторые изсоответствующих возможностей языка на примере элементарной(но полезной, несмотря на свою простоту) программы распреде-ления памяти. Имеются две функции: функция ALLOC(N) возвра-щает в качестве своего значения указатель P, который указы-вает на первую из N последовательных символьных позиций, ко-торые могут быть использованы вызывающей функцию ALLOC прог-раммой для хранения символов; функция FREE(P) освобождаетприобретенную таким образом память, так что ее в дальнейшемможно снова использовать. программа является "элементарной",потому что обращения к FREE должны производиться в порядке,обратном тому, в котором производились обращения к ALLOC.Таким образом, управляемая функциями ALLOC и FREE память яв-ляется стеком или списком, в котором последний вводимый эле-мент извлекается первым. Стандартная библиотека языка "C"содержит аналогичные функции, не имеющие таких ограничений,и, кроме того, в главе 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;) Дадим некоторые пояснения. Вообще говоря, указатель мо-жет быть инициализирован точно так же, как и любая другаяпеременная, хотя обычно единственными осмысленными значения-ми являются 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 истинно, если 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 - количествоэлементов между P и Q. Этот факт можно использовать для на-писания еще одного варианта функции STRLEN: STRLEN(S) /* RETURN LENGTH OF STRING S */ CHAR *S; { CHAR *P = S; WHILE (*P != '\0') P++; RETURN(P-S); } При описании указатель P в этой функции инициализированпосредством строки S, в результате чего он указывает на пер-вый символ строки. В цикле WHILE по очереди проверяется каж-дый символ до тех пор, пока не появится символ конца строки\0. Так как значение \0 равно нулю, а WHILE только выясняет,имеет ли выражение в нем значение 0, то в данном случае яв-ную проверку можно опустить. Такие циклы часто записывают ввиде WHILE (*P) P++; Так как P указывает на символы, то оператор P++ передви-гает P каждый раз так, чтобы он указывал на следующий сим-вол. В результате P-S дает число просмотренных символов, т.е. Длину строки. Арифметика указателей последовательна:если бы мы имели дело с переменными типа FLOAT, которые за-нимают больше памяти, чем переменные типа CHAR, и если бы Pбыл указателем на FLOAT, то оператор P++ передвинул бы P наследующее FLOAT. таким образом, мы могли бы написать другойвариант функции ALLOC, распределяющей память для FLOAT,вместо CHAR, просто заменив всюду в ALLOC и FREE описательCHAR на FLOAT. Все действия с указателями автоматически учи-тывают размер объектов, на которые они указывают, так чтобольше ничего менять не надо. За исключением упомянутых выше операций (сложение и вы-читание указателя и целого, вычитание и сравнение двух ука-зателей), вся остальная арифметика указателей является неза-конной. Запрещено складывать два указателя, умножать, де-лить, сдвигать или маскировать их, а также прибавлять к нимпеременные типа FLOAT или DOUBLE.