Смекни!
smekni.com

Методические указания к выполнению контрольных работ по дисциплине "Основы программирования" (стр. 28 из 40)

Таким образом, управляемая функциями alloc и free память является стеком или списком, в котором последний вводимый элемент извлекается первым. Стандартная библиотека языка «C» содержит аналогичные функции, не имеющие таких ограничений, и, кроме того, в главе 9 мы приведем улучшенные варианты. Между тем, однако, для многих приложений нужна только тривиальная функция alloc для распределения небольших участков памяти неизвестных заранее размеров в непредсказуемые моменты времени.

Простейшая реализация состоит в том, чтобы функция раздавала отрезки большого символьного массива, которому мы присвоили имя allocbuf. Этот массив является собственностью функций alloc и free. Так как они работают с указателями, а не с индексами массива, никакой другой функции не нужно знать имя этого массива. Он может быть описан как внешний статический, т.е. Он будет локальным по отношению к исходному файлу, содержащему alloc и free, и невидимым за его пределами. При практической реализации этот массив может даже не иметь имени; вместо этого он может быть получен в результате запроса к операционной системе на указатель некоторого неименованного блока памяти.

Другой необходимой информацией является то, какая часть массива allocbuf уже использована. Мы пользуемся указателем первого свободного элемента, названным allocp. Когда к функции alloc обращаются за выделением n символов, то она проверяет, достаточно ли осталось для этого места в allocbuf. Если достаточно, то alloc возвращает текущее значение allocp (т.е. начало свободного блока), затем увеличивает его на n, с тем чтобы он указывал на следующую свободную область. Функция free(p) просто полагает allocp равным p при условии, что p указывает на позицию внутри allocbuf.

Функция alloc:

#define null 0 //Указатель в случае ошибки

define allocsize 1000 //Доступный размер пространства

static char allocbuf[allocsize]; //Память для alloc

static char *allocp = allocbuf; //След. своб. позиция

сhar *alloc(int n) // Получить указатель на n символов

{

if (allocp + n <= allocbuf + allocsize)

{

allocp += n;

return(allocp - n); /* old p */

}

else // Нет достаточного участка памяти

return(null);

}

Функция free:

free(сhar *p) // Освободить память, указываемую в 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. Если запрос может быть удовлетворен, то allocp возвращает обычный указатель (обратите внимание на описание самой функции). Если же нет, то allocp должна вернуть некоторый признак, говорящий о том, что больше места не осталось.

В языке «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.

Пример 6-5. Вычитание указателей тоже возможно: если p и q указывают на элементы одного и того же массива, то p-q – это количество элементов между p и q. Этот факт можно использовать для написания еще одного варианта функции strlen:

strlen(char *s) // Получить длину строки s

{

char *p = s;

while (*p != '&bsol;0')

p++;

return(p-s);

}


При описании указатель p в этой функции инициализирован посредством строки s, в результате чего он указывает на первый символ строки. В цикле while по очереди проверяется каждый символ до тех пор, пока не появится символ конца строки &bsol;0. Так как значение &bsol;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.

6.5. Указатели символов и функции

Строчная константа, как, например, "Я - строка" является массивом символов. Компилятор завершает внутреннее представление такого массива символом &bsol;0, так что программы могут находить его конец. Таким образом, длина массива в памяти оказывается на единицу больше числа символов между двойными кавычками.

По-видимому чаще всего строчные константы появляются в качестве аргументов функций, как, например, в:

printf ("Здравствуй, Мир !&bsol;n");

когда символьная строка, подобная этой, появляется в программе, то доступ к ней осуществляется с помощью указателя символов; функция printf фактически получает указатель символьного массива.


Конечно, символьные массивы не обязаны быть только аргументами функций. Если описать message как:

char *message;

то в результате оператора:

message = "Now is the time";

переменная message станет указателем на фактический массив символов. Это не копирование строки; здесь участвуют только указатели. В языке «C» не предусмотрены какие-либо операции для обработки всей строки символов как целого.

Мы проиллюстрируем другие аспекты указателей и массивов, разбирая две полезные функции из стандартной библиотеки ввода-вывода, которая будет рассмотрена в главе 9.

Первая функция – это strcpy(s,t), которая копирует строку t в строку s. Аргументы написаны именно в этом порядке по аналогии с операцией присваивания, когда для того, чтобы присвоить t к s, обычно пишут s = t .

Пример 6-6. Сначала приведем версию с массивами:

void strcpy(char s[],char t[])// Скоприровать t в s

{

int i;

i = 0;

while ((s[i] = t[i]) != '&bsol;0')

i++;

}

Пример 6-7. Для сопоставления ниже даются 3 варианта strcpy с указателями:

void strcpy(char *s, char *t) // Вариант 1

{

while ((*s = *t) != '&bsol;0')

{

s++;

t++;

}

}

Так как аргументы передаются по значению, функция strcpy может использовать s и t так, как она пожелает. Здесь они с удобством полагаются указателями, которые передвигаются вдоль массивов, по одному символу за шаг, пока не будет скопирован в s завершающий в t символ &bsol;0.

Пример 6-8. На практике функция strcpy была бы записана не так, как мы показали выше. Вот вторая возможность:

strcpy(char *s, char *t) // Вариант 2

{

while ((*s++ = *t++) != '&bsol;0')

;

}

Здесь увеличение s и t внесено в проверочную часть. Значением *t++ является символ, на который указывал t до увеличения; постфиксная операция ++ не изменяет t, пока этот символ не будет извлечен. Точно так же этот символ помещается в старую позицию s, до того как s будет увеличено. Конечный результат заключается в том, что все символы, включая завершающий &bsol;0, копируются из t в s.

Пример 6-9. И как последнее сокращение мы опять отметим, что сравнение с &bsol;0 является излишним, так что функцию можно записать в виде:

strcpy(char *s, char *t) // Вариант 3

{

while (*s++ = *t++)

;

}

хотя с первого взгляда эта запись может показаться загадочной, она дает значительное удобство. Этой идиомой следует овладеть уже хотя бы потому, что вы с ней будете часто встречаться в «C»-программах.