Керниган и Ритчи - Язык программирования Си (793773), страница 26
Текст из файла (страница 26)
Поскольку имя массива является синонимом расположения егоначального элемента, присваивание ра=&а[0] можно также записать в следующем виде:pa = a;Еще более удивительно (по крайней мере на первый взгляд) то, что а[i] можно записать как *(а+i).Вычисляя а[i], Си сразу преобразует его в *(a+i); указанные две формы записи эквивалентны. Из этогоследует, что полученные в результате применения оператора & записи &а[i] и a+i также будутэквивалентными, т. е.
и в том и в другом случае это адрес 1-го элемента после а. С другой стороны, если ра —указатель, то его можно использовать с индексом, т. е. запись pa[i] эквивалентна записи *(pa+i). Корочеговоря, элемент массива можно изображать как в виде указателя со смещением, так и в виде имени массивас индексом.Между именем массива и указателем, выступающим в роли имени массива, существует одно различие.Указатель — это переменная, поэтому можно написать ра=а или ра++. Но имя массива не являетсяпеременной, и записи вроде а=ра или а++ не допускаются.Если имя массива передается функции, то последняя получает в качестве аргумента адрес его начальногоэлемента. Внутри вызываемой функции этот аргумент является локальной переменной, содержащей адрес.Мы можем воспользоваться отмеченным фактом и написать еще одну версию функции strlen,вычисляющей длину строки./* strlen: возвращает длину строки */int strlen(char *s){int n;for (n = 0; *s != '\0' ; s++)n++;return n;}Так как переменная s — указатель, к ней применима операция ++; s++ не оказывает никакого влияния настроку символов функции, которая обратилась к strlen.
Просто увеличивается на 1 некоторая копияуказателя, находящаяся в личном пользовании функции strlen. Это значит, что все вызовы, такие как:strlen("3дравствуй, мир"); /* строковая константа */strlen(array); /* char array[100]; */strlen(ptr); /* char *ptr; */правомерны.Формальные параметрыchar s[];иchar *s;в определении функции эквивалентны. Мы отдаем предпочтение последнему варианту, поскольку он болееявно сообщает, что s есть указатель. Если функции в качестве аргумента передается имя массива, то онаможет рассматривать его так, как ей удобно — либо, как имя массива, либо как указатель, и поступать с нимсоответственно.
Она может даже использовать оба вида записи, если это покажется уместным и понятным.Функции можно передать часть массива, для этого аргумент должен указывать на начало подмассива.Например, если а — массив, то в записяхf(&a[2])илиf(a+2)функции f передается адрес подмассива, начинающегося с элемента а[2]. Внутри функции f описаниепараметров может выглядеть какf(int arr[]) {…}илиf(int *arr) {…}Следовательно, для f тот факт, что параметр указывает на часть массива, а не на весь массив, не имеетзначения.Если есть уверенность, что элементы массива существуют, то возможно индексирование и в "обратную"сторону по отношению к нулевому элементу; выражения р[-1], р[-2] и т.
д. не противоречат синтаксисуязыка и обращаются к элементам, стоящим непосредственно перед р[0]. Разумеется, нельзя "выходить" заграницы массива и тем самым обращаться к несуществующим объектам.5.4. Адресная арифметикаЕсли р есть указатель на некоторый элемент массива, то р++ увеличивает р так, чтобы он указывал наследующий элемент, а р += i увеличивает его, чтобы он указывал на i-й элемент после того, на которыйуказывал ранее. Эти и подобные конструкции — самые простые примеры арифметики над указателями,называемой также адресной арифметикой.Си последователен и единообразен в своем подходе к адресной арифметике.
Это соединение в одном языкеуказателей, массивов и адресной арифметики — одна из сильных его сторон. Проиллюстрируем сказанноепостроением простого распределителя памяти, состоящего из двух программ. Первая, alloc(n),возвращает указатель р на n последовательно расположенных ячеек типа char; программой, обращающейсяк alloc, эти ячейки могут быть использованы для запоминания символов.
Вторая, afree(р), освобождаетпамять для, возможно, повторной ее утилизации. Простота алгоритма обусловлена предположением, чтообращения к afree делаются в обратном порядке по отношению к соответствующим обращениям к alloc.Таким образом, память, с которой работают alloc и afree, является стеком (списком, в основе котороголежит принцип "последним вошел, первым ушел").
В стандартной библиотеке имеются функции malloc иfree, которые делают то же самое, только без упомянутых ограничений; в параграфе 8.7 мы покажем, какони выглядят.Функцию alloc легче всего реализовать, если условиться, что она будет выдавать куски некоторогобольшого массива типа char, который мы назовем allocbuf. Этот массив отдадим в личное пользованиефункциям alloc и afree. Так как они имеют дело с указателями, а не с индексами массива, то другимпрограммам знать его имя не нужно. Кроме того, этот массив можно определить в том же исходном файле,что и alloc и afree, объявив его static, благодаря чему он станет невидимым вне этого файла.
Напрактике такой массив может и вовсе не иметь имени, поскольку его можно запросить с помощью malloc уоперационной системы и получить указатель на некоторый безымянный блок памяти.Естественно, нам нужно знать, сколько элементов массива allocbuf уже занято. Мы введем указательallocp, который будет указывать на первый свободный элемент. Если запрашивается память для nсимволов, то alloc возвращает текущее значение allocp (т. е.
адрес начала свободного блока) и затемувеличивает его на n, чтобы указатель allocp указывал на следующую свободную область. Если жепространства нет, то alloc выдает нуль. Функция afree[р] просто устанавливает allocp в значение р,если оно не выходит за пределы массива allocbuf.Перед вызовом alloc:После вызова alloc:#define ALLOCSIZE 10000 /* размер доступного пространства */static char allocbuf[ALLOCSIZE]; /* память для alloc */static char *allocp = allocbuf; /* указатель на своб. место */char *alloc(int n) /* возвращает указатель на п символов */{if (allocbuf + ALLOCSIZE - allocp >= n) {allocp += n; /* пространство есть */return allocp - n; /* старое р */} else /* пространства нет */return 0;}void afree(char *p) /* освобождает память, на которую указывает р */{if (р >= allocbuf && р < allocbuf + ALLOCSIZE)allocp = р;}В общем случае указатель, как и любую другую переменную, можно инициализировать, но только такимиосмысленными для него значениями, как нуль или выражение, приводящее к адресу ранее определенныхданных соответствующего типа.
Объявлениеstatic char *allocp = allocbuf;определяет allocp как указатель на char и инициализирует его адресом массива allocbuf, посколькуперед началом работы программы массив allocbuf пуст. Указанное объявление могло бы иметь и такойвид:static char *allocp = &allocbuf[0];поскольку имя массива и есть адрес его нулевого элемента.Проверкаif (allocbuf + ALLOCSIZE - allocp >= n) { /* годится */контролирует, достаточно ли пространства, чтобы удовлетворить запрос на n символов.
Если памятидостаточно, то новое значение для allocp должно указывать не далее чем на следующую позицию запоследним элементом allocbuf. При выполнении этого требования alloc выдает указатель на началовыделенного блока символов (обратите внимание на объявление типа самой функции). Если требование невыполняется, функция alloc должна выдать какой-то сигнал о том, что памяти не хватает.
Си гарантирует,что нуль никогда не будет правильным адресом для данных, поэтому мы будем использовать его в качествепризнака аварийного события, в нашем случае нехватки памяти.Указатели и целые не являются взаимозаменяемыми объектами. Константа нуль — единственное исключениеиз этого правила: ее можно присвоить указателю, и указатель можно сравнить с нулевой константой. Чтобыпоказать, что нуль — это специальное значение для указателя, вместо цифры нуль, как правило, записываютNULL — константу, определенную в файле <stdio.h>.
С этого момента и мы будем ею пользоваться.Проверкиif (allocbuf + ALLOCSIZE - allocp >= n) { /* годится */иif (p >= allocbuf && p < allocbuf + ALLOCSIZE)демонстрируют несколько важных свойств арифметики с указателями. Во-первых, при соблюдениинекоторых правил указатели можно сравнивать.Если р и q указывают на элементы одного массива, то к ним можно применять операторы отношения ==, !=,<, >= и т. д. Например, отношение видаp < qистинно, если р указывает на более ранний элемент массива, чем q. Любой указатель всегда можно сравнитьна равенство и неравенство с нулем. А вот для указателей, не указывающих на элементы одного массива,результат арифметических операций или сравнений не определен.
(Существует одно исключение: варифметике с указателями можно использовать адрес несуществующего "следующего за массивом"элемента, т. е. адрес того "элемента", который станет последним, если в массив добавить еще один элемент.)Во-вторых, как вы уже, наверное, заметили, указатели и целые можно складывать и вычитать. Конструкцияр + nозначает адрес объекта, занимающего n-е место после объекта, на который указывает р.














