Регистрация Вход
Библиотека /
Поиск по библиотекеМоя библиотекаИскать книгу(обмен)

Андрей Богатырев. Хрестоматия по программированию на Си в Unix

Андрей Богатырев. Хрестоматия по программированию на Си в Unix


© Copyright Андрей Богатырев. 1992-95


Email: abs@decart.msu.su Txt version is located at
А. Богатырев, 1992-95 - 1 - Си в UNIX

0. Напутствие в качестве вступления.


Ум подобен желудку. Важно не то, сколько ты в него вложишь, а то, сколько он сможет переварить. В этой книге вы найдете ряд задач, примеров, алгоритмов, советов и стилистичес- ких замечаний по использованию языка программирования "C" (Си) в среде операционной системы UNIX. Здесь собраны этюды разной сложности и "штрихи к портрету" языка Си. Также описаны различные "подводные камни" на которых нередко терпят крушение новички

не надо


Си". В большинстве случаев в качестве платформы используется персональный компьютер IBM PC с какой-либо системой UNIX, либо SPARCstation 20 с системой Solaris 2 (тоже UNIX svr4), но многие примеры без каких-либо изменений (либо с минимумом таковых) могут быть перенесены в среду MS DOS|=, либо на другой тип машины с системой UNIX. Это ваша ВТОРАЯ книга по Си. Эта книга не учебник, а хрестоматия к учебнику. Она не является ни систематическим курсом по Си, ни справочником по нему, и предназ- начена не для одноразового последовательного прочтения, а для чтения в несколько про-

вместе


щим" учебником по Си, среди которых наиболее известна книга Кернигана и Ритчи. Эта книга - не ПОСЛЕДНЯЯ ваша книга по Си. Во-первых потому, что кое-что в языке все же меняется со временем, хотя и настал час, когда стандарт на язык Си наконец принят... Но появился язык C++, который развивается довольно динамично. Еще есть

библиотеки


виваются вслед за развитием UNIX и других операционных систем. Следующими вашими (настольными) книгами должны стать "Справочное руководство": man2 (по системным вызо- вам), man3 (по библиотечным функциям). Мощь языка Си - в существующем многообразии библиотек. Прошу вас с первых же шагов следить за стилем оформления своих программ. Делайте отступы, пишите комментарии, используйте осмысленные имена переменных и функций, отделяйте логические части программы друг от друга пустыми строками. Помните, что "лишние" пробелы и пустые строки в Си допустимы везде, кроме изображений констант и имен. Программы на Си, набитые в одну колонку (как на FORTRAN-e) очень тяжело читать и понимать. Из-за этого бывает трудно находить потерянные скобки { и }, потерянные символы `;' и другие ошибки. Существует несколько "школ" оформления программ - приглядитесь к примерам в этой книге и в других источниках - и выберите любую! Ничего страшного, если вы будете смешивать эти стили. Но - ПОДАЛЬШЕ ОТ FORTRAN-а !!! Программу можно автоматически сформатировать к "каноническому" виду при помощи, например, программы cb.

НашФайл


tmp


но лучше сразу оформлять программу правильно. Выделяйте логически самостоятельные ("замкнутые") части программы в функции (даже если они будут вызываться единственный раз). Функции - не просто средство избежать повторения одних и тех же операторов в тексте программы, но и средство структурирования процесса программирования, делающее программу более понятной. Во- первых, вы можете в другой программе использовать текст уже написанной вами ранее функции вместо того, чтобы писать ее заново. Во-вторых, операцию, оформленную в виде функции, можно рассматривать как неделимый примитив (от довольно простого по смыслу, вроде strcmp, strcpy, до довольно сложного - qsort, malloc, gets) и забыть о его внутреннем устройстве (это хорошо - надо меньше помнить). ____________________ |= MS DOS - торговый знак фирмы Microsoft Corporation. (читается "Майкрософт"); DOS - дисковая операционная система. А. Богатырев, 1992-95 - 2 - Си в UNIX Не гонитесь за краткостью в ущерб ясности. Си позволяет порой писать такие выра- жения, над которыми можно полчаса ломать голову. Если же их записать менее мудрено, но чуть длиннее - они самоочевидны (и этим более защищены от ошибок). В системе UNIX вы можете посмотреть описание любой команды системы или функции Си, набрав команду

названиеФункции


manual


Еще одно напутствие: учите английский язык! Практически все языки программирова- ния используют английские слова (в качестве ключевых слов, терминов, имен переменных и функций). Поэтому лучше понимать значение этих слов (хотя и восприятие их как просто неких символов тоже имеет определенные достоинства). Обратно - программирова- ние на Си поможет вам выучить английский. По различным причинам на территории России сейчас используется много разных восьмибитных русских кодировок. Среди них: КОИ-8 Исторически принятая на русских UNIX системах - самая ранняя из появившихся.

c


все же читаема с терминала как транслитерация латинских букв. Именно этой коди- ровкой пользуется автор этой книги (как и большинство UNIX-sites сети RelCom). ISO 8859/5 Это американский стандарт на русскую кодировку. А русские программисты к ее разработке не имеют никакого отношения. Ею пользуется большинство коммерческих баз данных. Microsoft 1251 Это та кодировка, которой пользуется Microsoft Windows. Возможно, что именно к этой кодировке придут и UNIX системы (гипотеза 1994 года).

MS DOS


Русская кодировка с псевдографикой, использовавшаяся в MS DOS. Кодировка для Macintosh Это великое "разнообразие" причиняет массу неудобств. Но, господа, это Россия - что значит - широта души и абсолютный бардак. Relax and enjoy. Многие примеры в данной книге даны вместе с ответами - как образцами для подра- жания. Однако мы надеемся, что Вы удержитесь от искушения и сначала проверите свои силы, а лишь потом посмотрите в ответ! Итак, читая примеры - делайте по аналогии. А. Богатырев, 1992-95 - 3 - Си в UNIX

1. Простые программы и алгоритмы. Сюрпризы, советы.


1.1. Составьте программу приветствия с использованием функции printf. По традиции

Hello


1.2. Найдите ошибку в программе #include <stdio.h> main(){ printf("Hello, world\n"); } Ответ: раз не объявлено иначе, функция main считается возвращающей целое значение (int). Но функция main не возвращает ничего - в ней просто нет оператора return. Корректно было бы так: #include <stdio.h> main(){ printf("Hello, world\n"); return 0; } или #include <stdio.h> void main(){ printf("Hello, world\n"); exit(0); } а уж совсем корректно - так: #include <stdio.h>

argc


printf("Hello, world\n"); return 0; } 1.3. Найдите ошибки в программе #include studio.h main { int i i := 43 print ('В году i недель') } 1.4. Что будет напечатано в приведенном примере, который является частью полной программы: int n; n = 2; printf ("%d + %d = %d\n", n, n, n + n); 1.5. В чем состоят ошибки? А. Богатырев, 1992-95 - 4 - Си в UNIX if( x > 2 ) then x = 2; if x < 1 x = 1; Ответ: в Си нет ключевого слова then, условия в операторах if, while должны браться в ()-скобки. 1.6. Напишите программу, печатающую ваше имя, место работы и адрес. В первом вари- анте программы используйте библиотечную функцию printf, а во втором - puts. 1.7. Составьте программу с использованием следующих постфиксных и префиксных опера- ций: a = b = 5 a + b a++ + b ++a + b --a + b a-- + b Распечатайте полученные значения и проанализируйте результат. 1.8. Цикл for ________________________________________________________________________________

INIT


BODY


________________________________________________________________________________

INIT


repeat:

CONDITION


BODY


cont:

INCR


goto repeat; } out: ; Цикл while ________________________________________________________________________________

COND


BODY


________________________________________________________________________________ cont: repeat:

CONDITION


BODY


goto repeat; } out: ; А. Богатырев, 1992-95 - 5 - Си в UNIX Цикл do ________________________________________________________________________________ do

BODY


CONDITION


________________________________________________________________________________ cont: repeat:

BODY


CONDITION


out: ;

внутри


continue; которые означают на наших схемах следующее:

out


cont


1.9. Составьте программу печати прямоугольного треугольника из звездочек * ** *** **** ***** используя цикл for. Введите переменную, значением которой является размер катета тре- угольника.

WIDTH


x0


'-'. Ответ: int x; for(x=0; x < x0; ++x) putchar('-'); for( ; x < x0 + w; x++) putchar('*'); for( ; x < WIDTH ; ++x) putchar('-'); putchar('\n'); либо for(x=0; x < WIDTH; x++) putchar( x < x0 ? '-' : x < x0 + w ? '*' : '-' ); putchar('\n'); 1.11. Напишите программу с циклами, которая рисует треугольник: * *** ***** ******* ********* А. Богатырев, 1992-95 - 6 - Си в UNIX Ответ: /* Треугольник из звездочек */ #include <stdio.h> /* Печать n символов c */ printn(c, n){ while( --n >= 0 ) putchar(c); } int lines = 10; /* число строк треугольника */ void main(argc, argv) char *argv[]; { register int nline; /* номер строки */ register int naster; /* количество звездочек в строке */ register int i; if( argc > 1 ) lines = atoi( argv[1] ); for( nline=0; nline < lines ; nline++ ){ naster = 1 + 2 * nline; /* лидирующие пробелы */ printn(' ', lines-1 - nline); /* звездочки */ printn('*', naster); /* перевод строки */ putchar( '\n' ); } exit(0); /* завершение программы */ } 1.12. В чем состоит ошибка? main(){ /* печать фразы 10 раз */ int i; while(i < 10){ printf("%d-ый раз\n", i+1); i++; } }

i


любое


том числе и 0 по случайности). Не забывайте инициализировать переменные, возьмите

правило


i


i


В данном примере было бы еще лучше использовать цикл for, в котором все операции над индексом цикла собраны в одном месте - в заголовке цикла: for(i=0; i < 10; i++) printf(...); А. Богатырев, 1992-95 - 7 - Си в UNIX 1.13. Вспомогательные переменные, не несущие смысловой нагрузки (вроде счетчика пов- торений цикла, не используемого в самом теле цикла) принято по традиции обозначать

i


main(){ int _ ; for( _ = 0; _ < 10; _++) printf("%d\n", _ ); } основанные на том, что подчерк в идентификаторах - полноправная буква. 1.14. Найдите 2 ошибки в программе: main(){ int x = 12; printf( "x=%d\n" ); int y; y = 2 * x; printf( "y=%d\n", y ); } Комментарий: в теле функции все описания должны идти перед всеми выполняемыми опера- торами (кроме операторов, входящих в состав описаний с инициализацией). Очень часто после внесения правок в программу некоторые описания оказываются после выполняемых операторов. Именно поэтому рекомендуется отделять строки описания переменных от

пустыми строками


номии места). 1.15. Найдите ошибку: int n; n = 12; main(){ int y; y = n+2; printf( "%d\n", y ); } Ответ: выполняемый оператор n=12 находится вне тела какой-либо функции. Следует внести его в main() после описания переменной y, либо переписать объявление перед main() в виде int n = 12; В последнем случае присваивание переменной n значения 12 выполнит компилятор еще во время компиляции программы, а не сама программа при своем запуске. Точно так же про- исходит со всеми статическими данными (описанными как static, либо расположенными вне всех функций); причем если их начальное значение не указано явно - то подразумевается 0 ('\0', NULL, ""). Однако нулевые значения не хранятся в скомпилированном выполняе- мом файле, а требуемая "чистая" память расписывается при старте программы. 1.16. По поводу описания переменной с инициализацией:

x


является (почти) эквивалентом для

x


x


А. Богатырев, 1992-95 - 8 - Си в UNIX Рассмотрим пример:

stdio


extern double sqrt(); /* квадратный корень */

x


s12


y


fp


main(){

ss


... } Строки с метками #1, #2 и #3 ошибочны. Почему?

s12


так как описаны вне какой-либо функции) выражение должно содержать только константы, поскольку оно вычисляется КОМПИЛЯТОРОМ. Поэтому ни использование значений переменных, ни вызовы функций здесь недопустимы (но можно брать адреса от переменных).

ss


выполнения


уже не компилятором, а самой программой, что дает нам право использовать переменные, вызовы функций и.т.п., то есть выражения языка Си без ограничений. 1.17. Напишите программу, реализующую эхо-печать вводимых символов. Программа должна завершать работу при получении признака EOF. В UNIX при вводе с клавиатуры признак EOF обычно обозначается одновременным нажатием клавиш CTRL и D (CTRL чуть раньше), что в дальнейшем будет обозначаться CTRL/D; а в MS DOS - клавиш CTRL/Z. Используйте getchar() для ввода буквы и putchar() для вывода. 1.18. Напишите программу, подсчитывающую число символов поступающих со стандартного ввода. Какие достоинства и недостатки у следующей реализации:

stdio


cnt


cnt


cnt


} Ответ: и достоинство и недостаток в том, что счетчик имеет тип double. Достоинство -

очень


гораздо медленнее


работать дольше. В повседневных задачах вам вряд ли понадобится иметь счетчик,

cnt


1.19. Составьте программу перекодировки вводимых символов со стандартного ввода по следующему правилу: a -> b b -> c c -> d ... z -> a другой символ -> * Коды строчных латинских букв расположены подряд по возрастанию. 1.20. Составьте программу перекодировки вводимых символов со стандартного ввода по следующему правилу: А. Богатырев, 1992-95 - 9 - Си в UNIX B -> A C -> B ... Z -> Y другой символ -> * Коды прописных латинских букв также расположены по возрастанию. 1.21. Напишите программу, печатающую номер и код введенного символа в восьмеричном и

строку


нажмете клавишу ENTER, то программа напечатает вам на один символ больше, чем вы наб- рали. Дело в том, что код клавиши ENTER, завершившей ввод строки - символ '\n' -

тоже


начало следующей строки!). 1.22. Разберитесь, в чем состоит разница между символами '0' (цифра нуль) и '\0' (нулевой байт). Напечатайте printf( "%d %d %c\n", '\0', '0', '0' ); Поставьте опыт: что печатает программа? main(){ int c = 060; /* код символа '0' */ printf( "%c %d %o\n", c, c, c); } Почему печатается 0 48 60? Теперь напишите вместо int c = 060; строчку char c = '0'; 1.23. Что напечатает программа? #include <stdio.h> void main(){ printf("ab\0cd\nxyz"); putchar('\n'); } Запомните, что '\0' служит признаком конца строки в памяти, а '\n' - в файле. Что в строке "abcd\n" на конце неявно уже расположен нулевой байт: 'a','b','c','d','\n','\0' Что строка "ab\0cd\nxyz" - это 'a','b','\0','c','d','\n','x','y',z','\0' Что строка "abcd\0" - избыточна, поскольку будет иметь на конце два нулевых байта (что не вредно, но зачем?). Что printf печатает строку до нулевого байта, а не до закрывающей кавычки.

ab


Вопрос: чему равен sizeof("ab\0cd\nxyz")? Ответ: 10. 1.24. Напишите программу, печатающую целые числа от 0 до 100. 1.25. Напишите программу, печатающую квадраты и кубы целых чисел. А. Богатырев, 1992-95 - 10 - Си в UNIX 1.26. Напишите программу, печатающую сумму квадратов первых n целых чисел. 1.27. Напишите программу, которая переводит секунды в дни, часы, минуты и секунды. 1.28. Напишите программу, переводящую скорость из километров в час в метры в секун- дах. 1.29. Напишите программу, шифрующую текст файла путем замены значения символа (нап-

C


1.30. Напишите программу, которая при введении с клавиатуры буквы печатает на терми- нале ключевое слово, начинающееся с данной буквы. Например, при введении буквы 'b' печатает "break". 1.31. Напишите программу, отгадывающую задуманное вами число в пределах от 1 до 200, пользуясь подсказкой с клавиатуры "=" (равно), "<" (меньше) и ">" (больше). Для уга- дывания числа используйте метод деления пополам. 1.32. Напишите программу, печатающую степени двойки 1, 2, 4, 8, ... Заметьте, что, начиная с некоторого n, результат становится отрицательным из-за пере- полнения целого. 1.33. Напишите подпрограмму вычисления квадратного корня с использованием метода касательных (Ньютона): x(0) = a 1 a x(n+1) = - * ( ---- + x(n)) 2 x(n) Итерировать, пока не будет | x(n+1) - x(n) | < 0.001 Внимание! В данной задаче массив не нужен. Достаточно хранить текущее и предыду-

x


1.34. Напишите программу, распечатывающую простые числа до 1000. 1, 2, 3, 5, 7, 11, 13, 17, ... А. Богатырев, 1992-95 - 11 - Си в UNIX /*#!/bin/cc primes.c -o primes -lm * Простые числа. */ #include <stdio.h> #include <math.h> int debug = 0; /* Корень квадратный из числа по методу Ньютона */ #define eps 0.0001 double sqrt (x) double x; { double sq, sqold, EPS; if (x < 0.0) return -1.0; if (x == 0.0) return 0.0; /* может привести к делению на 0 */ EPS = x * eps; sq = x; sqold = x + 30.0; /* != sq */ while (fabs (sq * sq - x) >= EPS) { /* fabs( sq - sqold )>= EPS */ sqold = sq; sq = 0.5 * (sq + x / sq); } return sq; } /* таблица прoстых чисел */

_


register int i, up; int not_div; if (t == 2 || t == 3 || t == 5 || t == 7) return 1; /* prime */ if (t % 2 == 0 || t == 1) return 0; /* composite */ up = ceil (sqrt ((double) t)) + 1; i = 3; not_div = 1; while (i <= up && not_div) { if (t % i == 0) { if (debug) fprintf (stderr, "%d поделилось на %d\n", t, i); not_div = 0; break; } i += 2; /* * Нет смысла проверять четные, * потому что если делится на 2*n, * то делится и на 2, * а этот случай уже обработан выше. */ } return not_div; } А. Богатырев, 1992-95 - 12 - Си в UNIX #define COL 6 int n; main (argc, argv) char **argv; { int i, j; int n; if( argc < 2 ){ fprintf( stderr, "Вызов: %s число [-]\n", argv[0] ); exit(1); } i = atoi (argv[1]); /* строка -> целое, ею изображаемое */ if( argc > 2 ) debug = 1; printf ("\t*** Таблица простых чисел от 2 до %d ***\n", i); n = 0; for (j = 1; j <= i; j++) if (is_prime (j)){ /* распечатка в COL колонок */ printf ("%3d%s", j, n == COL-1 ? "\n" : "\t"); if( n == COL-1 ) n = 0; else n++; } printf( "\n---\n" ); exit (0); } 1.35. Составьте программу ввода двух комплексных чисел в виде A + B * I (каждое на отдельной строке) и печати их произведения в том же виде. Используйте scanf и printf. Перед тем, как использовать scanf, проверьте себя: что неверно в нижеприведенном опе- раторе?

x


x


x


x


отрезка пополам. Приведем реализацию этого алгоритма для поиска целочисленного квад- ратного корня из целого числа (этот алгоритм может использоваться, например, в машин- ной графике при рисовании дуг): /* Максимальное unsigned long число */ #define MAXINT (~0L) /* Определим имя-синоним для типа unsigned long */ typedef unsigned long ulong; /* Функция, корень которой мы ищем: */

x


x


x


/* Начальный интервал. Должен выбираться исходя из * особенностей функции FUNC */

_


_


/* КОРЕНЬ КВАДРАТНЫЙ, округленный вниз до целого. * Решается по методу деления отрезка пополам:

x


А. Богатырев, 1992-95 - 13 - Си в UNIX */

_


mid


rgt


lft


lft


mid


/* +1 для ошибок округления при целочисленном делении */

mid


rgt


rgt


lft


lft


mid


}

i


i


i


} Использованное нами при объявлении переменных ключевое слово register означает, что переменная является ЧАСТО ИСПОЛЬЗУЕМОЙ, и компилятор должен попытаться разместить ее на регистре процессора, а не в стеке (за счет чего увеличится скорость обращения к этой переменной). Это слово используется как

переменная


переменная


переменная


1.37. Напишите программу, вычисляющую числа треугольника Паскаля и печатающую их в виде треугольника. C(0,n) = C(n,n) = 1 n = 0... C(k,n+1) = C(k-1,n) + C(k,n) k = 1..n n - номер строки В разных вариантах используйте циклы, рекурсию. 1.38. Напишите функцию вычисления определенного интеграла методом Монте-Карло. Для этого вам придется написать генератор случайных чисел. Си предоставляет стандартный датчик ЦЕЛЫХ равномерно распределенных псевдослучайных чисел: если вы хотите получить целое число из интервала [A..B], используйте int x = A + rand() % (B+1-A);

разные


последовательности (это называется "рандомизация") при помощи srand( число ); /* лучше нечетное */

одну и ту же


должны поступать так: srand(NBEG); x=rand(); ... ; x=rand(); /* и повторить все сначала */ srand(NBEG); x=rand(); ... ; x=rand(); Используемый метод получения случайных чисел таков: А. Богатырев, 1992-95 - 14 - Си в UNIX static unsigned long int next = 1L; int rand(){ next = next * 1103515245 + 12345; return ((unsigned int)(next/65536) % 32768); } void srand(seed) unsigned int seed; { next = seed; } Для рандомизации часто пользуются таким приемом:

t


t


1.39. Напишите функцию вычисления определенного интеграла по методу Симпсона. /*#!/bin/cc $* -lm * Вычисление интеграла по методу Симпсона */ #include <math.h> extern double integral(), sin(), fabs(); #define PI 3.141593 double myf(x) double x; { return sin(x / 2.0); } int niter; /* номер итерации */ void main(){ double integral(); printf("%g\n", integral(0.0, PI, myf, 0.000000001)); /* Заметьте, что myf, а не myf(). * Точное значение интеграла равно 2.0 */ printf("%d итераций\n", niter ); } А. Богатырев, 1992-95 - 15 - Си в UNIX double integral(a, b, f, eps) double a, b; /* концы отрезка */ double eps; /* требуемая точность */ double (*f)(); /* подынтегральная функция */ { register long i; double fab = (*f)(a) + (*f)(b); /* сумма на краях */ double h, h2; /* шаг и удвоенный шаг */ long n, n2; /* число точек разбиения и оно же удвоенное */ double Sodd, Seven; /* сумма значений f в нечетных и в четных точках */ double S, Sprev;/* значение интеграла на данной и на предыдущей итерациях */ double x; /* текущая абсцисса */ niter = 0; n = 10L; /* четное число */ n2 = n * 2; h = fabs(b - a) / n2; h2 = h * 2.0; /* Вычисляем первое приближение */ /* Сумма по нечетным точкам: */ for( Sodd = 0.0, x = a+h, i = 0; i < n; i++, x += h2 ) Sodd += (*f)(x); /* Сумма по четным точкам: */ for( Seven = 0.0, x = a+h2, i = 0; i < n-1; i++, x += h2 ) Seven += f(x); /* Предварительное значение интеграла: */ S = h / 3.0 * (fab + 4.0 * Sodd + 2.0 * Seven ); do{ niter++; Sprev = S; /* Вычисляем интеграл с половинным шагом */ h2 = h; h /= 2.0; if( h == 0.0 ) break; /* потеря значимости */ n = n2; n2 *= 2; Seven = Seven + Sodd; /* Вычисляем сумму по новым точкам: */ for( Sodd = 0.0, x = a+h, i = 0; i < n; i++, x += h2 ) Sodd += (*f)(x); /* Значение интеграла */ S = h / 3.0 * (fab + 4.0 * Sodd + 2.0 * Seven ); } while( niter < 31 && fabs(S - Sprev) / 15.0 >= eps ); /* Используем условие Рунге для окончания итераций */ return ( 16.0 * S - Sprev ) / 15.0 ; /* Возвращаем уточненное по Ричардсону значение */ } А. Богатырев, 1992-95 - 16 - Си в UNIX 1.40. Где ошибка? struct time_now{ int hour, min, sec; } X = { 13, 08, 00 }; /* 13 часов 08 минут 00 сек.*/ Ответ: 08 - восьмеричное число (так как начинается с нуля)! А в восьмеричных числах цифры 8 и 9 не бывают. 1.41. Дан текст: int i = -2; i <<= 2; printf("%d\n", i); /* печать сдвинутого i : -8 */ i >>= 2; printf("%d\n", i); /* печатается -2 */ Закомментируем две строки (исключая их из программы): int i = -2; i <<= 2; /* printf("%d\n", i); /* печать сдвинутого i : -8 */ i >>= 2; */ printf("%d\n", i); /* печатается -2 */ Почему теперь возникает ошибка? Указание: где кончается комментарий? Ответ: Си не допускает вложенных комментариев. Вместо этого часто используются конструкции вроде: #ifdef COMMENT ... закомментированный текст ... #endif /*COMMENT*/ и вроде /**/ printf("here");/* отладочная выдача включена */ /* printf("here");/* отладочная выдача выключена */ или /* выключено(); /**/ включено(); /**/ А вот дешевый способ быстро исключить оператор (с возможностью восстановления) - конец комментария занимает отдельную строку, что позволяет отредактировать такой текст редактором почти не сдвигая курсор: /*printf("here"); */ 1.42. Почему программа печатает неверное значение для i2 ? А. Богатырев, 1992-95 - 17 - Си в UNIX int main(int argc, char *argv[]){ int i1, i2; i1 = 1; /* Инициализируем i1 / i2 = 2; /* Инициализируем i2 */ printf("Numbers %d %d\n", i1, i2); return(0); } Ответ: в первом операторе присваивания не закрыт комментарий - весь второй оператор присваивания полностью проигнорировался! Правильный вариант: int main(int argc, char *argv[]){ int i1, i2; i1 = 1; /* Инициализируем i1 */ i2 = 2; /* Инициализируем i2 */ printf("Numbers %d %d\n", i1, i2); return(0); } 1.43. А вот "шальной" комментарий. void main(){ int n = 10; int *ptr = &n; int x, y = 40; x = y/*ptr /* должно быть 4 */ + 1; printf( "%d\n", x ); /* пять */ exit(0); } /* или такой пример из жизни - взят из переписки в Relcom */ ... cost = nRecords/*pFactor /* divided by Factor, and */ + fixMargin; /* plus the precalculated */ ... Результат непредсказуем. Дело в том, что y/*ptr превратилось в начало комментария! Поэтому бинарные операции принято окружать пробелами. x = y / *ptr /* должно быть 4 */ + 1; 1.44. Найдите ошибки в директивах препроцессора Си |- (вертикальная черта обозначает левый край файла). ____________________ |- Препроцессор Си - это программа /lib/cpp А. Богатырев, 1992-95 - 18 - Си в UNIX | | #include <stdio.h> |#include < sys/types.h > |# define inc (x) ((x) + 1) |#define N 12; |#define X -2 | |... printf( "n=%d\n", N ); |... p = 4-X; Ответ: в первой директиве стоит пробел перед #. Диез должен находиться в первой позиции строки. Во второй директиве в <> находятся лишние пробелы, не относящиеся к имени файла - препроцессор не найдет такого файла! В данном случае "красота" пошла во вред делу. В третьей - между именем макро inc и его аргументом в круглых скобках

x


параметром


однако, что пробелы после # перед именем директивы вполне допустимы. В четвертом случае показана характерная опечатка - символ ; после определения. В результате напи- санный printf() заменится на printf( "n=%d\n", 12; ); где лишняя ; даст синтаксическую ошибку. В пятом случае ошибки нет, но нас ожидает неприятность в строке p=4-X; которая расширится в строку p=4--2; являющуюся синтаксически неверной. Чтобы избежать подоб- ной ситуации, следовало бы написать p = 4 - X; /* через пробелы */ но еще проще (и лучше) взять макроопределение в скобки: #define X (-2) 1.45. Напишите функцию max(x, y), возвращающую большее из двух значений. Напишите аналогичное макроопределение. Напишите макроопределения min(x, y) и abs(x) (abs - модуль числа). Ответ: #define abs(x) ((x) < 0 ? -(x) : (x)) #define min(x,y) (((x) < (y)) ? (x) : (y)) Зачем x взят в круглые скобки (x)? Предположим, что мы написали #define abs(x) (x < 0 ? -x : x ) вызываем abs(-z) abs(a|b) получаем (-z < 0 ? --z : -z ) (a|b < 0 ? -a|b : a|b ) У нас появилась "дикая" операция --z; а выражение a|b<0 соответствует a|(b<0), с сов- сем другим порядком операций! Поэтому заключение всех аргументов макроса в его теле в круглые скобки позволяет избежать многих неожиданных проблем. Придерживайтесь этого правила!

все


#define div(x, y) (x)/(y) При вызове А. Богатырев, 1992-95 - 19 - Си в UNIX z = sizeof div(1, 2); превратится в z = sizeof(1) / (2); что равно sizeof(int)/2, а не sizeof(int). Вариант #define div(x, y) ((x) / (y)) будет работать правильно. 1.46. Макросы, в отличие от функций, могут порождать непредвиденные побочные эффекты: int sqr(int x){ return x * x; } #define SQR(x) ((x) * (x)) main(){ int y=2, z; z = sqr(y++); printf("y=%d z=%d\n", y, z); y = 2; z = SQR(y++); printf("y=%d z=%d\n", y, z); } Вызов функции sqr печатает "y=3 z=4", как мы и ожидали. Макрос же SQR расширяется в z = ((y++) * (y++));

z


1.47. ANSI препроцессор|- языка Си имеет оператор ## - "склейка лексем": #define VAR(a, b) a ## b #define CV(x) command_ ## x main(){ int VAR(x, 31) = 1; /* превратится в int x31 = 1; */ int CV(a) = 2; /* даст int command_a = 2; */ ... } Старые версии препроцессора не обрабатывают такой оператор, поэтому раньше использо- вался такой трюк: #define VAR(a, b) a/**/b в котором предполагается, что препроцессор удаляет комментарии из текста, не заменяя их на пробелы. Это не всегда так, поэтому такая конструкция не мобильна и пользо- ваться ею не рекомендуется. 1.48. Напишите программу, распечатывающую максимальное и минимальное из ряда чисел,

max


сразу при вводе очередного числа! ____________________ |- ANSI - American National Standards Institute, разработавший стандарт на язык Си и его окружение. А. Богатырев, 1992-95 - 20 - Си в UNIX #include <stdio.h> main(){ int max, min, x, n; for( n=0; scanf("%d", &x) != EOF; n++) if( n == 0 ) min = max = x; else{ if( x > max ) max = x; if( x < min ) min = x; } printf( "Ввели %d чисел: min=%d max=%d\n", n, min, max); } Напишите аналогичную программу для поиска максимума и минимума среди элементов мас-

min


1.49. Напишите программу, которая сортирует массив заданных чисел по возрастанию (убыванию) методом пузырьковой сортировки. Когда вы станете более опытны в Си, напи- шите сортировку методом Шелла. /* * Сортировка по методу Шелла. * Сортировке подвергается массив указателей на данные типа obj. * v------.-------.------.-------.------0 * ! ! ! ! * * * * * * элементы типа obj * Программа взята из книги Кернигана и Ритчи. */ #include <stdio.h> #include <string.h> #include <locale.h> #define obj char static shsort (v,n,compare) int n; /* длина массива */ obj *v[]; /* массив указателей */ int (*compare)(); /* функция сравнения соседних элементов */ { int g, /* расстояние, на котором происходит сравнение */ i,j; /* индексы сравниваемых элементов */ obj *temp; for( g = n/2 ; g > 0 ; g /= 2 ) for( i = g ; i < n ; i++ ) for( j = i-g ; j >= 0 ; j -= g ) { if((*compare)(v[j],v[j+g]) <= 0) break; /* уже в правильном порядке */ /* обменять указатели */ temp = v[j]; v[j] = v[j+g]; v[j+g] = temp; /* В качестве упражнения можете написать * при помощи curses-а программу, * визуализирующую процесс сортировки: * например, изображающую эту перестановку * элементов массива */ } } А. Богатырев, 1992-95 - 21 - Си в UNIX /* сортировка строк */ ssort(v) obj **v; { extern less(); /* функция сравнения строк */ int len; /* подсчет числа строк */ len=0; while(v[len]) len++; shsort(v,len,less); } /* Функция сравнения строк. * Вернуть целое меньше нуля, если a < b * ноль, если a == b * больше нуля, если a > b */ less(a,b) obj *a,*b; { return strcoll(a,b); /* strcoll - аналог strcmp, * но с учетом алфавитного порядка букв. */ } char *strings[] = { "Яша", "Федя", "Коля", "Гриша", "Сережа", "Миша", "Андрей Иванович", "Васька", NULL }; int main(){ char **next; setlocale(LC_ALL, ""); ssort( strings ); /* распечатка */ for( next = strings ; *next ; next++ ) printf( "%s\n", *next ); return 0; } 1.50. Реализуйте алгоритм быстрой сортировки. А. Богатырев, 1992-95 - 22 - Си в UNIX /* Алгоритм быстрой сортировки. Работа алгоритма "анимируется" * (animate-оживлять) при помощи библиотеки curses. * cc -o qsort qsort.c -lcurses -ltermcap */ #include "curses.h" #define N 10 /* длина массива */ /* массив, подлежащий сортировке */ int target [N] = { 7, 6, 10, 4, 2, 9, 3, 8, 5, 1 }; int maxim; /* максимальный элемент массива */ /* quick sort */ qsort (a, from, to) int a[]; /* сортируемый массив */ int from; /* левый начальный индекс */ int to; /* правый конечный индекс */ { register i, j, x, tmp; if( from >= to ) return; /* число элементов <= 1 */ i = from; j = to; x = a[ (i+j) / 2 ]; /* значение из середины */ do{ /* сужение вправо */ while( a[i] < x ) i++ ; /* сужение влево */ while( x < a[j] ) j--; if( i <= j ){ /* обменять */ tmp = a[i]; a[i] = a[j] ; a[j] = tmp; i++; j--; demochanges(); /* визуализация */ } } while( i <= j ); /* Теперь обе части сошлись в одной точке. * Длина левой части = j - from + 1 * правой = to - i + 1 * Все числа в левой части меньше всех чисел в правой. * Теперь надо просто отсортировать каждую часть в отдельности. * Сначала сортируем более короткую (для экономии памяти * в стеке ). Рекурсия: */ if( (j - from) < (to - i) ){ qsort( a, from, j ); qsort( a, i, to ); } else { qsort( a, i, to ); qsort( a, from, j ); } } А. Богатырев, 1992-95 - 23 - Си в UNIX int main (){ register i; initscr(); /* запуск curses-а */ /* поиск максимального числа в массиве */ for( maxim = target[0], i = 1 ; i < N ; i++ ) if( target[i] > maxim ) maxim = target[i]; demochanges(); qsort( target, 0, N-1 ); demochanges(); mvcur( -1, -1, LINES-1, 0); /* курсор в левый нижний угол */ endwin(); /* завершить работу с curses-ом */ return 0; } #define GAPY 2 #define GAPX 20 /* нарисовать картинку */ demochanges(){ register i, j; int h = LINES - 3 * GAPY - N; int height; erase(); /* зачистить окно */ attron( A_REVERSE ); /* рисуем матрицу упорядоченности */ for( i=0 ; i < N ; i++ ) for( j = 0; j < N ; j++ ){ move( GAPY + i , GAPX + j * 2 ); addch( target[i] >= target[j] ? '*' : '.' ); addch( ' ' ); /* Рисовать '*' если элементы * идут в неправильном порядке. * Возможен вариант проверки target[i] > target[j] */ } attroff( A_REVERSE ); /* массив */ for( i = 0 ; i < N ; i++ ){ move( GAPY + i , 5 ); printw( "%4d", target[i] ); height = (long) h * target[i] / maxim ; for( j = 2 * GAPY + N + (h - height) ; j < LINES - GAPY; j++ ){ move( j, GAPX + i * 2 ); addch( '|' ); } } refresh(); /* проявить картинку */ sleep(1); } А. Богатырев, 1992-95 - 24 - Си в UNIX 1.51. Реализуйте приведенный фрагмент программы без использования оператора goto и без меток. if ( i > 10 ) goto M1; goto M2; M1: j = j + i; flag = 2; goto M3; M2: j = j - i; flag = 1; M3: ; Заметьте, что помечать можно только оператор (может быть пустой); поэтому не может встретиться фрагмент { ..... Label: } а только { ..... Label: ; } 1.52. В каком случае оправдано использование оператора goto? Ответ: при выходе из вложенных циклов, т.к. оператор break позволяет выйти только из самого внутреннего цикла (на один уровень). 1.53. К какому if-у относится else? if(...) ... if(...) ... else ... Ответ: ко второму (к ближайшему предшествующему, для которого нет другого else). Вообще же лучше явно расставлять скобки (для ясности): if(...){ ... if(...) ... else ... } if(...){ ... if(...) ... } else ... 1.54. Макроопределение, чье тело представляет собой последовательность операторов в {...} скобках (блок), может вызвать проблемы при использовании его в условном опера- торе if с else-частью: #define MACRO { x=1; y=2; } if(z) MACRO; else .......; Мы получим после макрорасширения if(z) { x=1; y=2; } /* конец if-а */ ; else .......; /* else ни к чему не относится */ то есть синтаксически ошибочный фрагмент, так как должно быть либо if(...) один_оператор; else ..... либо if(...){ последовательность; ...; операторов; } else ..... где точка-с-запятой после } не нужна. С этим явлением борются, оформляя блок {...} в виде do{...}while(0) #define MACRO do{ x=1; y=2; }while(0) Тело такого "цикла" выполняется единственный раз, при этом мы получаем правильный текст: А. Богатырев, 1992-95 - 25 - Си в UNIX if(z) do{ x=1; y=2; }while(0); else .......; 1.55. В чем ошибка (для знающих язык "Паскаль")? int x = 12; if( x < 20 and x > 10 ) printf( "O'K\n"); else if( x > 100 or x < 0 ) printf( "Bad x\n"); else printf( "x=%d\n", x); Напишите #define and && #define or || 1.56. Почему программа зацикливается? Мы хотим подсчитать число пробелов и табуля- ций в начале строки: int i = 0; char *s = " 3 spaces"; while(*s == ' ' || *s++ == '\t') printf( "Пробел %d\n", ++i); Ответ: логические операции || и && выполняются слева направо; как только какое-то

не


вычисляются


условия не выполняется! Мы должны были написать хотя бы так: while(*s == ' ' || *s == '\t'){ printf( "Пробел %d\n", ++i); s++; } С другой стороны, это свойство || и && черезвычайно полезно, например: if( x != 0.0 && y/x < 1.0 ) ... ; Если бы мы не вставили проверку на 0, мы могли бы получить деление на 0. В данном же

x


a


i


i


т.е. попытки прочесть элемент не входящий в массив не произойдет. Это свойство && позволяет писать довольно неочевидные конструкции, вроде if((cond) && f()); что оказывается эквивалентным if( cond ) f(); Вообще же if(C1 && C2 && C3) DO; эквивалентно if(C1) if(C2) if(C3) DO; и для "или" А. Богатырев, 1992-95 - 26 - Си в UNIX if(C1 || C2 || C3) DO; эквивалентно if(C1) goto ok; else if(C2) goto ok; else if(C3){ ok: DO; } Вот еще пример, пользующийся этим свойством ||

stdio


argc


fp


argc


stderr


exit(1); /* завершить программу */ } ... }

argc


argv


Ниже приведен еще один содержательный пример, представляющий собой одну из воз- можных схем написания "двуязычных" программ, т.е. выдающих сообщения на одном из двух языков по вашему желанию. Проверяется переменная окружения MSG (или LANG): ЯЗЫК: 1) "MSG=engl" английский 2) MSG нет в окружении английский 3) "MSG=rus" русский Про окружение и функцию getenv() смотри в главе "Взаимодействие с UNIX", про strchr() - в главе "Массивы и строки".

stdio


int _ediag = 0; /* язык диагностик: 1-русский */ extern char *getenv(), *strchr(); #define ediag(e,r) (_ediag?(r):(e)) main(){ char *s;

MSG


rRрР


d


}

s


(ее первый фргумент не должен быть NULL-ом). Здесь ее можно было бы упрощенно заме-

s


ние по указателю NULL дает непредсказуемые результаты и, скорее всего, вызовет крах программы).

правила де


Моргана


a && b = ! ( !a || !b ) a || b = ! ( !a && !b ) а также учитывая, что ! !a = a ! (a == b) = (a != b) Например: А. Богатырев, 1992-95 - 27 - Си в UNIX if( c != 'a' && c != 'b' && c != 'c' )...; превращается в if( !(c == 'a' || c == 'b' || c == 'c')) ...;

выражений


значение выражения присваивается некоторой переменной, но это не необходимо. Поэтому можно использовать свойства вычисления && и || в выражениях (хотя это не есть самый понятный способ написания программ, скорее некоторый род извращения). Ограничение тут таково: все части выражения должны возвращать значения. #include <stdio.h> extern int errno; /* код системной ошибки */ FILE *fp; int openFile(){ errno = 0; fp = fopen("/etc/inittab", "r"); printf("fp=%x\n", fp); return(fp == NULL ? 0 : 1); } int closeFile(){ printf("closeFile\n"); if(fp) fclose(fp); return 0; } int die(int code){ printf("exit(%d)\n", code); exit(code); return 0; } void main(){ char buf[2048]; if( !openFile()) die(errno); closeFile(); openFile() || die(errno); closeFile(); /* если файл открылся, то die() не вычисляется */ openFile() ? 0 : die(errno); closeFile(); if(openFile()) closeFile(); openFile() && closeFile();

только


openFile() && (printf("%s", fgets(buf, sizeof buf, fp)), closeFile()); } В последней строке использован оператор "запятая": (a,b,c) возвращает значение выра-

c


1.59. Напишите функцию, вычисляющую сумму массива заданных чисел. 1.60. Напишите функцию, вычисляющую среднее значение массива заданных чисел. 1.61. Что будет напечатано в результате работы следующего цикла? for ( i = 36; i > 0; i /= 2 ) printf ( "%d%s", i, i==1 ? ".\n":", "); А. Богатырев, 1992-95 - 28 - Си в UNIX Ответ: 36, 18, 9, 4, 2, 1. 1.62. Найдите ошибки в следующей программе: main { int i, j, k(10); for ( i = 0, i <= 10, i++ ){ k[i] = 2 * i + 3; for ( j = 0, j <= i, j++ ) printf ("%i\n", k[j]); } } Обратите внимание на формат %i, существует ли такой формат? Есть ли это тот формат, по которому следует печатать значения типа int? 1.63. Напишите программу, которая распечатывает элементы массива. Напишите прог- рамму, которая распечатывает элементы массива по 5 чисел в строке. 1.64. Составьте программу считывания строк символов из стандартного ввода и печати номера введенной строки, адреса строки в памяти ЭВМ, значения строки, длины строки. 1.65. Стилистическое замечание: в операторе return возвращаемое выражение не обяза-

оператор


выражение


выражение


функцию


лых скобках: exit(1); но не exit 1; 1.66. Избегайте ситуации, когда функция в разных ветвях вычисления то возвращает некоторое значение, то не возвращает ничего:

x


x


x


/* а здесь - неявный return; без значения */ }

x


такие ситуации и выдают предупреждение. 1.67. Напишите программу, запрашивающую ваше имя и "приветствующую" вас. Напишите функцию чтения строки. Используйте getchar() и printf(). Ответ:

stdio


main(){

buffer


Введите ваше имя


i


Здравствуй


Введите ваше имя


} }

s


s


maxlen


А. Богатырев, 1992-95 - 29 - Си в UNIX

maxlen


c


i


maxlen


i


c


s


/* обратите внимание, что сам символ '\n' * в строку не попадет */

s


i


/* вернем длину строки */ } Вот еще один вариант функции чтения строки: в нашем примере ее следует вызывать как

buffer


Это подправленный вариант стандартной функции fgets (в ней строки @1 и @2 обменяны местами).

s


c


maxlen


c


cs


}

c


s


cs


}

max-


len


buffer


1.68. Объясните, почему d стало отрицательным и почему %X печатает больше F, чем в исходном числе? Пример выполнялся на 32-х битной машине. main(){ unsigned short u = 65535; /* 16 бит: 0xFFFF */ short d = u; /* 15 бит + знаковый бит */ printf( "%X %d\n", d, d); /* FFFFFFFF -1 */ } Указание: рассмотрите двоичное представление чисел (смотри приложение). Какие приве- дения типов здесь происходят? 1.69. Почему 128 превратилось в отрицательное число? main() { /*signed*/ char c = 128; /* биты: 10000000 */ unsigned char uc = 128; int d = c; /* используется 32-х битный int */ printf( "%d %d %x\n", c, d, d ); /* -128 -128 ffffff80 */ d = uc; printf( "%d %d %x\n", uc, d, d ); /* 128 128 80 */ } А. Богатырев, 1992-95 - 30 - Си в UNIX Ответ: при приведении char к int расширился знаковый бит (7-ой), заняв всю старшую часть слова. Знаковый бит int-а стал равен 1, что является признаком отрицательного

c


щими бит 0200). При приведении unsigned char к int знаковый бит не расширяется. Можно было поступить еще и так:

c


c


char ВСЕГДА приводится к типу int), затем &0377 занулит старший байт полученного целого числа (состоящий из битов 1), снова превратив число в положительное. 1.70. Почему printf("%d\n", '\377' == 0377 ); printf("%d\n", '\xFF' == 0xFF ); печатает 0 (ложь)? Ответ: по той же причине, по которой printf("%d %d\n", '\377', 0377); печатает -1 255, а именно: char '\377' приводится в выражениях к целому расширением знакового бита (а 0377 - уже целое). 1.71. Рассмотрим программу #include <stdio.h> int main(int ac, char **av){ int c; while((c = getchar()) != EOF) switch(c){ case 'ы': printf("Буква ы\n"); break; case 'й': printf("Буква й\n"); break; default: printf("Буква с кодом %d\n", c); break; } return 0; } Она работает так: % a.out йфыв Буква с кодом 202 Буква с кодом 198 Буква с кодом 217 Буква с кодом 215 Буква с кодом 10 ^D %

ы


Ответ: русские буквы имеют восьмой бит (левый) равный 1. В case такой байт при-

отрицательное


число


void main(void){ int c = 'й'; printf("%d\n", c); } печатает -54 А. Богатырев, 1992-95 - 31 - Си в UNIX Решением служит подавление расширения знакового бита: #include <stdio.h> /* Одно из двух */ #define U(c) ((c) & 0xFF) #define UC(c) ((unsigned char) (c)) int main(int ac, char **av){ int c; while((c = getchar()) != EOF) switch(c){ case U('ы'): printf("Буква ы\n"); break; case UC('й'): printf("Буква й\n"); break; default: printf("Буква с кодом %d\n", c); break; } return 0; } Она работает правильно: % a.out йфыв Буква й Буква с кодом 198 Буква ы Буква с кодом 215 Буква с кодом 10 ^D % Возможно также использование кодов букв:

0312


но это гораздо менее наглядно. Подавление знакового бита необходимо также и в опера- торах if: int c; ... if(c == 'й') ... следует заменить на if(c == UC('й')) ... Слева здесь - signed int, правую часть компилятор тоже приводит к signed int. Прихо- дится явно говорить, что справа - unsigned. 1.72. Рассмотрим программу, которая должна напечатать числа от 0 до 255. Для этих чисел в качестве счетчика достаточен один байт: int main(int ac, char *av[]){ unsigned char ch; for(ch=0; ch < 256; ch++) printf("%d\n", ch); return 0; }

ch


ch


А. Богатырев, 1992-95 - 32 - Си в UNIX вычисления ведутся по модулю 256 (2 в 8 степени). То есть в данном случае 255+1=0 Решений существует два: первое - превратить unsigned char в int. Второе - вста- вить явную проверку на последнее значение диапазона. int main(int ac, char *av[]){ unsigned char ch; for(ch=0; ; ch++){ printf("%d\n", ch); if(ch == 255) break; } return 0; } 1.73. Подумайте, почему для

a


a


(первое - более корректно). Намек в виде примера (он выполнялся на 32-битной машине): a = 1; b = 3; c = 2; printf( "%u\n", a - b ); /* 4294967294, хотя в нормальной арифметике 1 - 3 = -2 */ printf( "%d\n", a < b + c ); /* 1 */ printf( "%d\n", a - b < c ); /* 0 */ Могут ли unsigned числа быть отрицательными? 1.74. Дан текст: short x = 40000; printf("%d\n", x); Печатается -25536. Объясните эффект. Указание: каково наибольшее представимое корот- кое целое (16 битное)? Что на самом деле оказалось в x? (лишние слева биты - обруба- ются). 1.75. Почему в примере double x = 5 / 2; printf( "%g\n", x ); значение x равно 2 а не 2.5 ? Ответ: производится целочисленное деление, затем в присваивании целое число 2 приводится к типу double. Чтобы получился ответ 2.5, надо писать одним из следующих способов: double x = 5.0 / 2; x = 5 / 2.0; x = (double) 5 / 2; x = 5 / (double) 2; x = 5.0 / 2.0; то есть в выражении должен быть хоть один операнд типа double. Объясните, почему следующие три оператора выдают такие значения: А. Богатырев, 1992-95 - 33 - Си в UNIX double g = 9.0; int t = 3; double dist = g * t * t / 2; /* 40.5 */ dist = g * (t * t / 2); /* 36.0 */ dist = g * (t * t / 2.0); /* 40.5 */ В каких случаях деление целочисленное, в каких - вещественное? Почему? 1.76. Странслируйте пример на машине с длиной слова int равной 16 бит: long n = 1024 * 1024; long nn = 512 * 512; printf( "%ld %ld\n", n, nn ); Почему печатается 0 0 а не 1048576 262144?

целое


велико для сохранения в 16 битах, поэтому старшие биты обрубаются. Получается 0. Затем в присваивании это уже обрубленное значение приводится к типу long (32 бита) - это все равно будет 0. Чтобы получить корректный результат, надо чтобы выражение справа от = уже имело тип long и сразу сохранялось в 32 битах. Для этого оно должно иметь хоть один операнд типа long: long n = (long) 1024 * 1024; long nn = 512 * 512L; 1.77. Найдите ошибку в операторе:

x


Ответ: между `-' и `=' не должно быть пробела. Операция вида

x


означает

x


x


венный раз (т.е. такая форма не только короче и понятнее, но и экономичнее). Однако имеется тонкое отличие a=a+n от a+=n; оно заключается в том, сколько раз

a


А. Богатырев, 1992-95 - 34 - Си в UNIX #include <stdio.h> static int x = 0; int *iaddr(char *msg){ printf("iaddr(%s) for x=%d evaluated\n", msg, x); return &x; } int main(){ static int a[4]; int *p, i; printf( "1: "); x = 0; (*iaddr("a"))++; printf( "2: "); x = 0; *iaddr("b") += 1; printf( "3: "); x = 0; *iaddr("c") = *iaddr("d") + 1; for(i=0, p = a; i < sizeof(a)/sizeof(*a); i++) a[i] = 0; *p++ += 1; for(i=0; i < sizeof(a)/sizeof(*a); i++) printf("a[%d]=%d ", i, a[i]); printf("offset=%d\n", p - a); for(i=0, p = a; i < sizeof(a)/sizeof(*a); i++) a[i] = 0; *p++ = *p++ + 1; for(i=0; i < sizeof(a)/sizeof(*a); i++) printf("a[%d]=%d ", i, a[i]); printf("offset=%d\n", p - a); return 0; } Выдача: 1: iaddr(a) for x=0 evaluated 2: iaddr(b) for x=0 evaluated 3: iaddr(d) for x=0 evaluated iaddr(c) for x=0 evaluated a[0]=1 a[1]=0 a[2]=0 a[3]=0 offset=1 a[0]=1 a[1]=0 a[2]=0 a[3]=0 offset=2 Заметьте также, что a[i++] += z; это a[i] = a[i] + z; i++; а вовсе не a[i++] = a[i++] + z;

y


y


y


y


или

y


tmp


А. Богатырев, 1992-95 - 35 - Си в UNIX значение последнего выражения из перечисленных (подробнее см. ниже).

x


y = ++x + ++x + ++x;

i


x = --i + --i + --i;

x


y = x++ + x++ + x++;

i


y = i-- + i-- + i--; 1.82. Корректны ли операторы

Jabberwocky


int i = 0;

или


или


или даже


Ответ: нет, стандарт не предусматривает, какая из частей присваивания вычисляется первой: левая или правая. Поэтому все может работать так, как мы и подразумевали, но

i


есть int i = 0; s[i] = i++; это s[0] = 0; или же s[1] = 0; ?

p


эта идея драматизирована в s[i++] = p[i++]; Заметим еще, что в int i=0, j=0; s[i++] = p[j++]; такой проблемы не возникает, поскольку индексы обоих в частях присваивания незави- симы. Зато аналогичная проблема встает в if( a[i++] < b[i] )...; Порядок вычисления операндов не определен, поэтому неясно, что будет сделано прежде:

b


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

или


i++; ++p; А. Богатырев, 1992-95 - 36 - Си в UNIX

i


i


i


if( a[i] < a[i+1] ) ... ; /* верно */ if( a[i] < a[++i] ) ... ; /* неверно */ 1.83. Порядок вычисления операндов в бинарных выражениях не определен (что раньше вычисляется - левый операнд или же правый ?). Так пример int f(x,s) int x; char *s; { printf( "%s:%d ", s, x ); return x; } main(){ int x = 1; int y = f(x++, "f1") + f(x+=2, "f2"); printf("%d\n", y); } может печатать либо f1:1 f2:4 5 либо f2:3 f1:3 6 в зависимости от особенностей поведения вашего компилятора (какая из двух f() выпол- нится первой: левая или правая?). Еще пример: int y = 2; int x = ((y = 4) * y ); printf( "%d\n", x ); Может быть напечатано либо 16, либо 8 в зависимости от поведения компилятора, т.е. данный оператор немобилен. Следует написать y = 4; x = y * y; 1.84. Законен ли оператор f(x++, x++); или f(x, x++); Ответ: Нет, порядок вычисления аргументов функций не определен. По той же причине мы не можем писать f( c = getchar(), c ); а должны писать c = getchar(); f(c, c); (если мы именно это имели в виду). Вот еще пример: ... case '+': push(pop()+pop()); break; case '-': push(pop()-pop()); break; ... А. Богатырев, 1992-95 - 37 - Си в UNIX следует заменить на ... case '+': push(pop()+pop()); break; case '-': { int x = pop(); int y = pop(); push(y - x); break; } ... И еще пример: int x = 0; printf( "%d %d\n", x = 2, x ); /* 2 0 либо 2 2 */ Нельзя также struct pnt{ int x; int y; }arr[20]; int i=0; ... scanf( "%d%d", & arr[i].x, & arr[i++].y );

i


main(){ int i = 3; printf( "%d %d %d\n", i += 7, i++, i++ ); } который показывает, что на IBM PC |- и PDP-11 |= аргументы функций вычисляются справа налево (пример печатает 12 4 3). Впрочем, другие компиляторы могут вычислять их слева направо (как и подсказывает нам здравый смысл). 1.85. Программа печатает либо x=1 либо x=0 в зависимости от КОМПИЛЯТОРА - вычисля- ется ли раньше правая или левая часть оператора вычитания: #include <stdio.h> void main(){ int c = 1; int x = c - c++; printf( "x=%d c=%d\n", x, c ); exit(0); } Что вы имели в виду ? left = c; right = c++; x = left - right; или right = c++; left = c; x = left - right; А если компилятор еще и распараллелит вычисление left и right - то одна программа в разные моменты времени сможет давать разные результаты. ____________________ |- IBM ("Ай-би-эм") - International Buisiness Machines Corporation. Персональные компьютеры IBM PC построены на базе микропроцессоров фирмы Intel. |= PDP-11 - (Programmed Data Processor) - компьютер фирмы DEC (Digital Equipment Corporation), у нас известный как СМ-1420. Эта же фирма выпускает машину VAX. А. Богатырев, 1992-95 - 38 - Си в UNIX Вот еще достойная задачка: x = c-- - --c; /* c-----c */ 1.86. Напишите программу, которая устанавливает в 1 бит 3 и сбрасывает в 0 бит 6. Биты в слове нумеруются с нуля справа налево. Ответ: int x = 0xF0; x |= (1 << 3); x &= ~(1 << 6); В программах часто используют битовые маски как флаги некоторых параметров (признак - есть или нет). Например: #define A 0x08 /* вход свободен */ #define B 0x40 /* выход свободен */ установка флагов : x |= A|B; сброс флагов : x &= ~(A|B); проверка флага A : if( x & A ) ...; проверка, что оба флага есть: if((x & (A|B)) == (A|B))...; проверка, что обоих нет : if((x & (A|B)) == 0 )...; проверка, что есть хоть один: if( x & (A|B))...; проверка, что есть только A : if((x & (A|B)) == A)...; проверка, в каких флагах различаются x и y : diff = x ^ y; 1.87. В программах иногда требуется использовать "множество": каждый допустимый эле-

номер


вать. Число вхождений не учитывается. Множества принято моделировать при помощи битовых шкал: #define SET(n,a) (a[(n)/BITS] |= (1L <<((n)%BITS))) #define CLR(n,a) (a[(n)/BITS] &= ~(1L <<((n)%BITS))) #define ISSET(n,a) (a[(n)/BITS] & (1L <<((n)%BITS))) #define BITS 8 /* bits per char (битов в байте) */ /* Перечислимый тип */ enum fruit { APPLE, PEAR, ORANGE=113, GRAPES, RAPE=125, CHERRY}; /* шкала: n из интервала 0..(25*BITS)-1 */ static char fr[25]; main(){ SET(GRAPES, fr); /* добавить в множество */ if(ISSET(GRAPES, fr)) printf("here\n"); CLR(GRAPES, fr); /* удалить из множества */ }

N


элементов. Алгоритм будет рекурсивным, например таким: в качестве первого элемента

i


N


i


i


оставшиеся


элементы массива в удобную структуру данных (чтобы постоянно не копировать массив). Можно использовать, например, битовую шкалу уже выбранных элементов. Воспользуемся для этого макросами из предыдущего параграфа: А. Богатырев, 1992-95 - 39 - Си в UNIX

n


extern void *calloc(unsigned nelem, unsigned elsize); /* Динамический выделитель памяти, зачищенной нулями. * Это стандартная библиотечная функция. * Обратная к ней - free(); */ extern void free(char *ptr); static int N, M, number; static char *scale; /* шкала выбранных элементов */ int *res; /* результат */ /* ... текст определений SET, CLR, ISSET, BITS ... */ static void choose(int ind){ if(ind == M){ /* распечатать перестановку */ register i; printf("Расстановка #%04d", ++number); for(i=0; i < M; i++) printf(" %2d", res[i]); putchar('\n'); return; } else /* Выбрать очередной ind-тый элемент перестановки * из числа еще не выбранных элементов. */ for(res[ind] = 0; res[ind] < N; ++res[ind]) if( !ISSET(res[ind], scale)) { /* элемент еще не был выбран */ SET(res[ind], scale); /* выбрать */ choose(ind+1); CLR(res[ind], scale); /* освободить */ } } void arrange(int n, int m){ res = (int *) calloc(m, sizeof(int)); scale = (char *) calloc((n+BITS-1)/BITS, 1); M = m; N = n; number = 0; if( N >= M ) choose(0); free((char *) res); free((char *) scale); } void main(int ac, char **av){ if(ac != 3){ printf("Arg count\n"); exit(1); } arrange(atoi(av[1]), atoi(av[2])); }

n


риал". По определению 0! = 1. Попробуйте переделать эту программу так, чтобы оче- редная перестановка печаталась по запросу:

res


/* печатать варианты, пока они есть */

_


_


_


1.89. Напишите макроопределения циклического сдвига переменной типа unsigned int на

skew


#define BITS 16 /* пусть целое состоит из 16 бит */

x


x


А. Богатырев, 1992-95 - 40 - Си в UNIX Вот как работает ROL(x, 2) при BITS=6 |abcdef| исходно abcdef00 << 2 0000abcdef >> 4 ------ операция | cdefab результат В случае signed int потребуется накладывать маску при сдвиге вправо из-за того, что левые биты при >> не заполняются нулями. Приведем пример для сдвига переменной типа signed char (по умолчанию все char - знаковые) на 1 бит влево: #define CHARBITS 8

x


соответственно для сдвига на 2 бита надо делать & 03 на 3 & 07 на 4 & 017

skew


1.90. Напишите программу, которая инвертирует (т.е. заменяет 1 на 0 и наоборот) N битов, начинающихся с позиции P, оставляя другие биты без изменения. Возможный ответ:

x


mask


x


xnew


Где маска получается так: ~0 = 11111....11111 ~0 << N = 11111....11000 /* N нулей */ ~(~0 << N) = 00000....00111 /* N единиц */ ~(~0 << N) << P = 0...01110...00 /* N единиц на местах P+N-1..P */ 1.91. Операции умножения * и деления / и % обычно достаточно медленны. В критичных по скорости функциях можно предпринять некоторые ручные оптимизации, связанные с представлением чисел в двоичном коде (хороший компилятор делает это сам!) - пользуясь тем, что операции +, &, >> и << гораздо быстрее. Пусть у нас есть

x


n


n


x * (2**n) = x << n x / (2**n) = x >> n x % (2**n) = x - ((x >> n) << n) x % (2**n) = x & (2**n - 1)

n


Например: А. Богатырев, 1992-95 - 41 - Си в UNIX x * 8 = x << 3; x / 8 = x >> 3; /* деление нацело */ x % 8 = x & 7; /* остаток от деления */ x * 80 = x*64 + x*16 = (x << 6) + (x << 4); x * 320 = (x * 80) * 4 = (x * 80) << 2 = (x << 8) + (x << 6); x * 21 = (x << 4) + (x << 2) + x; x & 1 = x % 2 = четное(x)? 0:1 = нечетное(x)? 1:0; x & (-2) = x & 0xFFFE = | если x = 2*k то 2*k | если x = 2*k + 1 то 2*k | то есть округляет до четного Или формула для вычисления количества дней в году (високосный/простой): days_in_year = (year % 4 == 0) ? 366 : 365; заменяем на days_in_year = ((year & 0x03) == 0) ? 366 : 365; Вот еще одно полезное равенство: x = x & (a|~a) = (x & a) | (x & ~a) = (x&a) + (x&~a) из чего вытекает, например x - (x % 2**n) = x - (x & (2**n - 1)) = = x & ~(2**n - 1) = (x>>n) << n x - (x%8) = x-(x&7) = x & ~7 Последняя строка может быть использована в функции untab() в главе "Текстовая обра- ботка". 1.92. Обычно мы вычисляем min(a,b) так: #define min(a, b) (((a) < (b)) ? (a) : (b)) или более развернуто if(a < b) min = a; else min = b; Здесь есть операция сравнения и условный переход. Однако, если (a < b) эквивалентно условию (a - b) < 0, то мы можем избежать сравнения. Это предположение верно при (unsigned int)(a - b) <= 0x7fffffff. что, например, верно если a и b - оба неотрицательные числа между 0 и 0x7fffffff. При этих условиях min(a, b) = b + ((a - b) & ((a - b) >> 31)); Как это работает? Рассмотрим два случая: А. Богатырев, 1992-95 - 42 - Си в UNIX Случай 1: a < b Здесь (a - b) < 0, поэтому старший (левый, знаковый) бит разности (a - b) равен 1. Следовательно, (a - b) >> 31 == 0xffffffff, и мы имеем: min(a, b) = b + ((a - b) & ((a - b) >> 31)) = b + ((a - b) & (0xffffffff)) = b + (a - b) = a что корректно. Случай 2: a >= b Здесь (a - b) >= 0, поэтому старший бит разности (a - b) равен 0. Тогда (a - b) >> 31 == 0, и мы имеем: min(a, b) = b + ((a - b) & ((a - b) >> 31)) = b + ((a - b) & (0x00000000)) = b + (0) = b что также корректно. Статья предоставлена by Jeff Bonwick. 1.93. Есть ли быстрый способ определить, является ли X степенью двойки? Да, есть. int X является степенью двойки тогда и только тогда, когда (X & (X - 1)) == 0 (в частности 2 здесь окажется степенью двойки). Как это работает? Пусть X != 0. Если X - целое, то его двоичное представление таково: X = bbbbbbbbbb10000... где 'bbb' представляет некие биты, '1' - младший бит, и все остальные биты правее - нули. Поэтому: X = bbbbbbbbbb10000... X - 1 = bbbbbbbbbb01111... ------------------------------------ X & (X - 1) = bbbbbbbbbb00000... Другими словами, X & (X-1) имеет эффект обнуления последнего единичного бита. Если X - степень двойки, то он содержит в двоичном представлении ровно ОДИН такой бит, поэ- тому его гашение обращает результат в ноль. Если X - не степень двойки, то в слове есть хотя бы ДВА единичных бита, поэтому X & (X-1) должно содержать хотя бы один из оставшихся единичных битов - то есть не равняться нулю. Следствием этого служит программа, вычисляющая число единичных битов в слове X: int popc; for (popc = 0; X != 0; X &= X - 1) popc++; При этом потребуется не 32 итерации (число бит в int), а ровно столько, сколько еди- ничных битов есть в X. Статья предоставлена by Jeff Bonwick. А. Богатырев, 1992-95 - 43 - Си в UNIX 1.94. Функция для поиска номера позиции старшего единичного бита в слове. Использу- ется бинарный поиск: позиция находится максимум за 5 итераций (двоичный логарифм 32х), вместо 32 при линейном поиске. int highbit (unsigned int x) { int i; int h = 0; for (i = 16; i >= 1; i >>= 1) { if (x >> i) { h += i; x >>= i; } } return (h); } Статья предоставлена by Jeff Bonwick. 1.95. Напишите функцию, округляющую свой аргумент вниз до степени двойки. #include <stdio.h> #define INT short #define INFINITY (-999) /* Функция, выдающая число, являющееся округлением вниз * до степени двойки. * Например: * 0000100010111000110 * заменяется на * 0000100000000000000 * то есть остается только старший бит. * В параметр power2 возвращается номер бита, * то есть показатель степени двойки. Если число == 0, * то эта степень равна минус бесконечности. */ А. Богатырев, 1992-95 - 44 - Си в UNIX unsigned INT round2(unsigned INT x, int *power2){ /* unsigned - чтобы число рассматривалось как * битовая шкала, а сдвиг >> заполнял левые биты * нулем, а не расширял вправо знаковый бит. * Идея функции: сдвигать число >> пока не получится 1 * (можно было бы выбрать 0). * Затем сдвинуть << на столько же разрядов, при этом все правые * разряды заполнятся нулем, что и требовалось. */ int n = 0; if(x == 0){ *power2 = -INFINITY; return 0; } if(x == 1){ *power2 = 0; return 1; } while(x != 1){ x >>= 1; n++; if(x == 0 || x == (unsigned INT)(-1)){ printf("Вижу %x: похоже, что >> расширяет знаковый бит.\n" "Зациклились!!!\n", x); return (-1); } } x <<= n; *power2 = n; return x; } int counter[ sizeof(unsigned INT) * 8]; int main(void){ unsigned INT i; int n2; for(i=0; ; i++){ round2(i, &n2); if(n2 == -INFINITY) continue; counter[n2]++; /* Нельзя писать for(i=0; i < (unsigned INT)(-1); i++) * потому что такой цикл бесконечен! */ if(i == (unsigned INT) (-1)) break; } for(i=0; i < sizeof counter/sizeof counter[0]; i++) printf("counter[%u]=%d\n", i, counter[i]); return 0; } 1.96. Если некоторая вычислительная функция будет вызываться много раз, не следует пренебрегать возможностью построить таблицу решений, где значение вычисляется один раз для каждого входного значения, зато потом берется непосредственно из таблицы и не вычисляется вообще. Пример: подсчет числа единичных бит в байте. Напоминаю: байт состоит из 8 бит. А. Богатырев, 1992-95 - 45 - Си в UNIX #include <stdio.h> int nbits_table[256]; int countBits(unsigned char c){ int nbits = 0; int bit; for(bit = 0; bit < 8; bit++){ if(c & (1 << bit)) nbits++; } return nbits; } void generateTable(){ int c; for(c=0; c < 256; c++){ nbits_table[ (unsigned char) c ] = countBits(c); /* printf("%u=%d\n", c, nbits_table[ c & 0377 ]); */ } } int main(void){ int c; unsigned long bits = 0L; unsigned long bytes = 0L; generateTable(); while((c = getchar()) != EOF){ bytes++; bits += nbits_table[ (unsigned char) c ]; } printf("%lu байт\n", bytes); printf("%lu единичных бит\n", bits); printf("%lu нулевых бит\n", bytes*8 - bits); return 0; } 1.97. Напишите макрос swap(x, y), обменивающий значениями два своих аргумента типа int. #define swap(x,y) {int tmp=(x);(x)=(y);(y)=tmp;} ... swap(A, B); ... Как можно обойтись без временной переменной? Ввиду некоторой курьезности последнего способа, приводим ответ: int x, y; /* A B */ x = x ^ y; /* A^B B */ y = x ^ y; /* A^B A */ x = x ^ y; /* B A */ Здесь используется тот факт, что A^A дает 0.

функцию


макроса ее придется вызывать как А. Богатырев, 1992-95 - 46 - Си в UNIX ... swap(&A, &B); ... Почему? 1.99. Пример объясняет разницу между формальным и фактическим параметром. Термин

формальный


само имя


f(x,y) { return(x + y); } и f(муж,жена) { return(муж + жена); }

Фактический


метру в момент вызова функции: f(xyz, 43+1); В Си это означает, что формальным параметрам (в качестве локальных переменных) прис-

начальные значения


x = xyz; y = 43 + 1; /*в теле ф-ции их можно менять*/ При выходе из функции формальные параметры (и локальные переменные) разопределяются (и даже уничтожаются, см. следующий параграф). Имена формальных параметров могут

override


время выполнения данной функции. Что печатает программа? char str[] = "строка1"; char lin[] = "строка2"; f(str) char str[]; /* формальный параметр. */ { printf( "%s %s\n", str, str ); } main(){ char *s = lin; /* фактический параметр: */ f(str); /* массив str */ f(lin); /* массив lin */ f(s); /* переменная s */ f("строка3"); /* константа */ f(s+2); /* значение выражения */ }

str


РАЗНЫЕ вещи, хотя и называющиеся одинаково. Переименуйте аргумент функции f и пере- пишите ее в виде f(ss) char ss[]; /* формальный параметр. */ { printf( "%s %s\n", ss, str ); } Что печатается теперь? Составьте аналогичный пример с целыми числами. 1.100. Поговорим более подробно про область видимости имен. int x = 12; f(x){ int y = x*x; if(x) f(x - 1); } main(){ int x=173, z=21; f(2); }

стеке


А. Богатырев, 1992-95 - 47 - Си в UNIX уничтожаются при выходе из нее: -+ +- вершина стека |локал y=0 | |аргумент x=0 | f(0) |---------------|--------- "кадр" |локал y=1 | frame |аргумент x=1 | f(1) |---------------|--------- |локал y=4 | |аргумент x=2 | f(2) |---------------|--------- |локал z=21 |

auto


================================== дно стека

static


==================================

локальные переменные


функции, в котором они отведены; но не видимы ни в вызывающих, ни в вызываемых функ-

гло-


бальные переменные


одноименной локальной переменной (или формалом) в данном кадре. Что напечатает программа? Постарайтесь ответить на этот вопрос не выполняя программу на машине! x1 x2 x3 x4 x5 int x = 12; /* x1 */ | . . . . f(){ |___ . . . int x = 8; /* x2, перекрытие */ : | . . . printf( "f: x=%d\n", x ); /* x2 */ : | . . . x++; /* x2 */ : | . . . } :--+ . . . g(x){ /* x3 */ :______ . . printf( "g: x=%d\n", x ); /* x3 */ : | . . x++; /* x3 */ : | . . } :-----+ . . h(){ :_________ . int x = 4; /* x4 */ : | . g(x); /* x4 */ : |___ { int x = 55; } /* x5 */ : : | printf( "h: x=%d\n", x ); /* x4 */ : |--+ } :--------+ main(){ | f(); h(); | printf( "main: x=%d\n", x ); /* x1 */ | } ---- Ответ: f: x=8 g: x=4 h: x=4 main: x=12

копиями


метров (т.е. являются локальными переменными функции, проинициализированными значени- ями фактических параметров), поэтому их изменение не приводит к изменению фактичес- кого параметра. Чтобы изменять фактический параметр, надо передавать его адрес! А. Богатырев, 1992-95 - 48 - Си в UNIX 1.101. Поясним последнюю фразу. (Внимание! Возможно, что данный пункт вам следует читать ПОСЛЕ главы про указатели). Пусть мы хотим написать функцию, которая обмени-

x


x


int msort(x, y) int x, y; { int tmp; if(x > y){ tmp=x; x=y; y=tmp; } return (x+y)/2; } int x=20, y=8; main(){ msort(x,y); printf("%d %d\n", x, y); /* 20 8 */ }

x


копиями


перестановка никак не проявляется! Чтобы мы могли изменить аргументы, копироваться в локальные переменные должны не

значения


int msort(xptr, yptr) int *xptr, *yptr; { int tmp; if(*xptr > *yptr){tmp= *xptr;*xptr= *yptr;*yptr=tmp;} return (*xptr + *yptr)/2; } int x=20, y=8, z; main(){ z = msort(&x,&y); printf("%d %d %d\n", x, y, z); /* 8 20 14 */ }

x


y


x


указания адресов: int x; scanf("%d", &x); /* но не scanf("%d", x); */ Заметим, что адрес от арифметического выражения или от константы (а не от переменной) вычислить нельзя, поэтому законны: int xx=12, *xxptr = &xx, a[2] = { 13, 17 }; int *fy(){ return &y; } msort(&x, &a[0]); msort(a+1, xxptr); msort(fy(), xxptr); но незаконны msort(&(x+1), &y); и msort(&x, &17); Заметим еще, что при работе с адресами мы можем направить указатель в неверное место и получить непредсказуемые результаты: msort(&xx - 20, a+40); (указатели указывают неизвестно на что).

В


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

ИЗ


функции - он должен быть указателем на переменную возвращаемого типа (лучше А. Богатырев, 1992-95 - 49 - Си в UNIX возвращать значение как значение функции - return-ом, но иногда надо возвращать нес- колько значений - и этого главного "окошка" не хватает). Контрольный вопрос: что печатает фрагмент? int a=2, b=13, c; int f(x, y, z) int x, *y, z; { *y += x; x *= *y; z--; return (x + z - a); } main(){ c=f(a, &b, a+4); printf("%d %d %d\n",a,b,c); } (Ответ: 2 15 33) 1.102. Формальные аргументы функции - это такие же локальные переменные. Параметры как бы описаны в самом внешнем блоке функции: char *func1(char *s){ int s; /* ошибка: повторное определение имени s */ ... } int func2(int x, int y){ int z; ... } соответствует int func2(){ int x = безымянный_аргумент_1_со_стека; int y = безымянный_аргумент_2_со_стека; int z; ... } Мораль такова: формальные аргументы можно смело изменять и использовать как локальные переменные. 1.103. Все параметры функции можно разбить на 3 класса: - in - входные; - out - выходные, служащие для возврата значения из функции; либо для изменения данных, находящихся по этому адресу; - in/out - для передачи значения в функцию и из функции. Два последних типа параметров должны быть указателями. Иногда (особенно в прототипах и в документации) бывает полезно указывать класс параметра в виде комментария:

x


yp


zp


yp


zp


}

x


res


res


/* 14 2 8 15 */ } Это полезно потому, что иногда трудно понять - зачем параметр описан как указатель.

выдается


передаваемые в


нами


А. Богатырев, 1992-95 - 50 - Си в UNIX область памяти, в которой будет размещен результат. Пример на эту тему есть в главе

_


1.104. Известен такой стиль оформления аргументов функции:

func


arg2


arg3


time


){ ... } Суть его в том, что запятые пишутся в столбик и в одну линию с ( и ) скобками для аргументов. При таком стиле легче добавлять и удалять аргументы, чем при версии с запятой в конце. Этот же стиль применим, например, к перечислимым типам: enum { red , green , blue }; Напишите программу, форматирующую заголовки функций таким образом. 1.105. В чем ошибка?

x


str


str


str


} void main(){

x


printf("The values:\n");

x


}

автоматическую


str


теперь указывает на испорченные данные! Возможным решением проблемы является превра-

str


str


Однако такой способ не позволит писать конструкции вида printf("%s %s\n", val(1), val(2));

один и тот же


таться "1 1" либо "2 2", но не "1 2". Более правильным будет задание буфера для результата val() как аргумента:

x


str


str


} void main(){

x


s1


x


} А. Богатырев, 1992-95 - 51 - Си в UNIX 1.106. Каковы ошибки (не синтаксические) в программе|-? main() { double y; int x = 12; y = sin (x); printf ("%s\n", y); } Ответ: - стандартная библиотечная функция sin() возвращает значение типа double, но мы нигде не информируем об этом компилятор. Поэтому он считает по умолчанию, что

y


дение типа int к типу левого операнда, т.е. к double. В результате возвращаемое значение (а оно на самом деле - double) интерпретируется неверно (как int), под- вергается приведению типа (которое портит его), и результат получается совер- шенно не таким, как надо. Подобная же ошибка возникает при использовании функ- ций, возвращающих указатель, например, функций malloc() и itoa(). Поэтому если

возвращающей не int


тельно (до первого использования) описать ее, например|=: extern double sin(); extern long atol(); extern char *malloc(), *itoa(); Это же относится и к нашим собственным функциям, которые мы используем прежде, чем определяем (поскольку из заголовка функции компилятор обнаружит, что она

после


/*extern*/ char *f(); main(){ char *s; s = f(1); puts(s); } char *f(n){ return "knights" + n; }

целое


стандартных функций уже помещены в системные include-файлы. Например, описания для математических функций (sin, cos, fabs, ...) содержатся в файле

usr


math


вместо extern double sin(), cos(), fabs(); - библиотечная функция sin() требует аргумента типа double, мы же передаем ей аргумент типа int (который короче типа double и имеет иное внутреннее представ- ление). Он будет неправильно проинтерпретирован функцией, т.е. мы вычислим синус отнюдь НЕ числа 12. Следует писать:

double


и sin(12.0); вместо sin(12); ____________________ |- Для трансляции программы, использующей стандартные математические функции sin, cos, exp, log, sqrt, и.т.п. следует задавать ключ компилятора -lm

file


|= Слово extern ("внешняя") не является обязательным, но является признаком хоро- шего тона - вы сообщаете программисту, читающему эту программу, что данная функция реализована в другом файле, либо вообще является стандартной и берется из библиотеки. А. Богатырев, 1992-95 - 52 - Си в UNIX - в printf мы печатаем значение типа double по неправильному формату: следует использовать формат %g или %f (а для ввода при помощи scanf() - %lf). Очень частой ошибкой является печать значений типа long по формату %d вместо %ld . Первых двух проблем в современном Си удается избежать благодаря заданию прототипов функций (о них подробно рассказано ниже, в конце главы "Текстовая обработка"). Нап- ример, sin имеет прототип

x


Третяя проблема (ошибка в формате) не может быть локализована средствами Си и имеет

streams


1.107. Найдите ошибку:

x


main(){

s


s


} Заметим, что если бы для функции sum() был задан прототип, то компилятор поймал бы

z


мы вызывали

s


то лишние аргументы были бы просто проигнорированы (но и тут может быть сюрприз - аргументы могли бы игнорироваться с ЛЕВОГО конца списка!). А вот пример опасной ошибки, которая не ловится даже прототипами:

x


Второе число по формату %d будет считано неизвестно по какому адресу и разрушит память программы. Ни один компилятор не проверяет соответствие числа %-ов в строке формата числу аргументов scanf и printf. 1.108. Что здесь означают внутренние (,,) в вызове функции f() ? f(x, y, z){ printf("%d %d %d\n", x, y, z); } main(){ int t; f(1, (2, 3, 4), 5); f(1, (t=3,t+1), 5); }

последнего выражения


списка перечисленных через запятую выражений. Здесь будет напечатано 1 4 5. Кажущаяся

аргументы функции


запятую, но это совсем другая синтаксическая конструкция. Вот еще пример: int y = 2, x; x = (y+4, y, y*2); printf("%d\n", x); /* 4 */ x = y+4, y, y*2 ; printf("%d\n", x); /* 6 */ x = (x=y+4, ++y, x*y); printf("%d\n", x); /* 18 */

x


y


объявляемых переменных! Далее следуют три строки выполняемых операторов. В первом случае выполнилось x=y*2; во втором x=y+4 (т.к. приоритет у присваивания выше, чем у А. Богатырев, 1992-95 - 53 - Си в UNIX

без присваивания


иметь эффекта или иметь только побочный эффект) вполне законно: x+y; или z++; или x == y+1; или x; В частности, все вызовы функций-процедур именно таковы (это выражения без оператора присваивания, имеющие побочный эффект): f(12,x); putchar('Ы'); в отличие, скажем, от x=cos(0.5)/3.0; или c=getchar();

выражения


один из перечисленных операторов не выдает значения, то это является ошибкой: main(){ int i, x = 0; for(i=1; i < 4; i++) x++, if(x > 2) x = 2; /* используй { ; } */ } оператор if не выдает значения. Также логически ошибочно использование функции типа void (не возвращающей значения): void f(){} ... for(i=1; i < 4; i++) x++, f(); хотя компилятор может допустить такое использование. Вот еще один пример того, как можно переписать один и тот же фрагмент, применяя разные синтаксические конструкции:

условие


условие


условие


1.109. Найдите опечатку: switch(c){ case 1: x++; break; case 2: y++; break; defalt: z++; break; } Если c=3, то z++ не происходит. Почему? (Потому, что defalt: - это метка, а не клю- чевое слово default). 1.110. Почему программа зацикливается и печатает совсем не то, что нажато на клавиа- туре, а только 0 и 1? while ( c = getchar() != 'e') printf("%d %c\n, c, c); Ответ: данный фрагмент должен был выглядеть так: while ((c = getchar()) != 'e') printf("%d %c\n, c, c); А. Богатырев, 1992-95 - 54 - Си в UNIX Сравнение в Си имеет высший приоритет, нежели присваивание! Мораль: надо быть внима- тельнее к приоритетам операций. Еще один пример на похожую тему: вместо if( x & 01 == 0 ) ... if( c&0377 > 0300)...; надо: if( (x & 01) == 0 ) ... if((c&0377) > 0300)...; И еще пример с аналогичной ошибкой:

fp


fp


stderr


exit(1); }

fp


fp


fprintf() не срабатывает (программа падает по защите памяти|-). Исправьте аналогичную ошибку (на приоритет операций) в следующей функции: /* копирование строки from в to */

to


{

p


to


p


} 1.111. Сравнения с нулем (0, NULL, '\0') в Си принято опускать (хотя это не всегда способствует ясности). if( i == 0 ) ...; --> if( !i ) ... ; if( i != 0 ) ...; --> if( i ) ... ; например, вместо char s[20], *p ; for(p=s; *p != '\0'; p++ ) ... ; будет for(p=s; *p; p++ ) ... ; и вместо char s[81], *gets(); while( gets(s) != NULL ) ... ; будет while( gets(s)) ... ; Перепишите strcpy в этом более лаконичном стиле. ____________________ |- "Падать" - программистский жаргон. Означает "аварийно завершаться". "Защита па- мяти" - обращение по некорректному адресу. В UNIX такая ошибка ловится аппаратно, и программа будет убита одним из сигналов: SIGBUS, SIGSEGV, SIGILL. Система сообщит нечто вроде "ошибка шины". Знайте, что это не ошибка аппаратуры и не сбой, а ВАША ошибка! А. Богатырев, 1992-95 - 55 - Си в UNIX 1.112. Истинно ли выражение if( 2 < 5 < 4 ) Ответ: да! Дело в том, что Си не имеет логического типа, а вместо "истина" и "ложь" использует целые значения "не 0" и "0" (логические операции выдают 1 и 0). Данное выражение в условии if эквивалентно следующему: ((2 < 5) < 4) Значением (2 < 5) будет 1. Значением (1 < 4) будет тоже 1 (истина). Таким образом мы получаем совсем не то, что ожидалось. Поэтому вместо if( a < x < b ) надо писать if( a < x && x < b ) 1.113. Данная программа должна печатать коды вводимых символов. Найдите опечатку; почему цикл сразу завершается? int c; for(;;) { printf("Введите очередной символ:"); c = getchar(); if(c = 'e') { printf("нажато e, конец\n"); break; } printf( "Код %03o\n", c & 0377 ); } Ответ: в if имеется опечатка: использовано `=' вместо `=='.

новое значение


левой части


c = 'e'; if( c ) ... ; и, поскольку 'e'!= 0, то условие оказывается истинным! Это еще и следствие того, что в Си нет специального логического типа (истина/ложь). Будьте внимательны: компилятор не считает ошибкой использование оператора = вместо == внутри условий if и условий

предупреждение


Еще аналогичная ошибка: for( i=0; !(i = 15) ; i++ ) ... ; (цикл не выполняется); или static char s[20] = " abc"; int i=0; while(s[i] = ' ') i++; printf("%s\n", &s[i]); /* должно напечататься abc */ (строка заполняется пробелами и цикл не кончается). То, что оператор присваивания имеет значение, весьма удобно: int x, y, z; это на самом деле x = y = z = 1; x = (y = (z = 1)); А. Богатырев, 1992-95 - 56 - Си в UNIX или|- y=f( x += 2 ); // вместо x+=2; y=f(x); if((y /= 2) > 0)...; // вместо y/=2; if(y>0)...; Вот пример упрощенной игры в "очко" (упрощенной - т.к. не учитывается ограниченность числа карт каждого типа в колоде (по 4 штуки)): #include <stdio.h> main(){ int sum = 0, card; char answer[36]; srand( getpid()); /* рандомизация */ do{ printf( "У вас %d очков. Еще? ", sum); if( *gets(answer) == 'n' ) break; /* иначе маловато будет */ printf( " %d очков\n", card = 6 + rand() % (11 - 6 + 1)); } while((sum += card) < 21); /* SIC ! */ printf ( sum == 21 ? "очко\n" : sum > 21 ? "перебор\n": "%d очков\n", sum); } Вот еще пример, использующийся для подсчета правильного размера таблицы. Обратите внимание, что присваивания используются в сравнениях, в аргументах вызова функции

выражение


#include <stdio.h> int width = 20; /* начальное значение ширины поля */ int len; char str[512]; main(){ while(gets(str)){ if((len = strlen(str)) > width){ fprintf(stderr,"width увеличить до %d\n", width=len); } printf("|%*.*s|\n", -width, width, str); } } Вызывай эту программу как

входнойФайл


1.114. Почему программа "зависает" (на самом деле - зацикливается) ? int x = 0; while( x < 100 ); printf( "%d\n", x++ ); printf( "ВСЕ\n" ); Указание: где кончается цикл while? Мораль: не надо ставить ; где попало. Еще мораль: даже отступы в оформлении программы не являются гарантией отсутствия ошибок в группировке операторов. 1.115. Вообще, приоритеты операций в Си часто не соответствуют ожиданиям нашего здравого смысла. Например, значением выражения: x = 1 << 2 + 1 ; ____________________

текст


тарий в стиле языка C++. Такой комментарий простирается от символа // до конца строки. А. Богатырев, 1992-95 - 57 - Си в UNIX будет 8, а не 5, поскольку сложение выполнится первым. Мораль: в затруднительных и неочевидных случаях лучше явно указывать приоритеты при помощи круглых скобок: x = (1 << 2) + 1 ; Еще пример: увеличивать x на 40, если установлен флаг, иначе на 1: int bigFlag = 1, x = 2; x = x + bigFlag ? 40 : 1; printf( "%d\n", x ); ответом будет 40, а не 42, поскольку это x = (x + bigFlag) ? 40 : 1; а не x = x + (bigFlag ? 40 : 1); которое мы имели в виду. Поэтому вокруг условного выражения ?: обычно пишут круглые скобки. Заметим, что () указывают только приоритет, но не порядок вычислений. Так, ком- пилятор имеет полное право вычислить long a = 50, x; int b = 4; x = (a * 100) / b; /* деление целочисленное с остатком ! */ и как x = (a * 100)/b = 5000/4 = 1250 и как x = (a/b) * 100 = 12*100 = 1200 невзирая на наши скобки, поскольку и * и / имеют одинаковый приоритет (хотя это "право" еще не означает, что он обязательно так поступит). Такие операторы прихо- дится разбивать на два, т.е. вводить промежуточную переменную: { long a100 = a * 100; x = a100 / b; } 1.116. Составьте программу вычисления тригонометрической функции. Название функции

argv


argc


$ a.out sin 0.5 sin(0.5)=0.479426 (здесь и далее значок $ обозначает приглашение, выданное интерпретатором команд). Для преобразования строки в значение типа double воспользуйтесь стандартной функцией atof().

str1


x


y


i


либо

str1


str2


К слову заметим, что обратное преобразование - числа в текст - удобнее всего делается при помощи функции sprintf(), которая аналогична printf(), но сформированная ею строка-сообщение не выдается на экран, а заносится в массив: А. Богатырев, 1992-95 - 58 - Си в UNIX

represent


i


represent


1.117. Составьте программу вычисления полинома n-ой степени: n n-1 Y = A * X + A * X + ... + A0 n n-1 схема (Горнера): Y = A0 + X * ( A1 + X * ( A2 + ... + X * An )))...) Оформите алгоритм как функцию с переменным числом параметров:

x


varargs


varargs


_


_


{

_


double sum = 0.0;

_


while( n-- >= 0 ){ sum *= x;

_


/* извлечь след. аргумент типа double */ }

_


return sum; } main(){ /* y = 12*x*x + 3*x + 7 */ printf( "%g\n", poly(2.0, 2, 12.0, 3.0, 7.0)); } Прототип этой функции:

x


_нечто


_


_


_


_


_


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

TYPE


извлекается из списка при помощи

x


Список аргументов просматривается слева направо в одном направлении, возврат к А. Богатырев, 1992-95 - 59 - Си в UNIX предыдущему аргументу невозможен. Нельзя указывать в качестве типов char, short, float:

ch


поскольку в языке Си аргументы функции таких типов автоматически расширяются в int, int, double соответственно. Корректно будет так:

ch


1.118. Еще об одной ловушке в языке Си на PDP-11 (и в компиляторах бывают ошибки!): unsigned x = 2; printf( "%ld %ld", - (long) x, (long) -x ); Этот фрагмент напечатает числа -2 и 65534. Во втором случае при приведении к типу long был расширен знаковый бит. Встроенная операция sizeof выдает значение типа unsigned. Подумайте, каков будет эффект в следующем фрагменте программы? static struct point{ int x, y ;} p = { 33, 13 }; FILE *fp = fopen( "00", "w" ); /* вперед на длину одной структуры */ fseek( fp, (long) sizeof( struct point ), 0 ); /* назад на длину одной структуры */ /*!*/ fseek( fp, (long) -sizeof( struct point ), 1 ); /* записываем в начало файла одну структуру */ fwrite( &p, sizeof p, 1, fp ); /* закрываем файл */ fclose( fp ); Где должен находиться минус во втором вызове fseek для получения ожидаемого резуль- тата? (Данный пример может вести себя по-разному на разных машинах, вопросы касаются PDP-11). 1.119. Обратимся к указателям на функции: void g(x){ printf("%d: here\n", x); } main(){

f


f


f


/* В обоих случаях вызывается g(x); */ } Что печатает программа? typedef void (*(*FUN))(); /* Попытка изобразить рекурсивный тип typedef FUN (*FUN)(); */ FUN g(FUN f){ return f; } void main(){ FUN y = g(g(g(g(g)))); if(y == g) printf("OK\n"); А. Богатырев, 1992-95 - 60 - Си в UNIX } Что печатает программа? char *f(){ return "Hello, user!"; } g(func) char * (*func)(); { puts((*func)()); } main(){ g(f); } Почему было бы неверно написать main(){ g(f()); } Еще аналогичная ошибка (посмотрите про функцию signal в главе "Взаимодействие с UNIX"):

signal


Good bye


main(){ signal ( SIGINT, f() ); ... } Запомните, что f() - это ЗНАЧЕНИЕ функции f (т.е. она вызывается и нечто возвращает return-ом; это-то значение мы и используем), а f - это АДРЕС функции f (раньше это так и писалось &f), то есть метка начала ее машинных кодов ("точка входа"). 1.120. Что напечатает программа? (Пример посвящен указателям на функции и массивам функций): int f(n){ return n*2; } int g(n){ return n+4; } int h(n){ return n-1; } int (*arr[3])() = { f, g, h }; main(){ int i; for(i=0; i < 3; i++ ) printf( "%d\n", (*arr[i])(i+7) ); } 1.121. Что напечатает программа? extern double sin(), cos(); main(){ double x; /* cc -lm */ for(x=0.0; x < 1.0; x += 0.2) printf("%6.4g %6.4g %6.4g\n", (x > 0.5 ? sin : cos)(x), sin(x), cos(x)); } то же в варианте А. Богатырев, 1992-95 - 61 - Си в UNIX extern double sin(), cos();

f


for(x=0.0; x < 1.0; x += 0.2){

f


f


} }

факториал


n


n


Все они иллюстрируют определенные подходы в программировании: /* ЦИКЛ (ИТЕРАЦИЯ) */ int factorial1(n){ int res = 1; while(n > 0){ res *= n--; } return res; } /* ПРОСТАЯ РЕКУРСИЯ */ int factorial2(n){ return (n==0 ? 1 : n * factorial2(n-1)); } /* Рекурсия, в которой функция вызывается рекурсивно

единственный раз


tail recursion


* легко преобразуется в цикл */ /* АВТОАППЛИКАЦИЯ */ int fi(f, n) int (*f)(), n; { if(n == 0) return 1; else return n * (*f)(f, n-1); } int factorial3(n){ return fi(fi, n); } /* РЕКУРСИЯ С НЕЛОКАЛЬНЫМ ПЕРЕХОДОМ */ #include <setjmp.h> jmp_buf checkpoint; void fact(n, res) register int n, res; { if(n) fact(n - 1, res * n); else longjmp(checkpoint, res+1); } int factorial4(n){ int res; if(res = setjmp(checkpoint)) return (res - 1); else fact(n, 1); } 1.123. Напишите функцию, печатающую целое число в системе счисления с основанием

base


А. Богатырев, 1992-95 - 62 - Си в UNIX printi( n, base ){ register int i; if( n < 0 ){ putchar( '-' ); n = -n; } if( i = n / base ) printi( i, base ); i = n % base ; putchar( i >= 10 ? 'A' + i - 10 : '0' + i ); } Попробуйте написать нерекурсивный вариант с накоплением ответа в строке. Приве- дем рекурсивный вариант, накапливающий ответ в строке s и пользующийся аналогом функ-

нечто


в ней написаны операторы

res


и рекурсивно вызывается конечно же prints. Итак: static char *res; ... текст функции prints ... char *itos( n, base, s ) char *s; /* указывает на char[] массив для ответа */ { res = s; prints(n, base); *res = '\0'; return s; } main(){ char buf[20]; printf( "%s\n", itos(19,2,buf); } 1.124. Напишите функцию для побитной распечатки целого числа. Имейте в виду, что число содержит 8 * sizeof(int) бит. Указание: используйте операции битового сдвига и &. Ответ: printb(n){ register i; for(i = 8 * sizeof(int) - 1; i >= 0; --i) putchar(n & (1 << i) ? '1':'0'); } 1.125. Напишите функцию, склоняющую существительные русского языка в зависимости от их числа. Например: printf( "%d кирпич%s", n, grammar( n, "ей", "", "а" )); Ответ: char *grammar( i, s1, s2, s3 ) char *s1, /* прочее */ *s2, /* один */ *s3; /* два, три, четыре */ { i = i % 100; if( i > 10 && i <= 20 ) return s1; i = i % 10; if( i == 1 ) return s2; if( i == 2 || i == 3 || i == 4 ) return s3; return s1; } А. Богатырев, 1992-95 - 63 - Си в UNIX 1.126. Напишите оператор printf, печатающий числа из интервала 0..99 с добавлением нуля перед числом, если оно меньше 10 : 00 01 ... 09 10 11 ... Используйте условное выражение, формат. Ответ: printf ("%s%d", n < 10 ? "0" : "", n); либо printf ("%02d", n ); либо printf ("%c%c", '0' + n/10, '0' + n%10 ); 1.127. Предостережем от одной ошибки, часто допускаемой начинающими. putchar( "c" ); является ошибкой. putchar( 'c' ); верно. Дело в том, что putchar требует аргумент - символ, тогда как "c" - СТРОКА из одного символа. Большинство компиляторов (те, которые не проверяют прототипы вызова стан- дартных функций) НЕ обнаружит здесь никакой синтаксической ошибки (кстати, ошибка эта - семантическая). Также ошибочны операторы printf ( '\n' ); /* нужна строка */ putchar( "\n" ); /* нужен символ */ putchar( "ab" ); /* нужен символ */ putchar( 'ab' ); /* ошибка в буквенной константе */ char c; if((c = getchar()) == "q" ) ... ; /* нужно писать 'q' */ Отличайте строку из одного символа и символ - это разные вещи! (Подробнее об этом - в следующей главе).

промах на единицу


очень многих и разнообразных случаях. Вот одна из возможных ситуаций: int m[20]; int i = 0; while( scanf( "%d", & m[i++] ) != EOF ); printf( "Ввели %d чисел\n", i );

i


Ответ: аргументы функции вычисляются до ее вызова, поэтому когда мы достигаем

i


сать while( scanf( "%d", & m[i] ) != EOF ) i++; 1.129. Замечание по стилистике: при выводе сообщения на экран printf( "Hello \n" ); пробелы перед \n достаточно бессмысленны, поскольку на экране никак не отобразятся. Надо писать (экономя память) printf( "Hello\n" ); А. Богатырев, 1992-95 - 64 - Си в UNIX Единственный случай, когда такие пробелы значимы - это когда вы выводите текст инвер- сией. Тогда пробелы отображаются как светлый фон. Еще неприятнее будет printf( "Hello\n " );

начале


1.130. printf - интерпретирующая функция, т.е. работает она довольно медленно. Поэ- тому вместо

s


...

s


надо всегда писать

s


поскольку printf в конце-концов (сделав все преобразования по формату) внутри себя вызывает putchar. Так сделаем же это сразу! 1.131. То, что параметр "формат" в функции printf может быть выражением, позволяет делать некоторые удобные вещи. Например:

x


x


x


x


x


ример

x


x


x


нельзя печатать по формату %f ни в каких случаях. Единственным способом сделать это

x


x


Будет ли законен оператор?

x


x


Ответ: нет. Условное выражение для аргумента будет иметь "старший" тип - double. А значение типа double нельзя печатать по формату %d. Мы должны использовать здесь оператор if:

x


x


1.132. Напишите функцию, печатающую размер файла в удобном виде: если файл меньше одного килобайта - печатать его размер в байтах, если же больше - в килобайтах (и мегабайтах). #define KBYTE 1024L /* килобайт */ #define THOUSAND 1024L /* кб. в мегабайте */ А. Богатырев, 1992-95 - 65 - Си в UNIX void tellsize(unsigned long sz){ if(sz < KBYTE) printf("%lu байт", sz); else{ unsigned long Kb = sz/KBYTE; unsigned long Mb = Kb/THOUSAND; unsigned long Dec = ((sz % KBYTE) * 10) / KBYTE; if( Mb ){ Kb %= THOUSAND; printf( Dec ? "%lu.%03lu.%01lu Мб." : "%lu.%lu Мб.", Mb, Kb, Dec ); } else printf( Dec ? "%lu.%01lu Кб.":"%lu Кб.", Kb, Dec); } putchar('\n'); } 1.133. Для печати строк используйте

string


string


Если мы используем вариант B, а в строке встретится символ '%'

string


формат


не будет напечатана; во-вторых - что же будет печататься по этому формату, когда у

единственный


1.134. Почему оператор

s


s


в ответ на ввод строки Пушкин А.С. печатает только "Пушкин"? Ответ: потому, что концом текста при вводе по формату %s считается либо \n, либо

слово


Чтение всех символов до конца строки, (включая пробелы) должно выглядеть так:

s


%[^\n] - читать любые символы, кроме \n (до \n) \n - пропустить \n на конце строки %[abcdef] - читать слово, состоящее из перечисленных букв. %[^abcde] - читать слово из любых букв, кроме перечисленных (прерваться по букве из списка). Пусть теперь строки входной информации имеют формат: Фрейд Зигмунд 1856 1939

s


- игнорировать. Как это сделать? Нам поможет формат "подавление присваивания" %*: scanf("%s%*s%d%*[^\n]\n",

s


А. Богатырев, 1992-95 - 66 - Си в UNIX

пропускает


переменную, а просто "забывая" его. Так формат "%*[^\n]\n" игнорирует "хвост" строки, включая символ перевода строки. Символы " ", "\t", "\n" в формате вызывают пропуск всех пробелов, табуляций, переводов строк во входном потоке, что можно описать как

c


c


либо как формат %*[ \t\n] Перед числовыми форматами (%d, %o, %u, %ld, %x, %e, %f), а также %s, пропуск пробелов делается автоматически. Поэтому

x


и

x


равноправны (пробел перед вторым %d просто не нужен). Неявный пропуск пробелов не делается перед %c и %[... , поэтому в ответ на ввод строки "12 5 x" пример main(){ int n, m; char c; scanf("%d%d%c", &n, &m, &c); printf("n=%d m=%d c='%c'\n", n, m, c); }

c


а не x. Автоматический пропуск пробелов перед %s не позволяет считывать по %s строки,

лидирующие


вались, следует использовать формат

s


в котором модификатор длины 1 заставляет игнорировать только один символ \n, а не ВСЕ пробелы и переводы строк, как "\n". К сожалению (как показал эксперимент) этот формат не в состоянии прочесть пустую строку (состоящую только из \n). Поэтому можно сделать глобальный вывод: строки надо считывать при помощи функций gets() и fgets()! 1.135. Еще пара слов про scanf: scanf возвращает число успешно прочитанных им данных (обработанных %-ов) или EOF в конце файла. Неудача может наступить, если данное во входном потоке не соответствует формату, например строка

12 quack


для

d1


d1


чает формату %lf, поэтому scanf прервет свою работу и выдаст значение 1 (успешно про-

quack


f


1.136. Си имеет квалификатор const, указывающий, что значение является не перемен- ной, а константой, и попытка изменить величину по этому имени является ошибкой. Во многих случаях const может заменить #define, при этом еще явно указан тип константы, что полезно для проверок компилятором. А. Богатырев, 1992-95 - 67 - Си в UNIX const int x = 22; x = 33; /* ошибка: константу нельзя менять */ Использование const с указателем:

Указуемый объект


const char *pc = "abc"; pc[1] = 'x'; /* ошибка */ pc = "123"; /* OK */

Сам указатель


char *const cp = "abc"; cp[1] = 'x'; /* OK */ cp = "123"; /* ошибка */

Указуемый объект и сам указатель


const char *const cpc = "abc"; cpc[1] = 'x'; /* ошибка */ cpc = "123"; /* ошибка */

Указатель на константу


int a = 1; const int b = 2; const int *pca = &a; /* OK, просто рассматриваем a как константу */ const int *pcb = &b; /* OK */ int *pb = &b; /* ошибка, так как тогда возможно было бы написать */ *pb = 3; /* изменить константу b */

quick sort


такой формат: чтобы отсортировать массив элементов типа TYPE

arr


надо вызывать

arr


N


n


arr


/* размер одного элемента массива*/

cmp


где

a1


a1


cmp


по возрастанию


возвращать целое < 0, если *a1 должно идти раньше *a2 < = 0, если *a1 совпадает с *a2 == > 0, если *a1 должно идти после *a2 >

строк


имеют тип (char **). Требуемому условию удовлетворяет такая функция: А. Богатырев, 1992-95 - 68 - Си в UNIX char *arr[N]; ... cmps(s1, s2) char **s1, **s2; { return strcmp(*s1, *s2); } (Про strcmp смотри раздел "Массивы и строки"). Заметим, что в некоторых системах программирования (например в TurboC++ |-) вы должны использовать функцию сравнения с прототипом

a1


и внутри нее явно делать приведение типа: cmps (const void *s1, const void *s2) { return strcmp(*(char **)s1, *(char **)s2); } или можно поступить следующим образом: int cmps(char **s1, char **s2){ return strcmp(*s1, *s2); }

CMPS


CMPS


Наконец, возможно и просто объявить int cmps(const void *A, const void *B){ return strcmp(A, B); }

целых


int arr[N]; ... cmpi(i1, i2) int *i1, *i2; { return *i1 - *i2; }

структур


struct XXX{ int key; ... } arr[N]; cmpXXX(st1, st2) struct XXX *st1, *st2; { return( st1->key - st2->key ); }

long


long arr[N]; ... cmpl(L1, L2) long *L1, *L2; { return *L1 - *L2; } Ответ: оказывается, что нет. Функция cmpl должна возвращать целое, а разность двух long-ов имеет тип long. Поэтому компилятор приводит эту разность к типу int (как

обрубанием


может изменить знак! Например: main(){ int n; long a = 1L; long b = 777777777L; n = a - b; /* должно бы быть отрицательным... */ printf( "%ld %ld %d\n", a, b, n ); } ____________________ |- TurboC - компилятор Си в MS DOS, разработанный фирмой Borland International. А. Богатырев, 1992-95 - 69 - Си в UNIX печатает 1 777777777 3472. Функция сравнения должна выглядеть так: cmpl(L1, L2) long *L1, *L2; { if( *L1 == *L2 ) return 0; if( *L1 < *L2 ) return (-1); return 1; } или cmpl(L1, L2) long *L1, *L2; { return( *L1 == *L2 ? 0 : *L1 < *L2 ? -1 : 1 ); }

знак


Учтите, что для использования функции сравнения вы должны либо определить функ-

до


int cmp(...){ ... } /* реализация */ ... qsort(..... , cmp); либо предварительно объявить имя функции сравнения, чтобы компилятор понимал, что это именно функция: int cmp(); qsort(..... , cmp); ... int cmp(...){ ... } /* реализация */ 1.138. Пусть у нас есть две программы, пользующиеся одной и той же структурой данных W:

a


-------------------------- ------------------------------ #include <fcntl.h> #include <fcntl.h> struct W{ int x,y; }a; struct W{ int x,y; }b; main(){ int fd; main(){ int fd; a.x = 12; a.y = 77; fd = open("f", O_RDONLY); fd = creat("f", 0644); read(fd, &b, sizeof b); write(fd, &a, sizeof a); close(fd); close(fd); printf("%d %d\n", b.x, b.y); } } Что будет, если мы изменим структуру на struct W { long x,y; }; или struct W { char c; int x,y; };

a


Из наблюдаемого можно сделать вывод, что если две или несколько программ (или частей одной программы), размещенные в разных файлах, используют общие - типы данных (typedef); - структуры и объединения; - константы (определения #define); - прототипы функций;

include


раммы придерживались одних и тех же общих соглашений. Даже если эти соглашения со А. Богатырев, 1992-95 - 70 - Си в UNIX временем изменятся, то они изменятся во всех файлах синхронно и как бы сами собой. В нашем случае исправлять определение структуры придется только в include-файле, а не выискивать все места, где оно написано, ведь при этом немудрено какое-нибудь место и пропустить!

W


----------------------- struct W{ long x, y; };

a


-------------------------- ------------------ #include <fcntl.h> #include <fcntl.h> #include "W.h" #include "W.h" struct W a; struct W b; main(){ ... main(){ ... printf("%ld... Кроме того, вынесение общих фрагментов текста программы (определений структур, конс- тант, и.т.п.) в отдельный файл экономит наши силы и время - вместо того, чтобы наби- вать один и тот же текст много раз в разных файлах, мы теперь пишем в каждом файле

единственную


ведь программа стала короче! Файлы включения имеют суффикс .h, что означает "header-file" (файл-заголовок). Синхронную перекомпиляцию всех программ в случае изменения include-файла можно

Makefile


a b


echo Запуск a и b

a


a


a


b


b


Правила make имеют вид цель: список_целей_от_которых_зависит команда

команда


список


хоть один из


st


1.139. Программа на Си может быть размещена в нескольких файлах. Каждый файл высту- пает в роли "модуля", в котором собраны сходные по назначению функции и переменные. Некоторые переменные и функции можно сделать невидимыми для других модулей. Для этого надо объявить их static:

внутри


(т.е. она будет сохранять свое значение при выходе из функции) и ограничивает ее

данной функции


- Переменные, описанные вне функций, и так являются статическими (по классу памяти). Однако слово static и в этом случае позволяет управлять видимостью этих

данного файла


данного файла


- Аргументы функции и локальные (автоматические) переменные функции и так сущест- вуют только на время вызова данной функции (память для них выделяется в стеке ____________________ |- Подробное описание make смотри в документации по системе UNIX. А. Богатырев, 1992-95 - 71 - Си в UNIX при входе в функцию и уничтожается при выходе) и видимы только внутри ее тела.

нельзя


f(x) static x; { x++; } незаконно. Таким образом все переменные и функции в данном файле делятся на две группы: - Видимые только внутри данного файла (локальные для модуля). Такие имена объяв- ляются с использованием ключевого слова static. В частности есть еще "более локальные" переменные - автоматические локалы функций и их формальные аргументы, которые видимы только в пределах данной функции. Также видимы лишь в пределах одной функции статические локальные переменные, объявленные в теле функции со словом static. - Видимые во всех файлах (глобальные имена).

интерфейс


лях. Локальные имена извне модуля недоступны. Если мы используем в файле-модуле функции и переменные, входящие в интерфейс

другого


сатели extern и int можно опускать:

A


int x, y, z; // глобальные char ss[200]; // глоб. static int v, w; // локальные static char *s, p[20]; // лок. int f(){ ... } // глоб. char *g(){ ... } // глоб. static int h(){ ... } // лок. static char *sf(){ ... } // лок. int fi(){ ... } // глоб.

B


extern int x, y; extern z; // int можно опустить extern char ss[]; // размер можно опустить extern int f(); char *g(); // extern можно опустить extern fi(); // int можно опустить Хорошим тоном является написание комментария - из какого модуля или библиотеки импор- тируется переменная или функция:

A


char *tgetstr(); /* import from termlib */

A


____________________

Makefile


CFLAGS = -O

AB


A


A


A


B


B


и собирать программу просто вызывая команду make. А. Богатырев, 1992-95 - 72 - Си в UNIX

A


x


A


----------------------------------------- int x=12; int x=25; main(){ f(y) int *y; f(&x); { printf("%d\n", x); *y += x; } }

глобальная


x


модуля):

x


_


A


---------------------------------------------------- int x; extern int x; main(){ f(5); g(77); } g(n){ f(x+n); } f(n) { x=n; } f(m){ printf("%d\n", m); }

B


системой


_


один


любом


функцией файла|=. 1.140. В чем ошибка?

A


---------------------------------------------------- extern int x; extern int x; main(){ x=2; f(){ f(); printf("%d\n", x); } }

x


x


extern! 1.141. В чем ошибка?

A


---------------------------------------------------- int x; extern double x; ... ...

Типы


каждый файл компилируется отдельно, независимо от остальных, а при "склейке" файлов в ____________________ |= Если вы пользуетесь "новым" стилем объявления функций, но не используете прото- типы, то следует определять каждую функцию до первого места ее использования, чтобы компилятору в точке вызова был известен ее заголовок. Это приведет к тому, что main()

последней


еще. А. Богатырев, 1992-95 - 73 - Си в UNIX

имена


их типы и прототипы. В результате программа нормально скомпилируется и соберется, но результат ее выполнения будет непредсказуем! Поэтому объявления extern тоже полезно выносить в include-файлы:

proto


------------------ extern int x;

A


------------------ ------------------

proto


int x; ...

x


т.к. в момент настоящего объявления этой переменной это слово начнет просто игнориро- ваться (лишь бы типы в объявлении с extern и без него совпадали - иначе ошибка!). 1.142. Что печатает программа и почему? int a = 1; /* пример Bjarne Stroustrup-а */ void f(){ int b = 1; static int c = 1; printf("a=%d b=%d c=%d\n", a++, b++, c++); } void main(){ while(a < 4) f(); } Ответ: a=1 b=1 c=1 a=2 b=1 c=2 a=3 b=1 c=3 1.143. Автоматическая переменная видима только внутри блока, в котором она описана. Что напечатает программа?

A


int x=666; /*глоб.*/ main(){ f(3); printf(" ::x = %d\n", x); g(2); g(5); printf(" ::x = %d\n", x); } g(n){ static int x=17; /*видима только в g*/ printf("g::x = %2d g::n = %d\n", x++, n); if(n) g(n-1); else x = 0; }

B


extern x; /*глобал*/ f(n){ /*локал функции*/ x++; /*глобал*/ { int x; /*локал блока*/ x = n+1; /*локал*/ А. Богатырев, 1992-95 - 74 - Си в UNIX n = 2*x; /*локал*/ } x = n-1; /*глобал*/ } 1.144. Функция, которая - не содержит внутри себя статических переменных, хранящих состояние процесса обработки данных (функция без "памяти");

только


статические переменные);

только


return);

реентерабельной


может параллельно (или псевдопараллельно) использоваться несколькими "потоками" обра- ботки информации в нашей программе, без какого-либо непредвиденного влияния этих "потоков обработки" друг на друга. Первый пункт требований позволяет функции не зависеть ни от какого конкретного процесса обработки данных, т.к. она не "помнит" обработанных ею ранее данных и не строит свое поведение в зависимости от них. Вторые

все без исключения


и из нее (интерфейс функции) были перечислены в ее заголовке. Это лишает функцию "побочных эффектов", не предусмотренных программистом при ее вызове (программист обычно смотрит только на заголовок функции, и не выискивает "тайные" связи функции с программой через глобальные переменные, если только это специально не оговорено). Вот пример не реентерабельной функции: FILE *fp; ... /* глобальный аргумент */ char delayedInput () { static char prevchar; /* память */ char c; c = prevchar; prevchar = getc (fp); return c; } А вот ее реентерабельный эквивалент: char delayedInput (char *prevchar, FILE *fp) { char c; c = *prevchar; *prevchar = getc (fp); return c; } /* вызов: */ FILE *fp1, *fp2; char prev1, prev2, c1, c2; ... x1 = delayedInput (&prev1, fp1); x2 = delayedInput (&prev2, fp2); ...

prevchar


подаются в нее в виде аргумента. Реентерабельные функции независимы от остальной части программы (их можно скопи-

все


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

глобальных


побочных


тоянии проверить прототип такой функции и предупредить вас, если вы забыли задать какой-то аргумент; если же аргументы передаются через глобальные переменные - вы можете забыть проинициализировать какую-то из них). Старайтесь делать функции реен- терабельными! А. Богатырев, 1992-95 - 75 - Си в UNIX Вот еще один пример на эту тему. Не-реентерабельный вариант: int x, y, result; int f (){ static int z = 4; y = x + z; z = y - 1; return x/2; } Вызов: x=13; result = f(); printf("%d\n", y); А вот реентерабельный эквивалент: int y, result, zmem = 4; int f (/*IN*/ int x, /*OUT*/ int *ay, /*INOUT*/ int *az){ *az = (*ay = x + *az) - 1; return x/2; } Вызов: result = f(13, &y, &zmem); printf("%d\n", y); 1.145. То, что формат заголовка функции должен быть известен компилятору до момента ее использования, побуждает нас помещать определение функции до точки ее вызова. Так,

main


g


f


main


разрабатывается


читается


main


Так мы вынуждены писать, чтобы удовлетворить Си-компилятор: #include <stdio.h> unsigned long g(unsigned char *s){ const int BITS = (sizeof(long) * 8); unsigned long sum = 0; for(;*s; s++){ sum ^= *s; /* cyclic rotate left */ sum = (sum<<1)|(sum>>(BITS-1)); } return sum; } void f(char *s){ printf("%s %lu\n", s, g((unsigned char *)s)); } int main(int ac, char *av[]){ int i; for(i=1; i < ac; i++) f(av[i]); return 0; } А вот как мы разрабатываем программу: А. Богатырев, 1992-95 - 76 - Си в UNIX #include <stdio.h> int main(int ac, char *av[]){ int i; for(i=1; i < ac; i++) f(av[i]); return 0; } void f(char *s){ printf("%s %lu\n", s, g((unsigned char *)s)); } unsigned long g(unsigned char *s){ const int BITS = (sizeof(long) * 8); unsigned long sum = 0; for(;*s; s++){ sum ^= *s; /* cyclic rotate left */ sum = (sum<<1)|(sum>>(BITS-1)); } return sum; } и вот какую ругань производит Си-компилятор в ответ на эту программу: "0000.c", line 10: identifier redeclared: f current : function(pointer to char) returning void previous: function() returning int : "0000.c", line 7 "0000.c", line 13: identifier redeclared: g current : function(pointer to uchar) returning ulong previous: function() returning int : "0000.c", line 11 Решением проблемы является - задать прототипы (объявления заголовков) всех функций в

начале файла


#include <stdio.h> int main(int ac, char *av[]); void f(char *s); unsigned long g(unsigned char *s); ... Тогда функции будет можно располагать в тексте в любом порядке. 1.146. Рассмотрим процесс сборки программы из нескольких файлов на языке Си. Пусть

file1


функций функцию main). Ключ компилятора -o заставляет создавать выполняемую прог- рамму с именем, указанным после этого ключа. Если этот ключ не задан - будет создан

a


file


file


cc -c file1.c получится file1.o cc -c file2.c file2.o cc -c file3.c file3.o

file


Ключ -c заставляет компилятор превратить файл на языке Си в "объектный" файл А. Богатырев, 1992-95 - 77 - Си в UNIX (содержащий машинные команды; не будем вдаваться в подробности). Четвертая команда "склеивает" объектные файлы в единое целое - выполняемую программу|-. При этом, если какие-то функции, используемые в нашей программе, не были определены (т.е. спрограм- мированы нами) ни в одном из наших файлов - будет просмотрена библиотека стандартных функций. Если же каких-то функций не окажется и там - будет выдано сообщение об ошибке. Если у нас уже есть какие-то готовые объектные файлы, мы можем транслировать только новые Си-файлы: cc -c file4.c

file


или (что то же самое, но cc сам разберется, что надо делать)

file


библио-


теку


ar


file


file


archive


библиотеку:

file


file4


ции), то просматривается библиотека, и в список файлов для "склейки" добавляется файл из библиотеки, содержащий определение этой функции (из библиотеки он не удаляется!). Тонкость: из библиотеки берутся не ВСЕ файлы, а лишь те, которые содержат определения недостающих функций|=. Если, в свою очередь, файлы, извлекаемые из библиотеки, будут содержать неопределенные функции - библиотека (библиотеки) будут просмотрены еще раз и.т.д. (на самом деле достаточно максимум двух проходов, так как при первом просмотре

каталог


вызывают). Можно указывать и несколько библиотек: cc file6.c file7.o \

file


Таким образом, в команде cc можно смешивать имена файлов: исходных текстов на Си .c, объектных файлов .o и файлов-библиотек .a. Просмотр библиотек, находящихся в стандартных местах (каталогах /lib и /usr/lib), можно включить и еще одним способом: указав ключ -l. Если библиотека называется

LIBR1


то подключение делается ключами

LIBR1


____________________ |- На самом деле, для "склейки" объектных файлов в выполняемую программу, команда /bin/cc вызывает программу /bin/ld - link editor, linker, редактор связей, компонов- щик. |= Поэтому библиотека может быть очень большой, а к нашей программе "приклеится" лишь небольшое число файлов из нее. В связи с этим стремятся делать файлы, помещаемые в библиотеку, как можно меньше: 1 функция; либо "пачка" функций, вызывающих друг друга. А. Богатырев, 1992-95 - 78 - Си в UNIX соответственно.

LIBR1


Список библиотек и ключей -l должен идти после имен всех исходных .c и объектных .o файлов.

lib


автоматически


при сборке, если какие-то функции, использованные вами, не были вами определены), то

всегда


printf, strcat, read). Многие прикладные пакеты функций поставляются именно в виде библиотек. Такие библиотеки состоят из ряда .o файлов, содержащих объектные коды для различных функций (т.е. функции в скомпилированном виде). Исходные тексты от большинства библиотек не поставляются (так как являются коммерческой тайной). Тем не менее, вы можете исполь- зовать эти функции, так как вам предоставляются разработчиком: - описание (документация). - include-файлы, содержащие форматы данных используемые функциями библиотеки (именно эти файлы включались #include в исходные тексты библ. функций. Теперь уже вы должны включать их в свою программу).

вызывать


данных вы должны использовать в своей программе для обращения к ним (хотя и не имеете текстов самих библиотечных функций, т.е. не знаете, как они устроены. Например, вы часто используете printf(), но задумываетесь ли вы о ее внутреннем устройстве?). Некоторые библиотечные функции могут быть вообще написаны не на Си, а на ассемблере или другом языке программирования|-|-. Еще раз обращаю ваше внимание, что библиотека

не исходные тексты


жат (как правило) не тексты функций, а только описание форматов данных)! Библиотека может также содержать статические данные, вроде массивов строк-сообщений об ошибках. Посмотреть список файлов, содержащихся в библиотеке, можно командой

имяФайлаБиблиотеки


а список имен функций - командой

имяФайлаБиблиотеки


Извлечь файл (файлы) из архива (скопировать его в текущий каталог), либо удалить его из библиотеки можно командами

имяФайлаБиблиотеки имяФайла1


имяФайлаБиблиотеки имяФайла1


где ... означает список имен файлов. "Лицом" библиотек служат прилагаемые к ним include-файлы. Системные include- файлы, содержащие общие форматы данных для стандартных библиотечных функций, хранятся

usr


файл


файл


____________________ |-|- Обратите внимание, что библиотечные функции не являются частью ЯЗЫКА Си как та- кового. То, что в других языках (PL/1, Algol-68, Pascal) является частью языка

оператора


функция


ким образом мощь языка Си состоит именно в том, что он позволяет использовать функ- ции, написанные другими программистами и даже на других языках, т.е. является функци- онально расширяемым. А. Богатырев, 1992-95 - 79 - Си в UNIX (sys - это каталог, где описаны форматы данных, используемых ядром ОС и системными вызовами). Ваши собственные include-файлы (посмотрите в предыдущий раздел!) ищутся в текущем каталоге и включаются при помощи

файл


h


usr


Непременно изучите содержимое стандартных include-файлов в своей системе! В качестве резюме - схема, поясняющая "превращения" Си-программы из текста на языке программирования в выполняемый код: все файлы .c могут использовать общие include-файлы; их подстановку в текст, а также обработку #define произведет препро- цессор cpp file1.c file2.c file3.c | | | "препроцессор" | cpp | cpp | cpp | | | "компиляция" | cc -c | cc -c | cc -c | | | file1.o file2.o file3.o | | | -----------*----------- | Неявно добавятся:

lib


lib


"связывание" | "компоновка" |<----- Явно указанные библиотеки:

m


V

a


1.147. Напоследок - простой, но жизненно важный совет. Если вы пишете программу, которую вставите в систему для частого использования, поместите в исходный текст этой программы идентификационную строку наподобие

This is


Тогда в случае аварии в файловой системе, если вдруг ваш файл "потеряется" (то есть у него пропадет имя - например из-за порчи каталога), то он будет найден программой проверки файловой системы - fsck - и помещен в каталог /lost+found под специальным кодовым именем, ничего общего не имеющим со старым. Чтобы понять, что это был за файл и во что его следует переименовать (чтобы восстановить правильное имя), мы при- меним команду

имя


Эта команда покажет все длинные строки из печатных символов, содержащиеся в данном

id


переименовать так:

имя


1.148. Где размещать include-файлы и как программа узнает, где же они лежат? Стан-

usr


пишем некую свою программу (проект) и используем директивы

имяФайла


А. Богатырев, 1992-95 - 80 - Си в UNIX

имяФайла


программой на Си). Однако мы можем помещать ВСЕ наши include-файлы в одно место (скажем, известное группе программистов, работающих над одним и тем же проектом). Хорошее место для всех ваших личных include-файлов - каталог (вами созданный)

include


где $HOME - ваш домашний каталог. Хорошее место для общих include-файлов - каталог

usr


Как сказать компилятору, что #include "" файлы надо брать из определенного места, а не из текущего каталога? Это делает ключ компилятора

имя


Например:

x


x


ac


.... return 0; }

x


лог). Запуск программы на компиляцию выглядит так:

home


или

include


x


include


Ключ -O задает вызов компилятора с оптимизацией. Ключ -I оказывает влияние и на #include <> директивы тоже. Для ОС Solaris на машинах Sun программы для оконной системы X Window System содержат строки вроде

X11


X11


usr


тому запуск на компиляцию оконных программ на Sun выглядит так:

usr


xprogram


X11


Если include-файлы находятся во многих каталогах, то можно задать поиск в нес- кольких каталогах, к примеру:

usr


А. Богатырев, 1992-95 - 81 - Си в UNIX

2. Массивы, строки, указатели.


Массив представляет собой агрегат из нескольких переменных одного и того же

a


a


Это соответствует тому, что объявляются переменные типа TYPE со специальными именами

a


x


x


value


a


целого


типа: char, short, int, long. Индексы элементов массива в Си начинаются с 0 (а не с

LENGTH


LENGTH


a


indx


a


indx


чтения/записи несуществующего элемента) может привести к непредсказуемым результатам и поведению программы. Отметим, что это одна из самых распространенных ошибок.

Статические


элементов в {} через запятую. Если задано меньше элементов, чем длина массива - остальные элементы считаются нулями:

a10


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

a3


В большинстве современных компьютеров (с фон-Неймановской архитектурой) память

массив байт


в памяти выделяется непрерывная область для хранения этой переменной. Все байты

Номер байта


адресом


сегмента


номера байта в этом сегменте). В Си адрес переменной можно получить с помощью опера-

var


нельзя


(смотри ниже).

несколько


памяти, требуемого для хранения значения типа TYPE, можно узнать при помощи операции

TYPE


sizeof(char)==1. В некоторых машинах адреса переменных (а также агрегатов данных -

int


align


данным более быстрым (аппаратура работает эффективнее).

указатели


pointer


менной). Отличие указателей от машинных адресов состоит в том, что указатель может

определенного типа


зывать на данные типа TYPE, описывается так:

var


ptr


ptr


А. Богатырев, 1992-95 - 82 - Си в UNIX

ptr


ptr


var


array


ptr


x


ptr


на такой же тип


ptr1


Мы можем изменять указуемую переменную при помощи операции *

ptr


value


array


поставлен указатель, то есть

ptr


Таким образом, операция * (значение по адресу) оказывается обратной к операции & (взятие адреса):

ptr


ptr


ptr


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

ptrptr


Имя массива - это константа, представляющая собой указатель на 0-ой элемент мас- сива. Этот указатель отличается от обычных тем, что его нельзя изменить (установить на другую переменную), поскольку он сам хранится не в переменной, а является просто некоторым постоянным адресом. массив указатель ____________ _____ array: | array[0] | ptr:| * | | array[1] | | | array[2] |<--------- сейчас равен &array[2] | ... | Следствием такой интерпретации имен массивов является то, что для того чтобы поста- вить указатель на начало массива, надо писать

ptr


но не

ptr


Операция & перед одиноким именем массива не нужна и недопустима! Такое родство указателей и массивов позволяет нам применять операцию * к имени

value


номера


ptr


ptr


ptr


ptr


А. Богатырев, 1992-95 - 83 - Си в UNIX

byteaddr


то есть прибавление единицы к указателю продвигает адрес не на 1 байт, а на размер

ptr


array


ptr2


ptr1


ptr


указатели указывают на

ptr1


ptr2


Если мы теперь рассмотрим цепочку равенств

ptr2


array


то получим

ptr


взятия значения по адресу, взятия адреса и прибавления целого к указателю связаны соотношениями:

ptr


ptr


x


ptr


ptr


Указатели можно индексировать подобно массивам. Рассмотрим пример: /* индекс: 0 1 2 3 4 */

numbers


dptr


number


numbers: [0] [1] [2] [3] [4] | [-2] [-1] [0] [1] [2] dptr поскольку

x


i


x


Указатель на один тип можно преобразовать в указатель на другой тип: такое пре- образование не вызывает генерации каких-либо машинных команд, но заставляет компиля- тор изменить параметры адресной арифметики, а также операции выборки данного по ука-

раз-


мерах


если указатель - на структурный тип). Целые (int или long) числа иногда можно преобразовывать в указатели. Этим поль- зуются при написании драйверов устройств для доступа к регистрам по физическим адре- сам, например: А. Богатырев, 1992-95 - 84 - Си в UNIX unsigned short *KISA5 = (unsigned short *) 0172352; Здесь возникают два тонких момента: 1. Как уже было сказано, адреса данных часто выравниваются на границу некоторого типа. Мы же можем задать невыровненное целое значение. Такой адрес будет некорректен. 2. Структура адреса, поддерживаемая процессором, может не соответствовать формату целых (или длинных целых) чисел. Так обстоит дело с IBM PC 8086/80286, где адрес

весь


(если рассматривать эти два числа как одно длинное целое) не является обычным

SEGMENT


преобразуется так

SEGMENT


ADDRESS


ADDRESS


диспетчером памяти


ется "виртуальным" (т.е. воображаемым, ненастоящим) и может не совпадать с физи- ческим адресом, по которому данные хранятся в памяти компьютера. В памяти может

своя


адресное пространство


начала области памяти, выделенной данной программе. Преобразование виртуальных адресов в физические выполняется аппаратно.

TYPE


является просто признаком, используемым для обозначения несуществующего адреса или конца цепочки указателей, и имеет специальное обозначение NULL. Обращение (выборка или запись данных) по этому указателю считается некорректным (кроме случая, когда вы

физическими


Отметим, что указатель можно направить в неправильное место - на участок памяти, содержащий данные не того типа, который задан в описании указателя; либо вообще содержащий неизвестно что: int i = 2, *iptr = &i; double x = 12.76; iptr += 7; /* куда же он указал ?! */ iptr = (int *) &x; i = *iptr; Само присваивание указателю некорректного значения еще не является ошибкой. Ошибка возникнет лишь при обращении к данным по этому указателю (такие ошибки довольно тяжело искать!). При передаче имени массива в качестве параметра функции, как аргумент передается не копия САМОГО МАССИВА (это заняло бы слишком много места), а копия АДРЕСА 0-ого элемента этого массива (т.е. указатель на начало массива).

x


xa


a


main(){

a


a


}

a


x


a


xa


xa


указатель


массива в объявлении аргумента можно не указывать. Это позволяет одной функцией А. Богатырев, 1992-95 - 85 - Си в UNIX обрабатывать массивы разной длины:

xa


xa


xa


Если функция должна знать длину массива - передавайте ее как дополнительный аргумент: int sum( int a[], int len ){ int s=0, i; for(i=0; i < len; i++) s += a[i]; return( s ); } ... int arr[10] = { ... }; ... int sum10 = sum(arr, 10); ...

arr


LENGTH


или

LENGTH


N


ления длины массивов, задаваемых в виде

arr


arr


arr


одинаковый


Строка


завершающаяся в конце специальным признаком - байтом '\0'. Этот признак добавляется

строка


явно


чена лишь размером массива, в котором сохранена строка, и может изменяться в процессе

0


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

начало


при ее просмотре. С массивами байт можно использовать следующую конструкцию, задающую массивы (строки) одинакового размера:

stringA


stringB


В данном разделе мы в основном будем рассматривать строки и указатели на символы. 2.1. Операции взятия адреса объекта и разыменования указателя - взаимно обратны.

objx


ptrx


objx


ptrx


Вот пример того, как можно заменить условный оператор условным выражением (это удастся не всегда): if(c) a = 1; else b = 1; А. Богатырев, 1992-95 - 86 - Си в UNIX Предупреждение: такой стиль не способствует понятности программы и даже компактности ее кода. #include <stdio.h> int main(int ac, char *av[]){ int a, b, c; a = b = c = 0; if(av[1]) c = atoi(av[1]); *(c ? &a : &b) = 1; /* !!! */ printf("cond=%d a=%d b=%d\n", c, a, b); return 0; } 2.2. Каким образом инициализируются по умолчанию внешние и статические массивы? Ини- циализируются ли по умолчанию автоматические массивы? Каким образом можно присваи- вать значения элементам массива, относящегося к любому классу памяти?

arr


arr[0] *arr *arr + 2 arr[2] *(arr + 2) arr &arr[2] arr+2

a


единицу?

a


Ответ: нет, надо:

a


2.5. Дан фрагмент текста:

a


b


Чему равны

b


(Ответ: 'x', '\0', 'd' )

a


да, да, нет) 2.6. Ниже приведена программа, вычисляющая среднее значение элементов массива int arr [] = {1, 7, 4, 45, 31, 20, 57, 11}; main () { int i; long sum; for ( i = 0, sum = 0L; i < (sizeof(arr)/sizeof(int)); i++ ) sum += arr[i]; printf ("Среднее значение = %ld\n", sum/8) А. Богатырев, 1992-95 - 87 - Си в UNIX } Перепишите указанную программу с применением указателей. 2.7. Что напечатается в результате работы программы? char arr[] = {'С', 'Л', 'А', 'В', 'А'}; main () { char *pt; int i; pt = arr + sizeof(arr) - 1; for( i = 0; i < 5; i++, pt-- ) printf("%c %c\n", arr[i], *pt); }

arr


Ответ: написать внутри main

arr


2.8. Можно ли писать на Си так: f( n, m ){ int x[n]; int y[n*2]; int z[n * m]; ... } Ответ: к сожалению нельзя (Си - это не Algol). При отведении памяти для массива в

константа


фиксиро-


ванную


2.9. Предположим, что у нас есть описание массива static int mas[30][100]; a) выразите адрес mas[22][56] иначе b) выразите адрес mas[22][0] двумя способами c) выразите адрес mas[0][0] тремя способами 2.10. Составьте программу инициализации двумерного массива a[10][10], выборки эле- ментов с a[5][5] до a[9][9] и их распечатки. Используйте доступ к элементам по ука- зателю. 2.11. Составьте функцию вычисления скалярного произведения двух векторов. Длина векторов задается в качестве одного из аргументов. 2.12. Составьте функцию умножения двумерных матриц a[][] * b[][]. 2.13. Составьте функцию умножения трехмерных матриц a[][][] * b[][][]. 2.14. Для тех, кто программировал на языке Pascal: какая допущена ошибка? char a[10][20]; char c; int x,y; ... c = a[x,y]; Ответ: многомерные массивы в Си надо индексировать так: А. Богатырев, 1992-95 - 88 - Си в UNIX c = a[x][y];

x


y


c = a[y]; Синтаксической ошибки нет, но смысл совершенно изменился! 2.15. Двумерные массивы в памяти представляются как одномерные. Например, если

a


a


такой:

a


_


то есть

a


Следствием этого является то, что компилятор для генерации индексации двумерных (и более) массовов должен знать M - размер массива по 2-ому измерению (а также 3-ему, 4-ому, и.т.д.). В частности, при передаче многомерного массива в функцию f(arr) int arr[N][M]; { ... } /* годится */ f(arr) int arr[] [M]; { ... } /* годится */ f(arr) int arr[] []; { ... } /* не годится */ f(arr) int (*arr)[M]; { ... } /* годится */ f(arr) int *arr [M]; { ... } /* не годится: это уже не двумерный массив, а одномерный массив указателей */ А также при описании внешних массивов: extern int a[N][M]; /* годится */ extern int a[ ][M]; /* годится */ extern int a[ ][ ]; /* не годится: компилятор не сможет сгенерить операцию индексации */ Вот как, к примеру, должна выглядеть работа с двумерным массивом arr[ROWS][COLS], отведенным при помощи malloc(); void f(int array[][COLS]){ int x, y; for(y=0; y < ROWS; y++) for(x=0; x < COLS; x++) array[y][x] = 1; } void main(){ int *ptr = (int *) malloc(sizeof(int) * ROWS * COLS); f( (int (*) [COLS]) ptr); } 2.16. Как описывать ссылки (указатели) на двумерные массивы? Рассмотрим такую прог- рамму: А. Богатырев, 1992-95 - 89 - Си в UNIX #include <stdio.h> #define First 3 #define Second 5 char arr[First][Second] = { "ABC.", { 'D', 'E', 'F', '?', '\0' }, { 'G', 'H', 'Z', '!', '\0' } }; char (*ptr)[Second]; main(){ int i; ptr = arr; /* arr и ptr теперь взаимозаменимы */ for(i=0; i < First; i++) printf("%s\t%s\t%c\n", arr[i], ptr[i], ptr[i][2]); }

ptr


Second


ные индексы. Попробуйте сами объявить char (*ptr)[4]; char (*ptr)[6]; char **ptr; и увидеть, к каким невеселым эффектам это приведет (компилятор, кстати, будет ругаться; но есть вероятность, что он все же странслирует это для вас. Но работать оно будет плачевно). Попробуйте также использовать ptr[x][y]. Обратите также внимание на инициализацию строк в нашем примере. Строка "ABC." равносильна объявлению { 'A', 'B', 'C', '.', '\0' },

s


помощи указателей, избавьтесь от операции умножения. Прямоугольник

x0


char s[W*H]; int x,y; int x0,y0,width,height; for(x=0; x < W*H; x++) s[x] = '.'; ... for(y=y0; y < y0+height; y++) for(x=x0; x < x0+width; x++) s[x + W*y] = '*'; Ответ: char s[W*H]; int i,j; int x0,y0,width,height; char *curs; ... for(curs = s + x0 + W*y0, i=0; i < height; i++, curs += W-width) for(j=0; j < width; j++) *curs++ = '*'; Такая оптимизация возможна в некоторых функциях из главы "Работа с видеопамятью". А. Богатырев, 1992-95 - 90 - Си в UNIX 2.18. Что означают описания? int i; // целое. int *pi; // указатель на целое. int *api[3]; // массив из 3х ук-лей на целые. int (*pai)[3]; // указатель на массив из 3х целых. // можно описать как int **pai; int fi(); // функция, возвращающая целое. int *fpi(); // ф-ция, возвр. ук-ль на целое. int (*pfi)(); // ук-ль на ф-цию, возвращающую целое. int *(*pfpi)(); // ук-ль на ф-цию, возвр. ук-ль на int. int (*pfpfi())(); // ф-ция, возвращающая указатель на // "функцию, возвращающую целое". int (*fai())[3]; // ф-ция, возвр. ук-ль на массив // из 3х целых. иначе ее // можно описать как int **fai(); int (*apfi[3])(); // массив из 3х ук-лей на функции, // возвращающие целые. Переменные в Си описываются в формате их использования. Так описание int (*f)(); означает, что f можно использовать в виде int value; value = (*f)(1, 2, 3 /* список аргументов */); Однако из такого способа описания тип самой описываемой переменной и его смысл

Communications of


the ACM


ние в стиле языка Algol-68. Далее

ТИП


ТИП


ТИП


x


Приведем несколько примеров, из которых ясен и способ преобразования: int (*f())(); означает (*f())() : int *f() : proc() int f() : ref proc() int f : proc() ref proc() int то есть f - функция, возвращающая указатель на функцию, возвращающую целое. int (*f[3])(); означает (*f[])() : int *f[] : proc() int f[] : ref proc() int f : array of ref proc() int f - массив указателей на функции, возвращающие целые. Обратно: опишем g как указа- тель на функцию, возвращающую указатель на массив из 5и указателей на функции, возв- ращающие указатели на целые. А. Богатырев, 1992-95 - 91 - Си в UNIX g : ref p() ref array of ref p() ref int *g : p() ref array of ref p() ref int (*g)() : ref array of ref p() ref int *(*g)() : array of ref p() ref int (*(*g)())[5] : ref p() ref int *(*(*g)())[5] : p() ref int (*(*(*g)())[5])(): ref int *(*(*(*g)())[5])(): int int *(*(*(*g)())[5])(); В Си невозможны функции, возвращающие массив: proc() array of ... а только proc() ref array of ... Само название типа (например, для использования в операции приведения типа) получа- ется вычеркиванием имени переменной (а также можно опустить размер массива): g = ( int *(*(*(*)())[])() ) 0;

d


Ответ: char *strcat(d,s) register char *d, *s; { while( *d ) d++; /* ищем конец строки d */ while( *d++ = *s++ ); /* strcpy(d, s) */ return (d-1); /* конец строки */ } Цикл, помеченный "strcpy" - это наиболее краткая запись операторов do{ char c; c = (*d = *s); s++; d++; } while(c != '\0'); На самом деле strcat должен по стандарту возвращать свой первый аргумент, как и функ- ция strcpy: char *strcat(d,s) register char *d, *s; { char *p = d; while( *d ) d++; strcpy(d, s); return p; } Эти два варианта демонстрируют, что функция может быть реализована разными способами. Кроме того видно, что вместо стандартной библиотечной функции мы можем определить

свою


ращаемое значение в 1-ом варианте). 2.20. Напишите программу, которая объединяет и распечатывает две строки, введенные с терминала. Для ввода строк используйте функцию gets(), а для их объединения - strcat(). В другом варианте используйте

result


2.21. Модифицируйте предыдущую программу таким образом, чтобы она выдавала длину (число символов) объединенной строки. Используйте функцию strlen(). Приведем нес- колько версий реализации strlen: /* При помощи индексации массива */ А. Богатырев, 1992-95 - 92 - Си в UNIX int strlen(s) char s[]; { int length = 0; for(; s[length] != '\0'; length++); return (length); } /* При помощи продвижения указателя */ int strlen(s) char *s; { int length; for(length=0; *s; length++, s++); return length; } /* При помощи разности указателей */ int strlen(register char *s) { register char *p = s; while(*p) p++; /* ищет конец строки */ return (p - s); } Разность двух указателей на один и тот же тип - целое число:

p1


p2


p2


p2


p2


p2


левый элемент массива.

s


s


s


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

s


ого элемента (поэтому в него нельзя производить запись признака конца). 2.23. Напишите функции преобразования строки, содержащей изображение целого числа, в само это число. В двух разных вариантах аргумент-адрес должен указывать на первый байт строки; на последний байт. Ответ: #define isdigit(c) ('0' <= (c) && (c) <= '9') int atoi(s) register char *s; { register int res=0, neg=0; for(;;s++){ switch(*s){ case ' ': case '\t': continue; case '-': neg++; case '+': s++; } break; } while(isdigit(*s)) res = res * 10 + *s++ - '0'; return( neg ? -res : res ); } int backatoi(s) register char *s; { int res=0, pow=1; while(isdigit(*s)){ А. Богатырев, 1992-95 - 93 - Си в UNIX res += (*s-- - '0') * pow; pow *= 10; } if(*s == '-') res = -res; return res; }

s


s


или

s


Ответ: нет. Массивы в Си нельзя присваивать целиком. Для пересылки массива байт надо

s


- это адрес начала памяти, выделенной для хранения массива), сделав его равным адресу

d


константой


описание


s


или

s


или

s


или

s


В этом случае компилятор резервирует память для хранения массива и расписывает ее байтами начального значения. Обратите внимание, что строка в двойных кавычках (если

массив


строке, поскольку в конце массива находится символ '\0' - признак конца, добавленный компилятором. Если бы мы написали

s


то компилятор сообщил бы об ошибке, поскольку длины массива (5) недостаточно, чтобы

s


читал необходимую длину массива. Наконец, возможна ситуация, когда массив больше, чем хранящаяся в нем строка. Тогда "лишнее" место содержит какой-то мусор (в static-памяти изначально - байты \0).

s


содержит: h e l l o \0 ? ? ? ? ? ? В программах текстовой обработки под "длиной строки" обычно понимают количество букв в строке НЕ считая закрывающий байт '\0'. Именно такую длину считает стандартная

s


строки


выше примера эти значения равны соответственно 5 и 12. Следует также отличать массивы от указателей:

sp


sp


sp


указатель


области памяти). Поскольку указатель - это переменная, то ее значение изменять

sp


sp


А. Богатырев, 1992-95 - 94 - Си в UNIX строку "hello". Здесь не происходит копирования массива, а происходит просто присва-

sp


Предостережем от возможной неприятности:

d


d


d


ничем не контролируется (ни компилятором, ни самой strcpy, ни вами явным образом), то

после


данных, которые будут испорчены. Это приведет к непредсказуемым эффектам. Некоторые возможности для контроля за длиной строк-аргументов вам дают функции

d


len


len


функцию strncpy:

dst


dst


n


save


save


dst


n


dst


save


}

save


}

n


dst


ректном (не каноническом) состоянии. Ответ:

s1


{

s1


return(0);

n


s1


return(0);

n


} 2.25. В чем ошибка?

stdio


s


s


s


s


s


2.26. Какие из приведенных конструкций обозначают одно и то же? А. Богатырев, 1992-95 - 95 - Си в UNIX char a[] = ""; /* пустая строка */ char b[] = "\0"; char c = '\0'; char z[] = "ab"; char aa[] = { '\0' }; char bb[] = { '\0', '\0' }; char xx[] = { 'a', 'b' }; char zz[] = { 'a', 'b', '\0' }; char *ptr = "ab"; 2.27. Найдите ошибки в описании символьной строки: main() { char mas[] = {'s', 'o', 'r', 't'}; /* "sort" ? */ printf("%s\n", mas); } Ответ: строка должна кончаться '\0' (в нашем случае printf не обнаружив символа конца

после


инициализированный массив не может быть автоматическим - требуется static: main() { static char mas[] = {'s', 'o', 'r', 't', '\0'}; } Заметим, что main(){ char *mas = "sort"; } законно, т.к. сама строка здесь хранится в статической памяти, а инициализируется лишь указатель на этот массив байт.

a


a


a


--------------------------------------------------- int n = 2; extern int n; char s[] = "012345678"; extern char *s; main(){ f(){ f(); s[n] = '+'; printf("%s\n", s ); } } Ответ: дело в том, что типы (char *) - указатель, и char[] - массив, означают одно и то же только при объявлении формального параметра функции:

arg


локальная переменная


изменять


используются


arg


скалярная переменная, хранящая адрес (указатель): -------- ------- p:| *--|----->| '0' | char -------- | '1' | char ... А. Богатырев, 1992-95 - 96 - Си в UNIX

a


------- a:| '0' | char | '1' | char ...

b


s


указатель на char. ------- s:| '0' | \ это будет воспринято как | '1' | / адрес других данных. | '2' | ... И индексироваться будет уже ЭТОТ адрес! Результат - обращение по несуществующему адресу. То, что написано у нас, эквивалентно char s[] = "012345678"; char **ss = s; /* s - как бы "массив указателей" */ /* первые байты s интерпретируются как указатель: */ char *p = ss[0]; p[2] = '+';

b


extern char s[]; /* размер указывать не требуется */ Вот еще один аналогичный пример, который пояснит вам, что происходит (а заодно пока- жет порядок байтов в long). Пример выполнялся на IBM PC 80386, на которой sizeof(char *) = sizeof(long) = 4

a


--------------------------------------------------- char s[20] = {1,2,3,4}; extern char *s; main(){ f(){ /*печать указателя как long */ f(); printf( "%08lX\n", s ); } } печатается 04030201. 2.29. Что напечатает программа? static char str1[ ] = "abc"; static char str2[4]; strcpy( str2, str1 ); /* можно ли написать str2 = str1; ? */ printf( str1 == str2 ? "равно":"не равно" ); Как надо правильно сравнивать строки? Что на самом деле сравнивается в данном при- мере?

адреса массивов


А. Богатырев, 1992-95 - 97 - Си в UNIX char str1[2]; char str2[2]; main(){ printf( str1 < str2 ? "<":">"); } печатает <, а если написать char str2[2]; char str1[2]; то напечатается >. 2.30. Напишите программу, спрашивающую ваше имя до тех пор, пока вы его правильно не введете. Для сравнения строк используйте функцию strcmp() (ее реализация есть в главе "Мобильность"). 2.31. Какие значения возвращает функция strcmp() в следующей программе? #include <stdio.h> main() { printf("%d\n", strcmp("abc", "abc")); /* 0 */ printf("%d\n", strcmp("ab" , "abc")); /* -99 */ printf("%d\n", strcmp("abd", "abc")); /* 1 */ printf("%d\n", strcmp("abc", "abd")); /* -1 */ printf("%d\n", strcmp("abc", "abe")); /* -2 */ } 2.32. В качестве итога предыдущих задач: помните, что в Си строки (а не адреса) надо сравнивать как if( strcmp("abc", "bcd") < 0) ... ; if( strcmp("abc", "bcd") == 0) ... ; вместо if( "abc" < "bcd" ) ... ; if( "abc" == "bcd" ) ... ; и присваивать как

d


d


2.33. Напишите программу, которая сортирует по алфавиту и печатает следующие ключе- вые слова языка Си: int char double long for while if 2.34. Вопрос не совсем про строки, скорее про цикл: чем плоха конструкция? char s[] = "You're a smart boy, now shut up."; int i, len; for(i=0; i < strlen(s); i++) putchar(s[i]); Ответ: в соответствии с семантикой Си цикл развернется примерно в А. Богатырев, 1992-95 - 98 - Си в UNIX i=0; LOOP: if( !(i < strlen(s))) goto ENDLOOP; putchar(s[i]); i++; goto LOOP; ENDLOOP: ;

s


ции цикла, совершая лишнюю работу! Борьба с этим такова: for(i=0, len=strlen(s); i < len; i++ ) putchar(s[i]); или for(i=0, len=strlen(s); len > 0; i++, --len ) putchar(s[i]); Аналогично, в цикле while( i < strlen(s))...; функция тоже будет вычисляться при каждой проверке условия! Это, конечно, относится к любой функции, используемой в условии, а не только к strlen. (Но, разумеется, случай когда функция возвращает признак "надо ли продолжать цикл" - совсем другое дело:

обязана


2.35. Что напечатает следующая программа? #include <stdio.h> main(){ static char str[] = "До встречи в буфете"; char *pt; pt = str; puts(pt); puts(++pt); str[7] = '\0'; puts(str); puts(pt); puts(++pt); } 2.36. Что напечатает следующая программа? main() { static char name[] = "Константин"; char *pt; pt = name + strlen(name); while(--pt >= name) puts(pt); } 2.37. Что напечатает следующая программа? char str1[] = "abcdef"; char str2[] = "xyz"; main(){ register char *a, *b; a = str1; b = str2; while( *b ) *a++ = *b++; printf( "str=%s a=%s\n", str1, a ); a = str1; b = str2; А. Богатырев, 1992-95 - 99 - Си в UNIX while( *b ) *++a = *b++; printf( "str=%s a=%s\n", str1, a ); } Ответ: str=xyzdef a=def str=xxyzef a=zef 2.38. Что печатает программа? char *s; for(s = "Ситроен"; *s; s+= 2){ putchar(s[0]); if(!s[1]) break; } putchar('\n'); 2.39. Что напечатает программа? Рассмотрите продвижение указателя s, указателей -

strs


++ изменяет указатель, а в каких - букву в строке? Нарисуйте себе картинку, изобража- ющую состояние указателей - она поможет вам распутать эти спагетти. Уделите разбору этого примера достаточное время! #include <stdio.h> /* определение NULL */ /* Латинский алфавит: abcdefghijklmnopqrstuvwxyz */ char *strs[] = { "abcd","ABCD","0fpx","159", "hello","-gop","A1479",NULL }; main(){ char c, **s = strs, *p; c = *++*s; printf("#1 %d %c %s\n", s-strs, c, *s); c = **++s; printf("#2 %d %c %s\n", s-strs, c, *s); c = **s++; printf("#3 %d %c %s\n", s-strs, c, *s); c = ++**s; printf("#4 %d %c %s\n", s-strs, c, *s); c = (**s)++; printf("#5 %d %c %s\n", s-strs, c, *s); c = ++*++*s; printf("#6 %d %c %s\n", s-strs, c, *s); c = *++*s++; printf("#7 %d %c %s %s\n", s-strs, c, *s, strs[2]); c = ++*++*s++; printf("#8 %d %c %s %s\n", s-strs, c, *s, strs[3]); c = ++*++*++s; printf("#9 %d %c %s\n", s-strs,c,*s); c = ++**s++; printf("#10 %d %c %s\n",s-strs,c,*s); p = *s; c = ++*(*s)++; printf("#11 %d %c %s %s %s\n",s-strs,c,*s,strs[6],p); c = ++*((*s)++); printf("#12 %d %c %s %s\n", s-strs, c, *s, strs[6]); c = (*++(*s))++; printf("#13 %d %c %s %s\n", s-strs, c, *s, strs[6]); for(s=strs; *s; s++) printf("strs[%d]=\"%s\"\n", s-strs, *s); putchar('\n'); } Печатается: А. Богатырев, 1992-95 - 100 - Си в UNIX #1 0 b bcd strs[0]="bcd" #2 1 A ABCD strs[1]="ABCD" #3 2 A 0fpx strs[2]="px" #4 2 1 1fpx strs[3]="69" #5 2 1 2fpx strs[4]="hello" #6 2 g gpx strs[5]="iop" #7 3 p 159 px strs[6]="89" #8 4 6 hello 69 #9 5 h hop #10 6 i A1479 #11 6 B 1479 1479 B1479 #12 6 2 479 479 #13 6 7 89 89 Учтите, что конструкция char *strs[1] = { "hello" };

strs


содержащего строку "hello". Этот указатель можно изменять! Попробуйте составить еще подобные примеры из *, ++, (). 2.40. Что печатает программа? char str[25] = "Hi, "; char *f(char **s){ int cnt; for(cnt=0; **s != '\0'; (*s)++, ++cnt); return("ny" + (cnt && (*s)[-1] == ' ') + (!cnt)); } void main(void){ char *s = str; if( *f(&s) == 'y') strcat(s, "dude"); else strcat(s, " dude"); printf("%s\n", str); } Что она напечатает, если задать char str[25]="Hi,"; или char str[25]=""; 2.41. В чем состоит ошибка? (Любимая ошибка начинающих) main(){

buf


buf


buf


}

buf


рит неизвестно куда. Надо было писать например так:

buf


или

mem


Обратите на этот пример особое внимание, поскольку, описав указатель (но никуда его не направив), новички успокаиваются, не заботясь о выделении памяти для хранения дан- ных. Указатель должен указывать на ЧТО-ТО, в чем можно хранить данные, а не "висеть", указывая "пальцем в небо"! Запись информации по "висячему" указателю разрушает память программы и приводит к скорому (но часто не немедленному и потому таинственному) краху. А. Богатырев, 1992-95 - 101 - Си в UNIX Вот программа, которая также использует неинициализированный указатель. На машине SPARCstation 20 эта программа убивается операционной системой с диагностикой "Segmentation fault" (SIGSEGV). Это как раз и значит обращение по указателю, указы- вающему "пальцем в небо". main(){ int *iptr; int ival = *iptr; printf("%d\n", ival); } 2.42. Для получения строки "Life is life" написана программа: main(){

buf


buf


buf


buf


buf


}

buf


Ответ: в начале массива окажется мусор, поскольку автоматический массив не инициали- зируется байтами '\0', а функция strcat() приписывает строки к концу строки. Для исп- равления можно написать

buf


перед первым strcat()-ом, либо вместо первого strcat()-а написать

buf


s1


s1


s


Многие современные компиляторы сами обращаются с подобными короткими (1-3 опера- тора) стандартными функциями как с макросами, то есть при обращении к ним генерят не вызов функции, а подставляют текст ее тела в место обращения. Это делает объектный код несколько "толще", но зато быстрее. В расширенных диалектах Си и в Си++ компиля- тору можно предложить обращаться так и с вашей функцией - для этого функцию следует объявить как inline (такие функции называются еще "intrinsic"). 2.45. Составьте рекурсивную и нерекурсивную версии программы инвертирования (зер- кального отображения) строки: abcdef --> fedcba.

s


s


Перепишите эту функцию с указателями, чтобы она возвращала указатель на первое вхождение символа. Если символ в строке отсутствует - выдавать NULL. В UNIX System-V такая функция называется strchr. Вот возможный ответ: char *strchr(s, c) register char *s, c; { while(*s && *s != c) s++; return *s == c ? s : NULL; } А. Богатырев, 1992-95 - 102 - Си в UNIX Заметьте, что p=strchr(s,'\0'); выдает указатель на конец строки. Вот пример исполь- зования: extern char *strchr(); char *s = "abcd/efgh/ijklm"; char *p = strchr(s, '/'); printf("%s\n", p==NULL ? "буквы / нет" : p); if(p) printf("Индекс вхождения = s[%d]\n", p - s ); 2.47. Напишите функцию strrchr(), указывающую на последнее вхождение символа. Ответ: char *strrchr(s, c) register char *s, c; { char *last = NULL; do if(*s == c) last = s; while(*s++); return last; } Вот пример ее использования: extern char *strrchr(); char p[] = "wsh"; /* эталон */ main(argc, argv) char *argv[];{ char *s = argv[1]; /* проверяемое имя */ /* попробуйте вызывать * a.out csh * a.out /bin/csh * a.out wsh * a.out /usr/local/bin/wsh */ char *base = (base = strrchr(s, '/')) ? base+1 : s; if( !strcmp(p, base)) printf("Да, это %s\n" , p); else printf("Нет, это %s\n", base); /* еще более изощренный вариант: */ if( !strcmp(p,(base=strrchr(s,'/')) ? ++base : (base=s)) ) printf("Yes %s\n", p); else printf("No %s\n", base); }

to


from


Ответ: #define substr(to, from, n, len) strncpy(to, from+n, len) или более корректная функция: А. Богатырев, 1992-95 - 103 - Си в UNIX char *substr(to, from, n, len) char *to, *from; { int lfrom = strlen(from); if(n < 0 ){ len += n; n = 0; } if(n >= lfrom || len <= 0) *to = '\0'; /* пустая строка */ else{ /* длина остатка строки: */ if(len > lfrom-n) len = lfrom - n; strncpy(to, from+n, len); to[len] = '\0'; } return to; }

abc


abc


делать. Эта функция полезна для генерации имен файлов с заданным расширением. Сде- лайте расширение аргументом функции.

s


int ls = strlen(s), lp = strlen(p); if(ls >= lp && !strcmp(s+ls-lp, p)) ...совпали...;

c


строки) и удаления символа в заданной позиции (со сдвижкой строки). Строка должна изменяться "на месте", т.е. никуда не копируясь. Ответ: /* удаление */ char delete(s, at) register char *s; { char c; s += at; if((c = *s) == '\0') return c; while( s[0] = s[1] ) s++; return c; } /* либо просто strcpy(s+at, s+at+1); */ /* вставка */ insert(s, at, c) char s[], c; { register char *p; s += at; p = s; while(*p) p++; /* на конец строки */ p[1] = '\0'; /* закрыть строку */ for( ; p != s; p-- ) p[0] = p[-1]; *s = c; }

c


встречается. Ответ: А. Богатырев, 1992-95 - 104 - Си в UNIX delc(s, c) register char *s; char c; { register char *p = s; while( *s ) if( *s != c ) *p++ = *s++; else s++; *p = '\0'; /* не забывайте закрывать строку ! */ }

S1


S2


s


табуляции и перевода строки должны заменяться на специальные двухсимвольные последо- вательности "\n" и "\t". Используйте switch. 2.54. Составьте функцию, которая "укорачивает" строку, заменяя изображения спецсим- волов (вроде "\n") на сами эти символы ('\n'). Ответ: extern char *strchr(); void unquote(s) char *s; { static char from[] = "nrtfbae", to [] = "\n\r\t\f\b\7\33"; char c, *p, *d; for(d=s; c = *s; s++) if( c == '\\'){ if( !(c = *++s)) break; p = strchr(from, c); *d++ = p ? to[p - from] : c; }else *d++ = c; *d = '\0'; }

S


Q


P = "ура"; Q = "ой"; S = "ура-ура-ура!"; Результат: "ой-ой-ой!" 2.56. Кроме функций работы со строками (где предполагается, что массив байт заверша- ется признаком конца '\0'), в Си предусмотрены также функции для работы с массивами

длину


n


c


s


#define REG register char *memset(s, c, n) REG char *s, c; { REG char *p = s; while( --n >= 0 ) *p++ = c; return s; } char *memcpy(dst, src, n) REG char *dst, *src; REG int n; { REG char *d = dst; А. Богатырев, 1992-95 - 105 - Си в UNIX while( n-- > 0 ) *d++ = *src++; return dst; } char *memchr(s, c, n) REG char *s, c; { while(n-- && *s++ != c); return( n < 0 ? NULL : s-1 ); } int memcmp(s1, s2, n) REG char *s1, *s2; REG n; { while(n-- > 0 && *s1 == *s2) s1++, s2++; return( n < 0 ? 0 : *s1 - *s2 ); }

стандартные


стандартными


(strcpy, strlen, strchr, memcpy, ...)? Ответ: потому, что они обычно реализованы поставщиками системы ЭФФЕКТИВНО, то есть написаны не на Си, а на ассемблере с использованием специализированных машинных команд и регистров. Это делает их более быстрыми. Написанный Вами эквивалент на Си может использоваться для повышения мобильности программы, либо для внесения поправок в стандартные функции. 2.58. Рассмотрим программу, копирующую строку саму в себя: #include <stdio.h> #include <string.h> char string[] = "abcdefghijklmn"; void main(void){ memcpy(string+2, string, 5); printf("%s\n", string); exit(0); Она печатает abababahijklmn. Мы могли бы ожидать, что кусок длины 5 символов "abcde" будет скопирован как есть: ab[abcde]hijklmn, а получили ab[ababa]hijklmn - цикличес- кое повторение первых двух символов строки... В чем дело? Дело в том, что когда области источника (src) и получателя (dst) перекрываются, то в некий момент *src берется из УЖЕ перезаписанной ранее области, то есть испорченной! Вот программа, иллюстрирующая эту проблему: А. Богатырев, 1992-95 - 106 - Си в UNIX #include <stdio.h> #include <string.h> #include <ctype.h> char string[] = "abcdefghijklmn"; char *src = &string[0]; char *dst = &string[2]; int n = 5; void show(int niter, char *msg){ register length, i; printf("#%02d %s\n", niter, msg); length = src-string; putchar('\t'); for(i=0; i < length+3; i++) putchar(' '); putchar('S'); putchar('\n'); printf("\t...%s...\n", string); length = dst-string; putchar('\t'); for(i=0; i < length+3; i++) putchar(' '); putchar('D'); putchar('\n'); } void main(void){ int iter = 0; while(n-- > 0){ show(iter, "перед"); *dst++ = toupper(*src++); show(iter++, "после"); } exit(0); } Она печатает: А. Богатырев, 1992-95 - 107 - Си в UNIX #00 перед S ...abcdefghijklmn... D #00 после S ...abAdefghijklmn... D #01 перед S ...abAdefghijklmn... D #01 после S ...abABefghijklmn... D #02 перед S ...abABefghijklmn... D #02 после S ...abABAfghijklmn... D #03 перед S ...abABAfghijklmn... D #03 после S ...abABABghijklmn... D #04 перед S ...abABABghijklmn... D #04 после S ...abABABAhijklmn... D Отрезки НЕ перекрываются, если один из них лежит либо целиком левее, либо целиком правее другого (n - длина обоих отрезков). dst src src dst ######## @@@@@@@@ @@@@@@@@ ######## dst+n <= src или src+n <= dst dst <= src-n или dst >= src+n Отрезки перекрываются в случае ! (dst <= src - n || dst >= src + n) = (dst > src - n && dst < src + n) При этом опасен только случай dst > src. Таким образом опасная ситуация описывается условием src < dst && dst < src + n (если dst==src, то вообще ничего не надо делать). Решением является копирование "от А. Богатырев, 1992-95 - 108 - Си в UNIX хвоста к голове": void bcopy(register char *src, register char *dst, register int n){ if(dst >= src){ dst += n-1; src += n-1; while(--n >= 0) *dst-- = *src--; }else{ while(n-- > 0) *dst++ = *src++; } } Или, ограничиваясь только опасным случаем:

src


n


if(dst==src || n <= 0) return; if(src < dst && dst < src + n) { dst += n-1; src += n-1; while(--n >= 0) *dst-- = *src--; }else memcpy(dst, src, n); } Программа #include <stdio.h> #include <string.h> #include <ctype.h> char string[] = "abcdefghijklmn"; char *src = &string[0]; char *dst = &string[2]; int n = 5; void show(int niter, char *msg){ register length, i; printf("#%02d %s\n", niter, msg); length = src-string; putchar('\t'); for(i=0; i < length+3; i++) putchar(' '); putchar('S'); putchar('\n'); printf("\t...%s...\n", string); length = dst-string; putchar('\t'); for(i=0; i < length+3; i++) putchar(' '); putchar('D'); putchar('\n'); } А. Богатырев, 1992-95 - 109 - Си в UNIX void main(void){ int iter = 0; if(dst==src || n <= 0){ printf("Ничего не надо делать\n"); return; } if(src < dst && dst < src + n) { dst += n-1; src += n-1; while(--n >= 0){ show(iter, "перед"); *dst-- = toupper(*src--); show(iter++, "после"); } }else while(n-- > 0){ show(iter, "перед"); *dst++ = toupper(*src++); show(iter++, "после"); } exit(0); } Печатает А. Богатырев, 1992-95 - 110 - Си в UNIX #00 перед S ...abcdefghijklmn... D #00 после S ...abcdefEhijklmn... D #01 перед S ...abcdefEhijklmn... D #01 после S ...abcdeDEhijklmn... D #02 перед S ...abcdeDEhijklmn... D #02 после S ...abcdCDEhijklmn... D #03 перед S ...abcdCDEhijklmn... D #03 после S ...abcBCDEhijklmn... D #04 перед S ...abcBCDEhijklmn... D #04 после S ...abABCDEhijklmn... D Теперь bcopy() - удобная функция для копирования и сдвига массивов, в частности мас- сивов указателей. Пусть у нас есть массив строк (выделенных malloc-ом): char *lines[NLINES]; Тогда циклическая перестановка строк выглядит так: А. Богатырев, 1992-95 - 111 - Си в UNIX void scrollUp(){ char *save = lines[0]; bcopy((char *) lines+1, /* from */ (char *) lines, /* to */ sizeof(char *) * (NLINES-1)); lines[NLINES-1] = save; } void scrollDown(){ char *save = lines[NLINES-1]; bcopy((char *) &lines[0], /* from */ (char *) &lines[1], /* to */ sizeof(char *) * (NLINES-1)); lines[0] = save; } Возможно, что написание по аналогии функции для копирования массивов элементов типа (void *) - обобщенных указателей - может оказаться еще понятнее и эффективнее. Такая функция - memmove - стандартно существует в UNIX SVR4. Заметьте, что порядок аргу-

все


_


(вместо unsigned long); в частности длина файла имеет именно этот тип (смотри систем- ные вызовы lseek и stat). #include <sys/types.h>

Dst


n


register caddr_t src = (caddr_t) Src, dst = (caddr_t) Dst; if(dst==src || n <= 0) return; if(src < dst && dst < src + n) { dst += n-1; src += n-1; while(--n >= 0) *dst-- = *src--; }else memcpy(dst, src, n); }

_


_


void *pointer; int n; значение pointer + n не определено и невычислимо, ибо sizeof(void) не имеет смысла - это не 0, а просто ошибка, диагностируемая компилятором! 2.59. Еще об опечатках: вот что бывает, когда вместо знака `=' печатается `-' (на клавиатуре они находятся рядом...). А. Богатырев, 1992-95 - 112 - Си в UNIX

stdio


strings


s


extern void *malloc();

s


}

ptr


ac


ptr


ptr


ptr


ptr


exit(0); }

pointer


ptr


телем в никуда. В операционной системе UNIX на машинах с аппаратной защитой памяти, страница памяти, содержащая адрес NULL (0) бывает закрыта на запись, поэтому любое обращение по записи в эту страницу вызывает прерывание от диспетчера памяти и аварий- ное прекращение процесса. Система сама помогает ловить ваши ошибки (но уже во время выполнения программы). Это ОЧЕНЬ частая ошибка - запись по адресу NULL. MS DOS в таких случаях предпочитает просто зависнуть, и вы бываете вынуждены играть аккорд из трех клавиш - Ctrl/Alt/Del, так и не поняв в чем дело.

стандартная


еще одну функцию для сохранения строк.

from


{

ptr


ptr


return NULL;

s


s


s


ptr


}

upto


2.61. Упрощенный аналог функции printf. А. Богатырев, 1992-95 - 113 - Си в UNIX /* * Машинно - независимый printf() (упрощенный вариант). * printf - Форматный Вывод. */ #include <stdio.h> #include <ctype.h> #include <varargs.h> #include <errno.h> #include <string.h> extern int errno; /* код системной ошибки, формат %m */ /* чтение значения числа */ #define GETN(n,fmt) \ n = 0; \ while(isdigit(*fmt)){ \ n = n*10 + (*fmt - '0'); \ fmt++; \ } void myprintf(fmt, va_alist) register char *fmt; va_dcl { va_list ap; char c, *s; int i; int width, /* минимальная ширина поля */ prec, /* макс. длина данного */ sign, /* выравнивание: 1 - вправо, -1 - влево */ zero, /* ширина поля начинается с 0 */ glong; /* требуется длинное целое */ va_start(ap); for(;;){ while((c = *fmt++) != '%'){ if( c == '\0' ) goto out; putchar(c); } sign = 1; zero = 0; glong = 0; if(*fmt == '-'){ sign = (-1); fmt++; } if(*fmt == '0'){ zero = 1; fmt++; } if(*fmt == '*'){ width = va_arg(ap, int); if(width < 0){ width = -width; sign = -sign; } fmt++; }else{ GETN(width, fmt); } width *= sign; if(*fmt == '.'){ if(*++fmt == '*'){ prec = va_arg(ap, int); fmt++; }else{ GETN(prec, fmt); } }else prec = (-1); /* произвольно */ if( *fmt == 'l' ){ glong = 1; fmt++; } А. Богатырев, 1992-95 - 114 - Си в UNIX switch(c = *fmt++){ case 'c': putchar(va_arg(ap, int)); break; case 's': prStr(width, prec, va_arg(ap, char *)); break; case 'm': prStr(width, prec, strerror(errno)); break; /* strerror преобразует код ошибки в строку-расшифровку */ case 'u': prUnsigned(width, glong ? va_arg(ap, unsigned long) : (unsigned long) va_arg(ap, unsigned int), 10 /* base */, zero); break; case 'd': prInteger(width, glong ? va_arg(ap, long) : (long) va_arg(ap, int), 10 /* base */, zero); break; case 'o': prUnsigned(width, glong ? va_arg(ap, unsigned long) : (unsigned long) va_arg(ap, unsigned int), 8 /* base */, zero); break; case 'x': prUnsigned(width, glong ? va_arg(ap, unsigned long) : (unsigned long) va_arg(ap, unsigned int), 16 /* base */, zero); break; case 'X': prUnsigned(width, glong ? va_arg(ap, unsigned long) : (unsigned long) va_arg(ap, unsigned int), -16 /* base */, zero); break; case 'b': prUnsigned(width, glong ? va_arg(ap, unsigned long) : (unsigned long) va_arg(ap, unsigned int), 2 /* base */, zero); break; case 'a': /* address */ prUnsigned(width, (long) (char *) va_arg(ap, char *), 16 /* base */, zero); break; case 'A': /* address */ prUnsigned(width, (long) (char *) va_arg(ap, char *), -16 /* base */, zero); break; case 'r': prRoman(width, prec, va_arg(ap, int)); break; case '%': putchar('%'); break; default: putchar(c); break; } } out: va_end(ap); } А. Богатырев, 1992-95 - 115 - Си в UNIX /* --------------------------------------------------------- */ int strnlen(s, maxlen) char *s; { register n; for( n=0; *s && n < maxlen; n++, s++ ); return n; } /* Печать строки */ static prStr(width, prec, s) char *s; { int ln; /* сколько символов выводить */ int toLeft = 0; /* к какому краю прижимать */ if(s == NULL){ pr( "(NULL)", 6); return; } /* Измерить длину и обрубить длинную строку. * Дело в том, что строка может не иметь \0 на конце, тогда * strlen(s) может привести к обращению в запрещенные адреса */ ln = (prec > 0 ? strnlen(s, prec) : strlen(s)); /* ширина поля */ if( ! width ) width = (prec > 0 ? prec : ln); if( width < 0){ width = -width; toLeft = 1; } if( width > ln){ /* дополнить поле пробелами */ if(toLeft){ pr(s, ln); prSpace(width - ln, ' '); } else { prSpace(width - ln, ' '); pr(s, ln); } } else { pr(s, ln); } } /* Печать строки длиной l */ static pr(s, ln) register char *s; register ln; { for( ; ln > 0 ; ln-- ) putchar( *s++ ); } /* Печать n символов c */ static prSpace(n, c) register n; char c;{ for( ; n > 0 ; n-- ) putchar( c ); } /* --------------------------------------------------------- */ static char *ds; /* Римские цифры */ static prRoman(w,p,n){ char bd[60]; ds = bd; if( n < 0 ){ n = -n; *ds++ = '-'; } prRdig(n,6); *ds = '\0'; prStr(w, p, bd); } А. Богатырев, 1992-95 - 116 - Си в UNIX static prRdig(n, d){ if( !n ) return; if( d ) prRdig( n/10, d - 2); tack(n%10, d); } static tack(n, d){ static char im[] = " MDCLXVI"; /* ..1000 500 100 50 10 5 1 */ if( !n ) return; if( 1 <= n && n <= 3 ){ repeat(n, im[d+2]); return; } if( n == 4 ) *ds++ = im[d+2]; if( n == 4 || n == 5 ){ *ds++ = im[d+1]; return; } if( 6 <= n && n <= 8 ){ *ds++ = im[d+1]; repeat(n - 5, im[d+2] ); return; } /* n == 9 */ *ds++ = im[d+2]; *ds++ = im[d]; } static repeat(n, c) char c; { while( n-- > 0 ) *ds++ = c; } /* --------------------------------------------------------- */ static char aChar = 'A'; static prInteger(w, n, base, zero) long n; { /* преобразуем число в строку */ char bd[128]; int neg = 0; /* < 0 */ if( n < 0 ){ neg = 1; n = -n; } if( base < 0 ){ base = -base; aChar = 'A'; } else { aChar = 'a'; } ds = bd; prUDig( n, base ); *ds = '\0'; /* Теперь печатаем строку */ prIntStr( bd, w, zero, neg ); } А. Богатырев, 1992-95 - 117 - Си в UNIX static prUnsigned(w, n, base, zero) unsigned long n; { char bd[128]; if( base < 0 ){ base = -base; aChar = 'A'; } else { aChar = 'a'; } ds = bd; prUDig( n, base ); *ds = '\0'; /* Теперь печатаем строку */ prIntStr( bd, w, zero, 0 ); } static prUDig( n, base ) unsigned long n; { unsigned long aSign; if((aSign = n/base ) > 0 ) prUDig( aSign, base ); aSign = n % base; *ds++ = (aSign < 10 ? '0' + aSign : aChar + (aSign - 10)); } static prIntStr( s, width, zero, neg ) char *s; { int ln; /* сколько символов выводить */ int toLeft = 0; /* к какому краю прижимать */ ln = strlen(s); /* длина строки s */ /* Ширина поля: вычислить, если не указано явно */ if( ! width ){ width = ln; /* ширина поля */ if( neg ) width++; /* 1 символ для минуса */ } if( width < 0 ){ width = -width; toLeft = 1; } if( ! neg ){ /* Положительное число */ if(width > ln){ if(toLeft){ pr(s, ln); prSpace(width - ln, ' '); } else { prSpace(width - ln, zero ? '0' : ' '); pr(s, ln); } } else { pr(s, ln); } }else{ /* Отрицательное число */ if(width > ln){ /* Надо заполнять оставшуюся часть поля */ width -- ; /* width содержит одну позицию для минуса */ if(toLeft){ putchar('-'); pr(s, ln); prSpace(width - ln, ' '); } else{ if( ! zero ){ prSpace(width - ln, ' '); putchar('-'); pr(s,ln); } else { putchar('-'); prSpace(width - ln, '0'); pr(s, ln); } } } else { putchar('-'); pr(s, ln); } } } А. Богатырев, 1992-95 - 118 - Си в UNIX /* --------------------------------------------------------- */ main(){ int i, n; static char s[] = "Hello, world!\n"; static char p[] = "Hello, world"; long t = 7654321L; myprintf( "%%abc%Y\n"); myprintf( "%s\n", "abs" ); myprintf( "%5s|\n", "abs" ); myprintf( "%-5s|\n", "abs" ); myprintf( "%5s|\n", "xyzXYZ" ); myprintf( "%-5s|\n", "xyzXYZ" ); myprintf( "%5.5s|\n", "xyzXYZ" ); myprintf( "%-5.5s|\n", "xyzXYZ" ); myprintf( "%r\n", 444 ); myprintf( "%r\n", 999 ); myprintf( "%r\n", 16 ); myprintf( "%r\n", 18 ); myprintf( "%r\n", 479 ); myprintf( "%d\n", 1234 ); myprintf( "%d\n", -1234 ); myprintf( "%ld\n", 97487483 ); myprintf( "%2d|%2d|\n", 1, -3 ); myprintf( "%-2d|%-2d|\n", 1, -3 ); myprintf( "%02d|%2d|\n", 1, -3 ); myprintf( "%-02d|%-2d|\n", 1, -3 ); myprintf( "%5d|\n", -12 ); myprintf( "%05d|\n", -12 ); myprintf( "%-5d|\n", -12 ); myprintf( "%-05d|\n", -12 ); for( i = -6; i < 6; i++ ) myprintf( "width=%2d|%0*d|%0*d|%*d|%*d|\n", i, i, 123, i, -123, i, 123, i, -123); myprintf( "%s at location %a\n", s, s ); myprintf( "%ld\n", t ); n = 1; t = 1L; for( i=0; i < 34; i++ ){ myprintf( "for %2d |%016b|%d|%u|\n\t |%032lb|%ld|%lu|\n", i, n, n, n, t, t, t ); n *= 2; t *= 2; } myprintf( "%8x %8X\n", 7777, 7777 ); myprintf( "|%s|\n", p ); myprintf( "|%10s|\n", p ); myprintf( "|%-10s|\n", p ); myprintf( "|%20s|\n", p ); myprintf( "|%-20s|\n", p ); myprintf( "|%20.10s|\n", p ); myprintf( "|%-20.10s|\n", p ); myprintf( "|%.10s|\n", p ); } А. Богатырев, 1992-95 - 119 - Си в UNIX Выдача этой программы: %abcY abs abs| abs | xyzXYZ| xyzXYZ| xyzXY| xyzXY| CDXLIV CMXCIX XVI XVIII CDLXXIX 1234 -1234 97487483 1|-3| 1 |-3| 01|-3| 1 |-3| -12| -0012| -12 | -12 | width=-6|123 |-123 |123 |-123 | width=-5|123 |-123 |123 |-123 | width=-4|123 |-123|123 |-123| width=-3|123|-123|123|-123| width=-2|123|-123|123|-123| width=-1|123|-123|123|-123| width= 0|123|-123|123|-123| width= 1|123|-123|123|-123| width= 2|123|-123|123|-123| width= 3|123|-123|123|-123| width= 4|0123|-123| 123|-123| width= 5|00123|-0123| 123| -123| Hello, world! at location 400980 7654321 for 0 |0000000000000001|1|1| |00000000000000000000000000000001|1|1| for 1 |0000000000000010|2|2| |00000000000000000000000000000010|2|2| for 2 |0000000000000100|4|4| |00000000000000000000000000000100|4|4| for 3 |0000000000001000|8|8| |00000000000000000000000000001000|8|8| for 4 |0000000000010000|16|16| |00000000000000000000000000010000|16|16| for 5 |0000000000100000|32|32| |00000000000000000000000000100000|32|32| for 6 |0000000001000000|64|64| |00000000000000000000000001000000|64|64| for 7 |0000000010000000|128|128| |00000000000000000000000010000000|128|128| for 8 |0000000100000000|256|256| |00000000000000000000000100000000|256|256| for 9 |0000001000000000|512|512| |00000000000000000000001000000000|512|512| for 10 |0000010000000000|1024|1024| А. Богатырев, 1992-95 - 120 - Си в UNIX |00000000000000000000010000000000|1024|1024| for 11 |0000100000000000|2048|2048| |00000000000000000000100000000000|2048|2048| for 12 |0001000000000000|4096|4096| |00000000000000000001000000000000|4096|4096| for 13 |0010000000000000|8192|8192| |00000000000000000010000000000000|8192|8192| for 14 |0100000000000000|16384|16384| |00000000000000000100000000000000|16384|16384| for 15 |1000000000000000|32768|32768| |00000000000000001000000000000000|32768|32768| for 16 |10000000000000000|65536|65536| |00000000000000010000000000000000|65536|65536| for 17 |100000000000000000|131072|131072| |00000000000000100000000000000000|131072|131072| for 18 |1000000000000000000|262144|262144| |00000000000001000000000000000000|262144|262144| for 19 |10000000000000000000|524288|524288| |00000000000010000000000000000000|524288|524288| for 20 |100000000000000000000|1048576|1048576| |00000000000100000000000000000000|1048576|1048576| for 21 |1000000000000000000000|2097152|2097152| |00000000001000000000000000000000|2097152|2097152| for 22 |10000000000000000000000|4194304|4194304| |00000000010000000000000000000000|4194304|4194304| for 23 |100000000000000000000000|8388608|8388608| |00000000100000000000000000000000|8388608|8388608| for 24 |1000000000000000000000000|16777216|16777216| |00000001000000000000000000000000|16777216|16777216| for 25 |10000000000000000000000000|33554432|33554432| |00000010000000000000000000000000|33554432|33554432| for 26 |100000000000000000000000000|67108864|67108864| |00000100000000000000000000000000|67108864|67108864| for 27 |1000000000000000000000000000|134217728|134217728| |00001000000000000000000000000000|134217728|134217728| for 28 |10000000000000000000000000000|268435456|268435456| |00010000000000000000000000000000|268435456|268435456| for 29 |100000000000000000000000000000|536870912|536870912| |00100000000000000000000000000000|536870912|536870912| for 30 |1000000000000000000000000000000|1073741824|1073741824| |01000000000000000000000000000000|1073741824|1073741824| for 31 |10000000000000000000000000000000|-2147483648|2147483648| |10000000000000000000000000000000|-2147483648|2147483648| for 32 |0000000000000000|0|0| |00000000000000000000000000000000|0|0| for 33 |0000000000000000|0|0| |00000000000000000000000000000000|0|0| 1e61 1E61 |Hello, world| |Hello, world| |Hello, world| | Hello, world| |Hello, world | | Hello, wor| |Hello, wor | |Hello, wor| 2.62. Рассмотрим программу суммирования векторов: А. Богатырев, 1992-95 - 121 - Си в UNIX int A[1024], B[1024], C[1024]; ... for(i=0; i < 1024; i++) C[i] = A[i] + B[i]; А почему бы не for(i=1024-1; i >=0 ; --i) ...; А почему бы не в произвольном порядке? foreach i in (0..1023) ...; Данный пример показывает, что некоторые операции обладают врожденным паралеллизмом, ведь все 1024 сложений можно было бы выполнять параллельно! Однако тупой компилятор будет складывать их именно в том порядке, в котором вы ему велели. Только самые сов- ременные компиляторы на многопроцессорных системах умеют автоматически распараллели- вать такие циклы. Сам язык Си не содержит средств указания параллельности (разве что снова - библиотеки и системные вызовы для этого). А. Богатырев, 1992-95 - 122 - Си в UNIX

3. Мобильность и машинная зависимость программ. Проблемы с русскими буквами.


Программа считается мобильной, если она без каких-либо изменений ее исходного текста (либо после настройки некоторых констант при помощи #define и #ifdef) трансли- руется и работает на разных типах машин (с разной разрядностью, системой команд, архитектурой, периферией) под управлением операционных систем одного семейства. Заме- тим, что мобильными могут быть только исходные тексты программ, объектные модули для разных процессоров, естественно, несовместимы! 3.1. Напишите программу, печатающую размер типов данных char, short, int, long,

sizeof


3.2. Составьте мобильную программу, выясняющую значения следующих величин для любой машины, на которой работает программа: 1) Наибольшее допустимое знаковое целое. 2) Наибольшее беззнаковое целое. 3) Наибольшее по абсолютной величине отрицательное целое.

x


e


ных чисел). 3.3. Составьте мобильную программу, выясняющую длину машинного слова ЭВМ (число битов в переменной типа int). Указание: для этого можно использовать битовые сдвиги. 3.4. Надо ли писать в своих программах определения #define EOF (-1) #define NULL ((char *) 0) /* или ((void *)0) */ Ответ: НЕТ. Во-первых, эти константы уже определены в include-файле, подключаемом по директиве

stdio


поэтому правильнее написать именно эту директиву. Во-вторых, это было бы просто неп- равильно: конкретные значения этих констант на данной машине (в данной реализации системы) могут быть другими! Чтобы придерживаться тех соглашений, которых придержива- ются все стандартные функции данной реализации, вы ДОЛЖНЫ брать эти константы из

stdio


По той же причине следует писать

fcntl


fd


вместо

fd


3.5. Почему может завершаться по защите памяти следующая программа? #include <sys/types.h> #include <stdio.h>

_


_


...

t


/* узнать текущее время в секундах с 1 Янв. 1970 г.*/ Ответ: дело в том, что прототип системного вызова time() это:

t


то есть аргумент должен быть указателем. Мы же вместо указателя написали в качестве А. Богатырев, 1992-95 - 123 - Си в UNIX аргумента 0 (типа int). На машине IBM PC AT 286 указатель - это 2 слова, а целое - одно. Недостающее слово будет взято из стека произвольно. В результате time() полу- чает в качестве аргумента не нулевой указатель, а мусор. Правильно будет написать:

t


либо (по определению time())

t


а еще более корректно так:

t


Мораль: везде, где требуется нулевой указатель, следует писать NULL (или явное приве- дение нуля к типу указателя), а не просто 0. 3.6. Найдите ошибку: void f(x, s) long x; char *s; { printf( "%ld %s\n", x, s ); } void main(){ f( 12, "hello" ); } Эта программа работает на IBM PC 386, но не работает на IBM PC 286. Ответ. Здесь возникает та же проблема, что и в примере про sin(12). Дело в том, что f требует первый аргумент типа long (4 байта на IBM PC 286), мы же передаем ей

x


s


вильным, программа обращается по несуществующему адресу и падает. На IBM PC 386 и int и long имеют длину 4 байта, поэтому там эта ошибка не проявляется! Опять-таки, это повод для использования прототипов функций (когда вы прочитаете про них - вернитесь к этому примеру!). Напишите прототип

x


и ошибки не будет. В данном примере мы использовали тип void, которого не сушествовало в ранних версиях языка Си. Этот тип означает, что функция не возвращает значения (то есть является "процедурой" в смысле языков Pascal или Algol). Если мы не напишем слово void перед f, то компилятор будет считать функцию f возвращающей целое (int), хотя эта функция ничего не возвращает (в ней нет оператора return). В большинстве случаев это не принесет вреда и программа будет работать. Но зато если мы напишем int x = f((long) 666, "good bye" );

x


ратор заставит компилятор сообщить об ошибке. Тип (void *) означает указатель на что угодно (понятно, что к такому указателю операции [], *, -> неприменимы: сначала следует явно привести указатель к содержа-

тип


функция динамического выделения памяти (memory allocation) malloc() (которая отводит в куче|= область памяти заказанного размера и выдает указатель на нее) имеет прототип: ____________________ |- В данной книге слова "указатель" и "ссылка" употребляются в одном и том же смысле. Если вы обратитесь к языку Си++, то обнаружите, что там эти два термина

pointer


____________________ А. Богатырев, 1992-95 - 124 - Си в UNIX

size


char *s = (char *) malloc( strlen(buf)+1 ); struct ST *p = (struct ST *) malloc( sizeof(struct ST)); /* или sizeof(*p) */ хотя раньше принято было char *malloc(); 3.7. Поговорим про оператор sizeof. Отметим распространенную ошибку, когда sizeof

трансляции


программы


a


b


Тогда

a


b


a


b


Если мы сделаем

b


a


то все равно

b


a


Таким образом sizeof выдает количество зарезервированной для переменной памяти (в байтах), независимо от текущего ее содержимого. Операция sizeof применима даже к выражениям. В этом случае она сообщает нам,

результата


ется


double f(){ printf( "Hi!\n"); return 12.34; } main(){ int x = 2; long y = 4; printf( "%u\n", sizeof(x + y + f())); } будет напечатано значение, совпадающее с sizeof(double), а фраза "Hi!" не будет напе- чатана. Когда оператор sizeof применяется к переменной (а не к имени типа), можно не писать круглые скобки:

x


3.8. Напишите объединение, в котором может храниться либо указатель, либо целое, либо действительное число. Ответ: union all{ char *s; int i; double f; ____________________

heap


ности, и предназначенная как раз для хранения динамически отведенных данных. А. Богатырев, 1992-95 - 125 - Си в UNIX } x; x.i = 12 ; printf("%d\n", x.i); x.f = 3.14; printf("%f\n", x.f); x.s = "Hi, there"; printf("%s\n", x.s); printf("int=%d double=%d (char *)=%d all=%d\n", sizeof(int), sizeof(double), sizeof(char *), sizeof x);

x


ров типов int, double, char *. Если вы хотите использовать одну и ту же переменную для хранения данных разных

мобильной


ями и никогда не привязываться к длине слова и представлению этих типов данных на конкретной ЗВМ! Раньше, когда программисты не думали о мобильности, они писали прог- раммы, где в одной переменой типа int хранили в зависимости от нужды то целые значе- ния, то указатели (это было на машинах PDP и VAX). Увы, такие программы оказались

sizeof


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

poor style


его! Сравните два примера, использующие два стиля программирования. Первый стиль не так плох, как только что описанный, но все же мы рекомендуем использовать только вто- рой: /* СТИЛЬ ПЕРВЫЙ: ЯВНЫЕ ПРЕОБРАЗОВАНИЯ ТИПОВ */ typedef void *PTR; /* универсальный указатель */ struct a { int x, y; PTR pa; } A; struct b { double u, v; PTR pb; } B; #define Aptr(p) ((struct a *)(p)) #define Bptr(p) ((struct b *)(p)) PTR ptr1, ptr2; main(){ ptr1 = &A; ptr2 = &B; Bptr(ptr2)->u = Aptr(ptr1)->x = 77; printf("%f %d\n", B.u, A.x); } /* СТИЛЬ ВТОРОЙ: ОБ'ЕДИНЕНИЕ */ /* предварительное объявление: */ extern struct a; extern struct b; /* универсальный тип данных: */ typedef union everything { int i; double d; char *s; struct a *ap; struct b *bp; } ALL; struct a { int x, y; ALL pa; } A; struct b { double u, v; ALL pb; } B; ALL ptr1, ptr2, zz; main(){ ptr1.ap = &A; ptr2.bp = &B; zz.i = 77; ptr2.bp->u = ptr1.ap->x = zz.i; printf("%f %d\n", B.u, A.x); } 3.9. Для выделения классов символов (например цифр), следует пользоваться макросами

ctype


if( '0' <= c && c <= '9' ) ... А. Богатырев, 1992-95 - 126 - Си в UNIX следует использовать

ctype


..... if(isdigit(c)) ... и вместо if((c >='a' && c <= 'z') || (c >= 'A' && c <= 'Z')) ... надо if(isalpha(c)) ... Дело в том, что сравнения < и > зависят от расположения букв в используемой коди- ровке. Но например, в кодировке КОИ-8 русские буквы расположены НЕ в алфавитном порядке. Вследствие этого, если для char c1, c2; c1 < c2

c1


кое сравнение требует специальной перекодировки букв к "упорядоченной" кодировке. Аналогично, сравнение if( c >= 'а' && c <= 'я' )

ctype


зуют массив флагов для каждой буквы кодировки, и потому не зависят от порядка букв (и работают быстрее). Идея реализации такова: extern unsigned char _ctype[]; /*массив флагов*/ #define US(c) (sizeof(c)==sizeof(char)?((c)&0xFF):(c)) /* подавление расширения знакового бита */ /* Ф Л А Г И */ #define _U 01 /* uppercase: большая буква */ #define _L 02 /* lowercase: малая буква */ #define _N 04 /* number: цифра */ #define _S 010 /* space: пробел */ /* ... есть и другие флаги ... */ #define isalpha(c) ((_ctype+1)[US(c)] & (_U|_L) ) #define isupper(c) ((_ctype+1)[US(c)] & _U ) #define islower(c) ((_ctype+1)[US(c)] & _L ) #define isdigit(c) ((_ctype+1)[US(c)] & _N ) #define isalnum(c) ((_ctype+1)[US(c)] & (_U|_L|_N)) #define tolower(c) ((c) + 'a' - 'A' ) #define toupper(c) ((c) + 'A' - 'a' )

_


хранится в стандартной библиотеке Си. Вот его фрагмент:

_


/* EOF код (-1) */ 0, ... /* '1' код 061 0x31 */ _N, ... /* 'A' код 0101 0x41 */ _U, ... /* 'a' код 0141 0x61 */ _L, ... }; А. Богатырев, 1992-95 - 127 - Си в UNIX Выигрыш в скорости получается вот почему: если мы определим|- #define isalpha(c) (((c) >= 'a' && (c) <= 'z') || \ ((c) >= 'A' && (c) <= 'Z'))

ctype


(как определено выше) - мы используем только две операции: индексацию и проверку

_


ции, и поэтому не вызывают генерации машинных команд. Определенные выше toupper и tolower работают верно лишь в кодировке ASCII|=, в которой все латинские буквы расположены подряд и по алфавиту. Обратите внимание, что tolower имеет смысл применять только к большим буквам, а toupper - только к малень- ким: if( isupper(c) ) c = tolower(c); Существует еще черезвычайно полезный макрос isspace(c), который можно было бы опреде- лить как #define isspace(c) (c==' ' ||c=='\t'||c=='\f'|| \ c=='\n'||c=='\r') или #define isspace(c) (strchr(" \t\f\n\r",(c)) != NULL)

_


словами


текста.

c


c


c


зится, но терминал произведет некоторое действие, вроде очистки экрана или перемеще- ния курсора в каком-то направлении. Они нужны, как правило, для отображения управля- ющих ("контроловских") символов в специальном печатном виде, вроде ^A для кода '\01'.

ctype


лексикографического сравнения букв и строк. Указание: пусть буквы имеют такие коды (это не соответствует реальности!): буква: а б в г д е код: 1 4 2 5 3 0 нужно: 0 1 2 3 4 5 Тогда идея функции Ctou перекодировки к упорядоченному алфавиту такова: unsigned char UU[] = { 5, 0, 2, 4, 1, 3 }; /* в действительности - 256 элементов: UU[256] */ Ctou(c) unsigned char c; { return UU[c]; } int strcmp(s1, s2) char *s1, *s2; { /* Проигнорировать совпадающие начала строк */ while(*s1 && *s1 == *s2) s1++, s2++; /* Вернуть разность [не]совпавших символов */ return Ctou(*s1) - Ctou(*s2); ____________________ |- Обратите внимание, что символ \ в конце строки макроопределения позволяет про- должить макрос на следующей строке, поэтому макрос может состоять из многих строк. |= ASCII - American Standard Code for Information Interchange - наиболее распрост- раненная в мире кодировка (Американский стандарт). А. Богатырев, 1992-95 - 128 - Си в UNIX }

UU


3.10. В современных UNIX-ах с поддержкой различных языков таблица ctype загружается из некоторых системных файлов - для каждого языка своя. Для какого языка - выбира- ется по содержимому переменной окружения LANG. Если переменная не задана - использу- ется значение "C", английский язык. Загрузка таблиц должна происходить явно, вызовом ...

locale


... main(){

_


... все остальное ... } 3.11. Вернемся к нашей любимой проблеме со знаковым битом у типа char. #include <stdio.h> #include <locale.h> #include <ctype.h> int main(int ac, char *av[]){ char c; char *string = "абвгдежзиклмноп"; setlocale(LC_ALL, ""); for(;c = *string;string++){ #ifdef DEBUG printf("%c %d %d\n", *string, *string, c); #endif if(isprint(c)) printf("%c - печатный символ\n", c); } return 0; } Эта программа неожиданно печатает % a.out в - печатный символ з - печатный символ И все. В чем дело??? Рассмотрим к примеру символ 'г'. Его код '\307'. В операторе c = *string;

c


usr


#define isprint(c) ((_ctype + 1)[c] & (_P|_U|_L|_N|_B))

c


индекс приводится к типу int (signed). Откуда теперь извлекается значение флагов -

_


А. Богатырев, 1992-95 - 129 - Си в UNIX Проблему решает либо использование isprint(c & 0xFF) либо isprint((unsigned char) c) либо объявление в нашем примере unsigned char c; В первом случае мы явно приводим signed к unsigned битовой операцией, обнуляя лишние биты. Во втором и третьем - unsigned char расширяется в unsigned int, который оста- нется положительным. Вероятно, второй путь предпочтительнее.

отрица-


тельные


char c = 'г'; int x[256]; ...x[c]... /* индекс < 0 */ ...x['г']... Поэтому байтовые индексы должны быть либо unsigned char, либо & 0xFF. Как в следую- щем примере: /* Программа преобразования символов в файле: транслитерация tr abcd prst заменяет строки xxxxdbcaxxxx -> xxxxtrspxxxx По мотивам книги М.Дансмура и Г.Дейвиса. */ #include <stdio.h> #define ASCII 256 /* число букв в алфавите ASCII */ /* BUFSIZ определено в stdio.h */ char mt[ ASCII ]; /* таблица перекодировки */ /* начальная разметка таблицы */ void mtinit(){ register int i; for( i=0; i < ASCII; i++ ) mt[i] = (char) i; } А. Богатырев, 1992-95 - 130 - Си в UNIX int main(int argc, char *argv[]) { register char *tin, *tout; /* unsigned char */ char buffer[ BUFSIZ ]; if( argc != 3 ){ fprintf( stderr, "Вызов: %s что наЧто\n", argv[0] ); return(1); } tin = argv[1]; tout = argv[2]; if( strlen(tin) != strlen(tout)){ fprintf( stderr, "строки разной длины\n" ); return(2); } mtinit(); do{ mt[ (*tin++) & 0xFF ] = *tout++; /* *tin - имеет тип char. * & 0xFF подавляет расширение знака */ } while( *tin ); tout = mt; while( fgets( buffer, BUFSIZ, stdin ) != NULL ){ for( tin = buffer; *tin; tin++ ) *tin = tout[ *tin & 0xFF ]; fputs( buffer, stdout ); } return(0); } 3.13. int main(int ac, char *av[]){ char c = 'г'; if('a' <= c && c < 256) printf("Это одна буква.\n"); return 0; } Увы, эта программа не печатает НИЧЕГО. Просто потому, что signed char в сравнении (в

отрицательна


c


c


вать две переменные типа char. Нужно принимать предохранительные меры по подавлению расширения знака: if((ch1 & 0xFF) < (ch2 & 0xFF))...; Для unsigned char такой проблемы не будет. 3.14. Почему неверно: А. Богатырев, 1992-95 - 131 - Си в UNIX #include <stdio.h> main(){ char c; while((c = getchar()) != EOF) putchar(c); }

c


Русская буква "Большой твердый знак" в кодировке КОИ-8 имеет код '\377' (0xFF). Если мы подадим на вход этой программе эту букву, то в сравнении signed char со зна-

c


знака. 0xFF превратится в (-1), что означает, что поступил символ EOF. Сюрприз!!! Посему данная программа будет делать вид, что в любом файле с большим русским твердым знаком после этого знака (и включая его) дальше ничего нет. Что есть досадное заблуж- дение.

c


3.15. Изучите поведение программы #define TYPE char void f(TYPE c){ if(c == 'й') printf("Это буква й\n"); printf("c=%c c=\\%03o c=%03d c=0x%0X\n", c, c, c, c); } int main(){ f('г'); f('й'); f('z'); f('Z'); return 0; } когда TYPE определено как char, unsigned char, int. Объясните поведение. Выдачи в этих трех случаях таковы (int == 32 бита): c=г c=\37777777707 c=-57 c=0xFFFFFFC7 Это буква й c=й c=\37777777712 c=-54 c=0xFFFFFFCA c=z c=\172 c=122 c=0x7A c=Z c=\132 c=090 c=0x5A c=г c=\307 c=199 c=0xC7 c=й c=\312 c=202 c=0xCA c=z c=\172 c=122 c=0x7A c=Z c=\132 c=090 c=0x5A и снова как 1 случай. Рассмотрите альтернативу if(c == (unsigned char) 'й') printf("Это буква й\n");

c


фраза 'Это буква й' не печатается ни с типом char, ни с типом int, поскольку в срав-

c


Слева получается отрицательное число! В таких случаях вновь следует писать if((unsigned char)c == (unsigned char)'й') printf("Это буква й\n"); А. Богатырев, 1992-95 - 132 - Си в UNIX 3.16. Обычно возникают проблемы при написании функций с переменным числом аргумен-

_


от соглашений о вызовах функций на данной машине, и использующих эти макросы специ- альных функций. Есть два стиля оформления таких программ: с использованием

varargs


функции poly(). Для иллюстрации второго приведем пример функции трассировки, записы- вающей собщение в файл:

stdio


stdarg


void trace(char *fmt, ...) {

_


static FILE *fp = NULL; if(fp == NULL){ if((fp = fopen("TRACE", "w")) == NULL) return; }

_


/* второй аргумент: арг-т после которого * в заголовке функции идет ... */

args


fflush(fp); /* вытолкнуть сообщение в файл */

_


} main(){ trace( "%s\n", "Go home."); trace( "%d %d\n", 12, 34); } Символ `...' (троеточие) в заголовке функции обозначает переменный (возможно пустой)

последним


аргументами функции.

_


type


написана через функцию vsprintf (в действительности обе функции - стандартные):

fp


buffer


buffer


buffer


}

str


str


подобном приведенному. В конец сформированной строки sprintf записывает '\0'. 3.17. Напишите функцию printf, понимающую форматы %c (буква), %d (целое), %o (вось- меричное), %x (шестнадцатеричное), %b (двоичное), %r (римское), %s (строка), %ld (длинное целое). Ответ смотри в приложении. 3.18. Для того, чтобы один и тот же исходный текст программы транслировался на раз- ных машинах (в разных системах), приходится выделять в программе системно-зависимые части. Такие части должны по-разному выглядеть на разных машинах, поэтому их оформ-

условно компилируемых


XX


... вариант1 #else ... вариант2 #endif А. Богатырев, 1992-95 - 133 - Си в UNIX

XX


определен

XX


вариант1


вариант2


вариант1


else if:

макро1


...

макро2


... #else ... #endif Определить макрос можно не только при помощи #define, но и при помощи ключа компиля- тора, так

XX file


file


XX


А для программы main(){

XX


XX


#else printf( "XX undefined\n"); #endif } ключ

XX


эквивалентен заданию директивы

XX


Что будет, если совсем не задать ключ -D в данном примере? Этот прием используется в частности в тех случаях, когда какие-то стандартные типы или функции в данной системе носят другие названия:

void


strchr


автоматически


компиляторы в UNIX неявно подставляют один из ключей (или несколько сразу):

M


M


unix


M


SVR4


USG


... бывают и другие А. Богатырев, 1992-95 - 134 - Си в UNIX Это позволяет программе "узнать", что ее компилируют для системы UNIX. Более под- робно про это написано в документации по команде cc. 3.19. Оператор #ifdef применяется в include-файлах, чтобы исключить повторное вклю-

aa


aa


#include "cc.h" #include "cc.h" typedef unsigned long ulong; typedef int cnt_t;

cc


cc


... #include "aa.h" struct II { int x, y; }; #include "bb.h" ... main(){ ... }

cc


00


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

cc


#ifndef _CC_H # define _CC_H /* определяется при первом включении */ ... struct II { int x, y; }; ... #endif /* _CC_H */

пустое место


sys


_


3.20. Любой макрос можно отменить, написав директиву

имяМакро


Пример: #include <stdio.h>

M


M


main() { putchar('!');

putchar


putchar


putchar('?');

M


M


printf("Это UNIX\n"); #else printf("Это не UNIX\n"); #endif /* UNIX */ }

пере


stdio


Директива #if, использованная нами, является расширением оператора #ifdef и подставляет текст если выполнено указанное условие: А. Богатырев, 1992-95 - 135 - Си в UNIX

MACRO


MACRO


VALUE


VALUE


больше 15 (==, !=, <=, ...) */ #if COND1 || COND2 /* если верно любое из условий */ #if COND1 && COND2 /* если верны оба условия */ Директива #if допускает использование в качестве аргумента довольно сложных выраже- ний, вроде

M1


3.21. Условная компиляция может использоваться для трассировки программ: #ifdef DEBUG # define DEBUGF(body) \ { \ body; \ } #else # define DEBUGF(body) #endif int f(int x){ return x*x; } int main(int ac, char *av[]){ int x = 21; DEBUGF(x = f(x); printf("%s equals to %d\n", "x", x)); printf("x=%d\n", x); } При компиляции

file


в выходном потоке программы будет присутствовать отладочная выдача. При компиляции

DEBUG


3.22. В языке C++ (развитие языка Си) слова class, delete, friend, new, operator, overload, template, public, private, protected, this, virtual являются зарезервиро- ванными (ключевыми). Это может вызвать небольшую проблему при переносе текста прог- раммы на Си в систему программирования C++, например:

termio


...

fd


old


fd


new


new


fd


... Строки, содержащие имя переменной (или функции) new, окажутся неправильными в C++. Проще всего эта проблема решается переименованием переменной (или функции). Чтобы не производить правки во всем тексте, достаточно переопределить имя при помощи директивы define: А. Богатырев, 1992-95 - 136 - Си в UNIX

_


... старый текст ... #undef new

каждой


прототип


опускать прототипы для многих функций, особенно возвращающих значения типов int или void). А. Богатырев, 1992-95 - 137 - Си в UNIX

4. Работа с файлами.


Файлы


ном диске), предназначенные для: - хранения данных, превосходящих по объему память компьютера (меньше, разумеется, тоже можно); - долговременного хранения информации (она сохраняется при выключении машины). В UNIX и в MS DOS файлы не имеют предопределенной структуры и представляют собой

массивы байт


сами


от обычных массивов тем, что - они могут изменять свой размер; - обращение к элементам этих массивов производится не при помощи операции индекса- ции [], а при помощи специальных системных вызовов и функций; - доступ к элементам файла происходит в так называемой "позиции чтения/записи", которая автоматически продвигается при операциях чтения/записи, т.е. файл прос- матривается последовательно. Есть, правда, функции для произвольного изменения этой позиции.

имена


простых файлов. Об этом и о системе именования файлов прочитайте в документации по UNIX.

открыть


новить связь между именем файла и некоторой переменной в программе. При открытии

открытый


файл


f


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

RWptr


чтения/записи;

f


режимы открытия файла: чтение, запись, чтение и запись, некоторые дополнительные флаги;

f


расположение файла на диске (в UNIX - в виде ссылки на I-узел файла|-); и кое-что еще. У каждого процесса имеется таблица открытых им файлов - это массив ссылок на упомянутые "связующие" структуры|=. При открытии файла в этой таблице ищется ____________________ |- I-узел (I-node, индексный узел) - своеобразный "паспорт", который есть у каждого файла (в том числе и каталога). В нем содержатся:

di


di


di


- время создания и последней модификации

_


di


di


и.т.п. Содержимое некоторых полей этого паспорта можно узнать вызовом stat(). Все I-узлы собраны в единую область в начале файловой системы - так называемый I-файл. Все I- узлы пронумерованы, начиная с номера 1. Корневой каталог (файл с именем "/") как правило имеет I-узел номер 2. |= У каждого процесса в UNIX также есть свой "паспорт". Часть этого паспорта нахо- дится в таблице процессов в ядре ОС, а часть - "приклеена" к самому процессу, однако не доступна из программы непосредственно. Эта вторая часть паспорта носит название "u-area" или структура user. В нее, в частности, входят таблица открытых процессом файлов А. Богатырев, 1992-95 - 138 - Си в UNIX свободная ячейка, в нее заносится ссылка на структуру "открытый файл" в ядре, и ИНДЕКС этой ячейки выдается в вашу программу в виде целого числа - так называемого

дескриптора файла


закрытии


тается свободной, т.е. связь программы и файла разрывается.

локальными


открыли один и тот же файл - дескрипторы этого файла в каждой из них не обязательно совпадут (хотя и могут). Обратно: одинаковые дескрипторы (номера) в разных програм- мах не обязательно обозначают один и тот же файл. Следует учесть и еще одну вещь:

несколько


раз. При этом будет создано несколько "связующих" структур (по одной для каждого открытия); каждая из них будет иметь СВОЙ указатель чтения/записи. Возможна и ситуа- ция, когда несколько дескрипторов ссылаются к одной структуре - смотри ниже описание вызова dup2.

fd u


0 ## -------------

f


f


f


f


процесс1 | ------!------ | | ! V 0 ## | struct file ! struct inode 1 ## | ------------- ! -------------

f


f


f


f


-------!----- *=========* | файла ! ! V

i


@@@@@@@@@@@!@@@@@@@@@@@@@@@@@@@@@!@@@@@@ файл на диске /* открыть файл */

fd


... /* какие-то операции с файлом */

fd


как


fcntl


_


_


_


_


открытием для записи, "добавление" в файл:

_


Если файл еще не существовал, то его нельзя открыть: open вернет значение (-1), ____________________

u


ссылка на I-узел текущего каталога

u


а также ссылка на часть паспорта в таблице процессов

u


А. Богатырев, 1992-95 - 139 - Си в UNIX сигнализирующее об ошибке. В этом случае файл надо создать:

fd


fd


существовал, creat опустошает его, т.е. уничтожает его прежнее содержимое и делает

Коды


Это число задает битовую шкалу из 9и бит, соответствующих строке биты: 876 543 210 rwx rwx rwx r - можно читать файл w - можно записывать в файл x - можно выполнять программу из этого файла Первая группа - эта права владельца файла, вторая - членов его группы, третяя - всех прочих. Эти коды для владельца файла имеют еще и мнемонические имена (используемые в вызове stat): #include <sys/stat.h> /* Там определено: */

_


_


_


Подробности - в руководствах по системе UNIX. Отметим в частности, что open() может

fd


errno


errno


UNIX"). Вызов creat - это просто разновидность вызова open в форме

fd


_


_


означает, что если файл уже существует, то он должен быть опустошен при откры- тии. Коды доступа и владелец не изменяются.

_


означает, что файл должен быть создан, если его не было (без этого флага файл не

fd


коды


_


Существует также флаг

_


_


errno


____________________ |- Заметим, что на самом деле коды доступа у нового файла будут равны

di


u


u


(вызов выдает прежнее значение маски) и в дальнейшем наследуется всеми потомками дан- ного процесса (она хранится в u-area процесса). Эта маска позволяет запретить доступ

всех


коды доступа, например umask(0077); /* ???------ */ делает значащими только первые 3 бита кодов доступа (для владельца файла). Остальные биты будут равны нулю. Все это относится и к созданию каталогов вызовом mkdir. А. Богатырев, 1992-95 - 140 - Си в UNIX

_


уже существующие файлы от уничтожения. Файл удаляется при помощи

имя


У каждой программы по умолчанию открыты три первых дескриптора, обычно связанные 0 - с клавиатурой (для чтения) 1 - с дисплеем (выдача результатов) 2 - с дисплеем (выдача сообщений об ошибках)

fd


рыт) - ничего не происходит. Часто используется такая метафора: если представлять себе файлы как книжки

открытие


это выбор блокнота по заглавию на его обложке и открытие обложки (на первой стра- нице). Теперь можно читать записи, дописывать, вычеркивать и править записи в сере-

блокам


с книжками - каталогу. 4.2. Напишите программу, которая копирует содержимое одного файла в другой (новый) файл. При этом используйте системные вызовы чтения и записи read и write. Эти сис- вызовы пересылают массивы байт из памяти в файл и наоборот. Но любую переменную можно рассматривать как массив байт, если забыть о структуре данных в переменной! Читайте и записывайте файлы большими кусками, кратными 512 байтам. Это уменьшит число обращений к диску. Схема:

buffer


...

n


fd


Приведем несколько примеров использования write: char c = 'a'; int i = 13, j = 15; char s[20] = "foobar"; char p[] = "FOOBAR"; struct { int x, y; } a = { 666, 999 }; /* создаем файл с доступом rw-r--r-- */ int fd = creat("aFile", 0644); write(fd, &c, 1); write(fd, &i, sizeof i); write(fd, &j, sizeof(int)); write(fd, s, strlen(s)); write(fd, &a, sizeof a); write(fd, p, sizeof(p) - 1); close(fd); Обратите внимание на такие моменты: - При использовании write() и read() надо передавать АДРЕС данного, которое мы хотим записать в файл (места, куда мы хотим прочитать данные из файла). - Операции read и write возвращают число действительно прочитанных/записанных байт (при записи оно может быть меньше указанного нами, если на диске не хватает места; при чтении - если от позиции чтения до конца файла содержится меньше информации, чем мы затребовали). - Операции read/write продвигают указатель чтения/записи

RWptr


RWptr


А. Богатырев, 1992-95 - 141 - Си в UNIX если надо автоматически увеличивает свой размер. При чтении - если мы достигнем конца файла, то read будет возвращать "прочитано 0 байт" (т.е. при чтении указа- тель чтения не может стать больше размера файла).

сколькоБайт


n


n


упрощенные


устройства


быми свойствами): 4.2.1. m = write(fd, addr, n);

fd


n


fd


RWptr


RWptr


заполнить нулями байты файла в интервале

fd


скопировать байты из памяти процесса в файл

fd


отводя на диске новые блоки, если надо

RWptr


RWptr


длина


n


4.2.2. m = read(fd, addr, n);

fd


RWptr


m


скопировать байты из файла в память процесса

addr


RWptr


m


4.3. Найдите ошибки в фрагменте программы: #define STDOUT 1 /* дескриптор стандартного вывода */ int i; static char s[20] = "hi\n"; char c = '\n'; struct a{ int x,y; char ss[5]; } po; scanf( "%d%d%d%s%s", i, po.x, po.y, s, po.ss); write( STDOUT, s, strlen(s)); write( STDOUT, c, 1 ); /* записать 1 байт */

i


po


s


адресом


c


значения


не может


fmt


А. Богатырев, 1992-95 - 142 - Си в UNIX

не определенных


на собственную внимательность! 4.4. Как по дескриптору файла узнать, открыт он на чтение, запись, чтение и запись одновременно? Вот два варианта решения:

fcntl


stdio


sys


errno


fd


flags


flags


fd


flags


flags


_


_


_


default: return NULL; } }

fd


errno


r


errno


errno


errno


errno


w


w


r


"closed"; } main(){

i


i


s


i


} } Константа NOFILE означает максимальное число одновременно открытых файлов для одного процесса (это размер таблицы открытых процессом файлов, таблицы дескрипторов). Изу- чите описание системного вызова fcntl (file control).

rename


темные вызовы link() и unlink(). Ответ: А. Богатырев, 1992-95 - 143 - Си в UNIX

from


from


to


{

to


from


return (-1);

from


return 0; /* OK */ } Вызов

существующее


создает файлу альтернативное имя - в UNIX файл может иметь несколько имен: так каждый каталог имеет какое-то имя в родительском каталоге, а также имя "." в себе самом. Каталог же, содержащий подкаталоги, имеет некоторое имя в своем родительском ката- логе, имя "." в себе самом, и по одному имени ".." в каждом из своих подкаталогов.

новое


другой


имя


удаляет имя файла. Если файл больше не имеет имен - он уничтожается. Здесь есть одна тонкость: рассмотрим фрагмент

fd


close(creat("/tmp/xyz", 0644)); /*Создать пустой файл*/

fd


unlink("/tmp/xyz"); ...

fd


Первый оператор создает пустой файл. Затем мы открываем файл и уничтожаем его единственное имя. Но поскольку есть программа, открывшая этот файл, он не удаляется

безымянным


Как только файл закрывается - он будет уничтожен системой (как не имеющий имен). Такой трюк используется для создания временных рабочих файлов.

каталог


файла


В современных версиях UNIX есть системный вызов rename, который делает то же самое, что и написанная нами одноименная функция. 4.6. Существование альтернативных имен у файла позволяет нам решить некоторые проб- лемы, которые могут возникнуть при использовании чужой программы, от которой нет исходного текста (которую нельзя поправить). Пусть программа выдает некоторую инфор-

zz


менты программы): /* Эта программа компилируется в a.out */ main(){ int fd = creat("zz.out", 0644); write(fd, "It's me\n", 8); } Мы же хотим получить вывод на терминал, а не в файл. Очевидно, мы должны сделать файл

zz


дой ln:

zz


$ a.out

zz


или программно: А. Богатырев, 1992-95 - 144 - Си в UNIX /* Эта программа компилируется в start */ /* и вызывается вместо a.out */ #include <stdio.h> main(){

zz


dev


a


else wait(NULL);

zz


} (про fork, exec, wait смотри в главе про UNIX).

usr


про функцию system() сноску через несколько страниц): main(){

xx


}

usr


альтернативное имя этому редактору:

usr


той же


где содержится исходное имя. В семействе BSD |- это ограничение можно обойти, создав "символьную ссылку" вызовом

link


Символьная ссылка - это файл, содержащий имя другого файла (или каталога). Система не производит автоматический подсчет числа таких ссылок, поэтому возможны "висячие" ссылки - указывающие на уже удаленный файл. Прочесть содержимое файла-ссылки можно системным вызовом

linkbuf


len


linkbuf


Системный вызов stat автоматически разыменовывает символьные ссылки и выдает информа- цию про указуемый файл. Системный вызов lstat (аналог stat за исключением названия)

_


имеют никакого значения для системы, существенны только коды доступа самого указуе- мого файла. Еще раз: символьные ссылки удобны для указания файлов и каталогов на другом диске. Пусть у вас не помещается на диск каталог /opt/wawa. Вы можете разместить каталог wawa на диске USR: /usr/wawa. После чего создать символьную ссылку из /opt:

usr


чтобы программы видели этот каталог под его прежним именем /opt/wawa. Еще раз: hard link - то, что создается системным вызовом link, имеет тот же I-node (индексный узел, паспорт), что и исходный файл. Это просто альтернативное имя файла, учитываемое

di


____________________ |- BSD - семейство UNIX-ов из University of California, Berkley. Berkley Software Distribution. А. Богатырев, 1992-95 - 145 - Си в UNIX symbolic link - создается вызовом symlink. Это отдельный самостоятельный файл, с собственным I-node. Правда, коды доступа к этому файлу не играют никакой роли; значимы только коды доступа указуемого файла. 4.7. Напишите программу, которая находит в файле символ @ и выдает файл с этого места дважды. Указание: для запоминания позиции в файле используйте вызов lseek() - позиционирование указателя чтения/записи: long offset, lseek(); ... /* Узнать текущую позицию чтения/записи: * сдвиг на 0 от текущей позиции. lseek вернет новую * позицию указателя (в байтах от начала файла). */ offset = lseek(fd, 0L, 1); /* ftell(fp) */ А для возврата в эту точку: lseek(fd, offset, 0); /* fseek(fp, offset, 0) */ По поводу lseek надо помнить такие вещи:

fd


offset


whence


RWptr


RWptr


RWptr


whence


stdio


_


_


_


виртуальная


подвода магнитных головок и вообще обращения к диску она не вызывает. Реальное движение головок к нужному месту диска произойдет только при операциях

дешевая


RWptr


начала файла


чение, то вы должны предварительно описать lseek как функцию, возвращающую длин- ное целое: long lseek();

offset


- Если поставить указатель за конец файла (это допустимо!), то операция записи write() сначала заполнит байтом '\0' все пространство от конца файла до позиции указателя; операция read() при попытке чтения из-за конца файла вернет "прочи- тано 0 байт". Попытка поставить указатель перед началом файла вызовет ошибку. - Вызов lseek() неприменим к pipe и FIFO-файлам, поэтому попытка сдвинуться на 0 байт выдаст ошибку: /* это стандартная функция */

fd


errno


fd


}

fd


А. Богатырев, 1992-95 - 146 - Си в UNIX 4.8. Каков будет эффект следующей программы?

fd


открытый на запись, с доступом rw-r--r-- */

fd


fd


fd


fd


автоматически


записываем информацию за прежним концом файла. Это вызывает отведение места на диске для хранения новых данных (порциями, называемыми блоками - размером от 1/2 до 8 Кб в разных версиях). Таким образом, размер файла ограничен только наличием свободных блоков на диске. В нашем примере получится файл длиной 1024003 байта. Будет ли он занимать на диске 1001 блок (по 1 Кб)? В системе UNIX - нет! Вот кое-что про механику выделения блоков: - Блоки располагаются на диске не обязательно подряд - у каждого файла есть специ- альным образом организованная таблица адресов его блоков. - Последний блок файла может быть занят не целиком (если длина файла не кратна

целое


BSD, где блок может делиться на фрагменты, принадлежащие разным файлам). Опера- ционная система в каждый момент времени знает длину файла с точностью до одного байта и не позволяет нам "заглядывать" в остаток блока, пока при своем "росте" файл не займет эти байты.

после операции записи


В нашем примере: при создании файла его размер 0, и ему выделено 0 блоков. При первой записи файлу будет выделен один блок (логический блок номер 0 для файла) и в его начало запишется "begin". Длина файла станет равна 5 (остаток блока - 1019 байт - не используется и файлу логически не принадлежит!). Затем lseek поставит указатель записи далеко за конец файла и write запишет в 1000-ый блок слово "end". 1000-ый блок будет выделен на диске. В этот момент у файла "возникнут" и все промежуточные блоки

не будут


(в таблице блоков файла это обозначается адресом 0)! При чтении из них будут

дырка


байта, но на диске занимает всего 2 блока (на самом деле чуть больше, т.к. часть таблицы блоков файла тоже находится в специальных блоках файла). Блок из "дырки" станет реальным, если в него что-нибудь записать. Будьте готовы к тому, что "размер файла" (который, кстати, можно узнать систем- ным вызовом stat) - это в UNIX не то же самое, что "место, занимаемое файлом на диске". 4.9. Найдите ошибки:

fp


...

fp


fp


Ответ: используется системный вызов open() вместо функции fopen(); а также close вместо fclose, а их форматы (и результат) различаются! Следует четко различать две существующие в Си модели обмена с файлами: через системные вызовы: open, creat, close, read, write, lseek; и через библиотеку буферизованного обмена stdio: fopen, fclose, fread, fwrite, fseek, getchar, putchar, printf, и.т.д. В первой из них обра-

fd


fp


хотя второй является просто надстройкой над первым. Тем не менее, лучше их не смеши- вать. А. Богатырев, 1992-95 - 147 - Си в UNIX 4.10. Доступ к диску (чтение/запись) гораздо (на несколько порядков) медленнее, чем доступ к данным в оперативной памяти. Кроме того, если мы читаем или записываем файл при помощи системных вызовов маленькими порциями (по 1-10 символов) char c; while( read(0, &c, 1)) ... ; /* 0 - стандартный ввод */ то мы проигрываем еще в одном: каждый системный вызов - это обращение к ядру операци-

большая


работа (смотри главу "Взаимодействие с UNIX"). При этом накладные расходы на такое посимвольное чтение файла могут значительно превысить полезную работу. Еще одной проблемой является то, что системные вызовы работают с файлом как с неструктурированным массивом байт; тогда как человеку часто удобнее представлять, что файл поделен на строки, содержащие читабельный текст, состоящий лишь из обычных печатных символов (текстовый файл). Для решения этих двух проблем была построена специальная библиотека функций,

standard input


library


над системными вызовами (т.к. в конце концов все ее функции время от времени обраща-

реже


stdio


нием форматов данных и констант, используемых этой библиотекой. Библиотеку stdio можно назвать библиотекой буферизованного обмена, а также биб- лиотекой работы с текстовыми файлами (т.е. имеющими разделение на строки), поскольку для оптимизации обменов с диском (для уменьшения числа обращений к нему и тем самым

буферизацию


доставляет несколько функций для работы со строчно-организованными файлами. Связь с файлом в этой модели обмена осуществляется уже не при помощи целого

дескриптора файла


указателем на файл


pointer


fd


- указатель на буфер, размещенный в памяти программы; - указатель на текущее место в буфере, откуда надо выдать или куда записать оче- редной символ; этот указатель продвигается при каждом вызове getc или putc; - счетчик оставшихся в буфере символов (при чтении) или свободного места (при записи); - режимы открытия файла (чтение/запись/чтение+запись) и текущее состояние файла. Одно из состояний - при чтении файла был достигнут его конец|=; - способ буферизации; Предусмотрено несколько стандартных структур FILE, указатели на которые называются

stdin


ввод, стандартный вывод, стандартный вывод ошибок). Напомним, что эти каналы открыты неявно (автоматически) и, если не перенаправлены, связаны с вводом с клавиатуры и выводом на терминал. Буфер в оперативной памяти нашей программы создается (функцией malloc) при отк- рытии файла при помощи функции fopen(). После открытия файла все операции обмена с файлом происходят не по 1 байту, а большими порциями размером с буфер - обычно по 512 байт (константа BUFSIZ). При чтении символа

c


c


____________________ |- Это не та "связующая" структура file в ядре, про которую шла речь выше, а ЕЩЕ одна - в памяти самой программы.

fp


достигнут, ложен - если еще нет. А. Богатырев, 1992-95 - 148 - Си в UNIX в буфер считывается read-ом из файла порция информации, и getc выдает ее первый байт. При последующих вызовах getc выдаются следующие байты из буфера, а обращений к диску уже не происходит! Лишь когда буфер будет исчерпан - произойдет очередное чтение с диска. Таким образом, информация читается из файла с опережением, заранее наполняя буфер; а по требованию выдается уже из буфера. Если мы читаем 1024 байта из файла при помощи getc(), то мы 1024 раза вызываем эту функцию, но всего 2 раза системный вызов read - для чтения двух порций информации из файла, каждая - по 512 байт. При записи

c


c


выводимые символы накапливаются в буфере. Только когда в нем окажется большая порция информации, она за одно обращение write записывается на диск. Буфер записи "выталки- вается" в файл в таких случаях: - буфер заполнен (содержит BUFSIZ символов). - при закрытии файла (fclose или exit |-|-). - при вызове функции fflush (см. ниже). - в специальном режиме - после помещения в буфер символа '\n' (см. ниже).

stdin


stdout


смотри ниже), что по-умолчанию так и есть. Приведем упрощенную схему, поясняющую взаимоотношения основных функций и макросов из

s


структуру FILE |=|=. Функции, работающие со строками, в цикле вызывают посимвольные

системным


вызовам


курсивом


Открыть файл, создать буфер:

stdio


FILE *fp = fopen(char *name, char *rwmode); | вызывает V int fd = open (char *name, int irwmode); Если открываем на запись и файл не существует (fd < 0),

создать


fd = creat(char *name, int accessmode); fd будет открыт для записи в файл. По умолчанию fopen() использует для creat коды доступа accessmode равные 0666 (rw- rw-rw-). ____________________ |-|- При выполнении вызова завершения программы exit(); все открытые файлы автомати- чески закрываются.

fd


дует придерживаться. Если переменная должна иметь более мнемоничное имя - следует

fp


А. Богатырев, 1992-95 - 149 - Си в UNIX Соответствие аргументов fopen и open: rwmode irwmode -------------------------

_


_


_


_


_


_


Для r, r+ файл уже должен существовать, в остальных случаях файл создается, если его не было. Если fopen() не смог открыть (или создать) файл, он возвращает значение NULL: if((fp = fopen(name, rwmode)) == NULL){ ...неудача... } Итак, схема: printf(fmt,...)--->--,----fprintf(fp,fmt,...)->--*

stdout


fputs(s,fp)--------->--|

putchar


stdout


fwrite(array,size,count,fp)->--| |

putc


------------------* | |файловая---<--write(fd,s,len)------------<----БУФЕР |система---->---read(fd,s,len)-* _flsbuf(c,fp) | | ! | |системные буфера ! | | | ! V ungetc(c,fp) |драйвер устр-ва ! | | |(диск, терминал) ! | _filbuf(fp) | | | ! *--------->-----БУФЕР<-* |устройство ! |

getc


| rdcount=fread(array,size,count,fp)--<--|

getchar


stdout


| fgets(sbuf,buflen,fp)-<--| scanf(fmt,.../*ук-ли*/)--<-,--fscanf(fp,fmt,...)-*

stdin


Закрыть файл, освободить память выделенную под буфер: fclose(fp) ---> close(fd); И чуть в стороне - функция позиционирования: fseek(fp,long_off,whence) ---> lseek(fd,long_off,whence);

_


либо читают новый буфер из файла.

fp


fileno


FILE


А. Богатырев, 1992-95 - 150 - Си в UNIX файл open-ом, мы можем ввести буферизацию этого канала: int fd = open(name, O_RDONLY); /* или creat() */ ... FILE *fp = fdopen(fd, "r"); (здесь надо вновь указать КАК мы открываем файл, что должно соответствовать режиму

fp


В приложении имеется текст, содержащий упрощенную реализацию главных функций из библиотеки stdio.

c


возвращается в буфер, поэтому эта операция неприменима к небуферизованным каналам. Возврат соответствует сдвигу указателя чтения из буфера (который увеличивается при

назад


дующим ungetc-ом должен быть хоть один getc), поскольку в противном случае можно

c


программы.

getchar


stdin


/* А можно заменить этот символ на другой! */

getchar


4.12. Очень часто делают ошибку в функции fputc, путая порядок ее аргументов. Так ничего не стоит написать: FILE *fp = ......; fputc( fp, '\n' ); Запомните навсегда!

c


указатель файла идет вторым! Существует также макроопределение putc( c, fp ); Оно ведет себя как и функция fputc, но не может быть передано в качестве аргумента в функцию: #include <stdio.h> putNtimes( fp, c, n, f ) FILE *fp; int c; int n; int (*f)(); { while( n > 0 ){ (*f)( c, fp ); n--; }} возможен вызов putNtimes( fp, 'a', 3, fputc ); но недопустимо putNtimes( fp, 'a', 3, putc ); Тем не менее всегда, где возможно, следует пользоваться макросом - он работает быст- рее. Аналогично, есть функция fgetc(fp) и макрос getc(fp). Отметим еще, что putchar и getchar это тоже всего лишь макросы

stdout


stdin


А. Богатырев, 1992-95 - 151 - Си в UNIX

stdio


дит в семейство функций:

fp


fp


fmt


bf


Первая из функций форматирует свои аргументы в соответствии с форматом, заданным

fmt


fp


stdout


bf


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

fp


fmt


bf


Функции fprintf и fscanf являются наиболее мощным средством работы с текстовыми фай-

изображение


4.14. Текстовые файлы (имеющие строчную организацию) хранятся на диске как линейные массивы байт. Для разделения строк в них используется символ '\n'. Так, например, текст стр1 стрк2 кнц хранится как массив с т р 1 \n с т р к 2 \n к н ц длина=14 байт !

RWptr


(расстояние в байтах от начала файла) При выводе на экран дисплея символ \n преобразуется драйвером терминалов в последова- тельность \r\n, которая возвращает курсор в начало строки ('\r') и опускает курсор на строку вниз ('\n'), то есть курсор переходит в начало следующей строки. В MS DOS строки в файле на диске разделяются двумя символами \r\n и при выводе на экран никаких преобразований не делается|-. Зато библиотечные функции языка Си преобразуют эту последовательность при чтении из файла в \n, а при записи в файл превращают \n в \r\n, поскольку в Си считается, что строки разделяются только \n. Для работы с файлом без таких преобразований, его надо открывать как "бинарный":

fp


fd


____________________ |- Управляющие символы имеют следующие значения: '\n' - '\012' (10) line feed '\r' - '\015' (13) carriage return '\t' - '\011' (9) tab '\b' - '\010' (8) backspace '\f' - '\014' (12) form feed '\a' - '\007' (7) audio bell (alert) '\0' - 0. null byte А. Богатырев, 1992-95 - 152 - Си в UNIX Все нетекстовые файлы в MS DOS надо открывать именно так, иначе могут произойти раз- ные неприятности. Например, если мы программой копируем нетекстовый файл в текстовом режиме, то одиночный символ \n будет считан в программу как \n, но записан в новый файл как пара \r\n. Поэтому новый файл будет отличаться от оригинала (что для файлов с данными и программ совершенно недопустимо!). Задание: напишите программу подсчета строк и символов в файле. Указание: надо

последняя


иметь этого символа на конце. Поэтому если последний символ файла (тот, который вы прочитаете самым последним) не есть '\n', то добавьте к счетчику строк 1. 4.15. Напишите программу подсчета количества вхождений каждого из символов алфавита в файл и печатающую результат в виде таблицы в 4 колонки. (Указание: заведите массив из 256 счетчиков. Для больших файлов счетчики должны быть типа long).

fp


ваться типом int а не char? Ответ: функция getchar() сообщает о конце файла тем, что возвращает значение EOF

end of file


getchar() может прочесть из файла любой символ кодировки (кодировка содержит символы с кодами 0...255), а специальный признак не должен совпадать ни с одним из хранимых в файле символов. Поэтому для его хранения требуется больше одного байта (нужен хотя бы еще 1 бит). Проверка на конец файла в программе обычно выглядит так: ...

ch


ch


... }

ch


НИКОГДА не будет равно (-1). Даже если getchar() вернет такое значение, оно будет приведено к типу unsigned char обрубанием и станет равным 255. При срав- нении с целым (-1) оно расширится в int добавлением нулей слева и станет равно 255. Таким образом, наша программа никогда не завершится, т.к. вместо признака конца файла она будет читать символ с кодом 255 (255 != -1).

ch


ch


ого). Если getchar вернет значение (-1), то оно будет сначала в присваивании

ch


будет приведено к типу int и получится (-1). Таким образом, истинный конец файла будет обнаружен. Но теперь, если из файла будет прочитан настоящий символ с кодом 255, он будет приведен в сравнении к целому значению (-1) и будет также воспринят как конец файла. Таким образом, если в нашем файле окажется символ с кодом 255, то программа воспримет его как фальшивый конец файла и оставит весь остаток файла необработанным (а в нетекстовых файлах такие символы - не ред- кость).

ch


Отметим, что в UNIX признак конца файла в самом файле физически НЕ ХРАНИТСЯ. Система в любой момент времени знает длину файла с точностью до одного байта; признак EOF вырабатывается стандартными функциями тогда, когда обнаруживается, что указатель чте- ния достиг конца файла (то есть позиция чтения стала равной длине файла - последний байт уже прочитан). В MS DOS же в текстовых файлах признак конца (EOF) хранится явно и обозначается символом CTRL/Z. Поэтому, если программным путем записать куда-нибудь в середину файла символ CTRL/Z, то некоторые программы перестанут "видеть" остаток файла после этого символа! Наконец отметим, что разные функции при достижении конца файла выдают разные значения: scanf, fscanf, fgetc, getc, getchar выдают EOF, read - выдает 0, а gets, fgets - NULL. А. Богатырев, 1992-95 - 153 - Си в UNIX 4.17. Напишите программу, которая запрашивает ваше имя и приветствует вас. Для ввода имени используйте стандартные библиотечные функции

s


s


В чем разница?

fp


Она не контролирует длину буфера, в которую считывается строка, поэтому если строка окажется слишком длинной - ваша программа повредит свою память (и аварийно завер-

достаточно большим


ное понятие!), чтобы вместить максимально возможную (длинную) строку. Функция fgets() контролирует длину строки: если строка на входе окажется длин-

slen


лен "на потом". Следующий вызов fgets прочитает этот сохраненный остаток. Кроме того fgets, в отличие от gets, не обрубает символ '\n' на конце строки, что доставляет нам дополнительные хлопоты по его уничтожению, поскольку в Си "нормальные" строки завер- шаются просто '\0', а не "\n\0".

buffer


...

buffer


len


buffer


buffer


}

len


печатал бы текст через строку, поскольку выдавал бы код '\n' дважды - из строки

buffer


Если в файле больше нет строк (файл дочитан до конца), то функции gets и fgets возвращают значение NULL. Обратите внимание, что NULL, а не EOF. Пока файл не дочи- тан, эти функции возвращают свой первый аргумент - адрес буфера, в который была запи- сана очередная строка файла. Фрагмент для обрубания символа перевода строки может выглядеть еще так:

stdio


string


buffer


...

buffer


sptr


sptr


sptr


buffer


}

s


s


s


перевода строки не добавляет. Упрощенно: fputs(s, fp) char *s; FILE *fp; { while(*s) putc(*s++, fp); } puts(s) char *s; { fputs(s, stdout); putchar('\n'); } А. Богатырев, 1992-95 - 154 - Си в UNIX 4.19. Найдите ошибки в программе: #include <stdio.h> main() { int fp; int i; char str[20]; fp = fopen("файл"); fgets(stdin, str, sizeof str); for( i = 0; i < 40; i++ ); fputs(fp, "Текст, выводимый в файл:%s",str ); fclose("файл"); } Мораль: надо быть внимательнее к формату вызова и смыслу библиотечных функций. 4.20. Напишите программу, которая распечатывает самую длинную строку из файла ввода и ее длину.

n


файла задаются как аргументы main(). 4.22. Напишите программу

сКакой


сколько


строк с единицы). #include <stdio.h> #include <ctype.h> long line, count, nline, ncount; /* нули */ char buf[512]; void main(int argc, char **argv){ char c; FILE *fp; argc--; argv++; /* Разбор ключей */ while((c = **argv) == '-' || c == '+'){ long atol(), val; char *s = &(*argv)[1]; if( isdigit(*s)){ val = atol(s); if(c == '-') nline = val; else ncount = val; } else fprintf(stderr,"Неизвестный ключ %s\n", s-1); argc--; ++argv; } if( !*argv ) fp = stdin; else if((fp = fopen(*argv, "r")) == NULL){ fprintf(stderr, "Не могу читать %s\n", *argv); exit(1); } for(line=1, count=0; fgets(buf, sizeof buf, fp); line++){ if(line >= nline){ fputs(buf, stdout); count++; } if(ncount && count == ncount) break; } А. Богатырев, 1992-95 - 155 - Си в UNIX

явно


} /* End_Of_File */

n


n


4.25. Напишите программу, которая читает 2 файла и печатает их вперемежку: одна строка из первого файла, другая - из второго. Придумайте, как поступить, если файлы содержат разное число строк. 4.26. Напишите программу сравнения двух файлов, которая будет печатать первую из различающихся строк и позицию символа, в котором они различаются. 4.27. Напишите программу для интерактивной работы с файлом. Сначала у вас запраши- вается имя файла, а затем вам выдается меню: 1. Записать текст в файл. 2. Дописать текст к концу файла. 3. Просмотреть файл. 4. Удалить файл. 5. Закончить работу. Текст вводится в файл построчно с клавиатуры. Конец ввода - EOF (т.е. CTRL/D), либо одиночный символ '.' в начале строки. Выдавайте число введенных строк. Просмотр файла должен вестись постранично: после выдачи очередной порции строк выдавайте подсказку --more-- _ (курсор остается в той же строке и обозначен подчерком) и ожидайте нажатия клавиши. Ответ 'q' завершает просмотр. Если файл, который вы хотите просмотреть, не сущест- вует - выдавайте сообщение об ошибке. После выполнения действия программа вновь запрашивает имя файла. Если вы отве- тите вводом пустой строки (сразу нажмете <ENTER>, то должно использоваться имя файла, введенное на предыдущем шаге. Имя файла, предлагаемое по умолчанию, принято писать в запросе в [] скобках.

oldfile


Когда вы научитесь работать с экраном дисплея (см. главу "Экранные библиотеки"), перепишите меню и выдачу сообщений с использованием позиционирования курсора в задан- ное место экрана и с выделением текста инверсией. Для выбора имени файла предложите меню: отсортированный список имен всех файлов текущего каталога (по поводу получения списка файлов см. главу про взаимодействие с UNIX). Просто для распечатки текущего каталога на экране можно также использовать вызов system("ls -x"); а для считывания каталога в программу|-

fp


fp


fp


(в этом примере читаются только имена .c файлов).

n


К сожалению, это возможно только путем переписывания всего файла в другое место (без ненужной строки) и последующего его переименования. А. Богатырев, 1992-95 - 156 - Си в UNIX 4.29. Составьте программу перекодировки текста, набитого в кодировке КОИ-8, в аль- тернативную кодировку и наоборот. Для этого следует составить таблицу перекодировки

c


дартную функцию strchr(). Программа читает один файл и создает новый. 4.30. Напишите программу, делящую большой файл на куски заданного размера (не в строках, а в килобайтах). Эта программа может применяться для записи слишком боль- шого файла на дискеты (файл режется на части и записывается на несколько дискет).

fcntl


stdio


#define min(a,b) (((a) < (b)) ? (a) : (b)) #define KB 1024 /* килобайт */ #define PORTION (20L* KB) /* < 32768 */ long ONEFILESIZE = (300L* KB); extern char *strrchr(char *, char); extern long atol (char *); extern errno; /* системный код ошибки */ char buf[PORTION]; /* буфер для копирования */ void main (int ac, char *av[]) { char name[128], *s, *prog = av[0]; int cnt=0, done=0, fdin, fdout; /* M_UNIX автоматически определяется * компилятором в UNIX */

_


fmode


/* Задает режим открытия и создания ВСЕХ файлов */ #endif if(av[1] && *av[1] == '-'){ /* размер одного куска */ ONEFILESIZE = atol(av[1]+1) * KB; av++; ac--; } if (ac < 2){ fprintf(stderr, "Usage: %s [-size] file\n", prog); exit(1); } if ((fdin = open (av[1], O_RDONLY)) < 0) { fprintf (stderr, "Cannot read %s\n", av[1]); exit (2); } if ((s = strrchr (av[1], '.'))!= NULL) *s = '\0'; do { unsigned long sent; sprintf (name, "%s.%d", av[1], ++cnt); if ((fdout = creat (name, 0644)) < 0) { fprintf (stderr, "Cannot create %s\n", name); exit (3); } sent = 0L; /* сколько байт переслано */ for(;;){ unsigned isRead, /* прочитано read-ом */ need = min(ONEFILESIZE - sent, PORTION); if( need == 0 ) break; sent += (isRead = read (fdin, buf, need)); errno = 0; if (write (fdout, buf, isRead) != isRead && errno){ perror("write"); exit(4); } else if (isRead < need){ done++; break; } } if(close (fdout) < 0){ perror("Мало места на диске"); exit(5); } printf("%s\t%lu байт\n", name, sent); } while( !done ); exit(0); } А. Богатырев, 1992-95 - 157 - Си в UNIX 4.31. Напишите обратную программу, которая склеивает несколько файлов в один. Это аналог команды cat с единственным отличием: результат выдается не в стандартный вывод, а в файл, указанный в строке аргументов последним. Для выдачи в стандартный вывод следует указать имя "-".

fcntl


stdio


void main (int ac, char **av){ int i, err = 0; FILE *fpin, *fpout; if (ac < 3) { fprintf(stderr,"Usage: %s from... to\n", av[0]); exit(1); } fpout = strcmp(av[ac-1], "-") ? /* отлично от "-" */ fopen (av[ac-1], "wb") : stdout; for (i = 1; i < ac-1; i++) { register int c; fprintf (stderr, "%s\n", av[i]); if ((fpin = fopen (av[i], "rb")) == NULL) { fprintf (stderr, "Cannot read %s\n", av[i]); err++; continue; } while ((c = getc (fpin)) != EOF) putc (c, fpout); fclose (fpin); } fclose (fpout); exit (err); } Обе эти программы могут без изменений транслироваться и в MS DOS и в UNIX. UNIX просто игнорирует букву b в открытии файла "rb", "wb". При работе с read мы могли бы открывать файл как

_


_


#endif

fdin


4.32. Каким образом стандартный ввод переключить на ввод из заданного файла, а стан- дартный вывод - в файл? Как проверить, существует ли файл; пуст ли он? Как надо открывать файл для дописывания информации в конец существующего файла? Как надо отк- рывать файл, чтобы попеременно записывать и читать тот же файл? Указание: см. fopen, freopen, dup2, stat. Ответ про перенаправления ввода: способ 1 (библиотечные функции) #include <stdio.h> ... freopen( "имя_файла", "r", stdin ); способ 2 (системные вызовы) #include <fcntl.h> int fd; ... fd = open( "имя_файла", O_RDONLY ); dup2 ( fd, 0 ); /* 0 - стандартный ввод */ close( fd ); /* fd больше не нужен - закрыть его, чтоб не занимал место в таблице */ А. Богатырев, 1992-95 - 158 - Си в UNIX способ 3 (системные вызовы) #include <fcntl.h> int fd; ... fd = open( "имя_файла", O_RDONLY ); close (0); /* 0 - стандартный ввод */

_


close (fd); Это перенаправление ввода соответствует конструкции

a


написанной на командном языке СиШелл. Для перенаправления вывода замените 0 на 1,

stdin


Рассмотрим механику работы вызова dup2 |-: new = open("файл1",...); dup2(new, old); close(new); таблица открытых файлов процесса ...## ## new----##---> файл1 new---##---> файл1 ## ## old----##---> файл2 old---## файл2 ## ## 0:до вызова 1:разрыв связи old с файл2 dup2() (закрытие канала old, если он был открыт) ## ## new----##--*--> файл1 new ## *----> файл1 ## | ## | old----##--* old--##--* ## ## 2:установка old на файл1 3:после оператора close(new); на этом dup2 завершен. дескриптор new закрыт.

файл1


валось выше (в них содержатся указатели чтения/записи). После вызова dup2 дескрипторы

new


new


использоваться даже вперемежку:

new


new


old


new


файл1


____________________ |- Функция

команда


команда


манд

команда


А. Богатырев, 1992-95 - 159 - Си в UNIX int fd; printf( "Hi there\n"); fd = creat( "newout", 0640 ); dup2(fd, 1); close(fd); printf( "Hey, You!\n");

newout


stdout


4.33. Напишите программу, которая будет выдавать подряд в стандартный вывод все

argc


зации цикла. Добавьте сквозную нумерацию строк и печать номера строки. 4.34. Напишите программу, распечатывающую первую директиву препроцессора, встретив- шуюся в файле ввода.

stdio


buf


s


s


s


s


} 4.35. Напишите программу, которая переключает свой стандартный вывод в новый файл

имяФайла


имяФайла


Ответ: #include <stdio.h> char line[512]; main(){ FILE *fp = fopen("00", "w"); while(gets(line) != NULL) if( !strncmp(line, ">>>", 3)){ if( freopen(line+3, "a", fp) == NULL){ fprintf(stderr, "Can't write to '%s'\n", line+3); fp = fopen("00", "a"); } } else fprintf(fp, "%s\n", line); } 4.36. Библиотека буферизованного обмена stdio содержит функции, подобные некоторым системным вызовам. Вот функции - аналоги read и write: Стандартная функция fread из библиотеки стандартных функций Си предназначена для чтения нетекстовой (как правило) информации из файла: ____________________ и возвращает код ответа этой программы. Функция popen (pipe open) также запускает интерпретатор команд, при этом перенаправив его стандартный вывод в трубу (pipe).

fp


рамму выдачу запущенной команды. ____________________ |- dup2 читается как "dup to", в английском жаргоне принято обозначать предлог "to" цифрой 2, поскольку слова "to" и "two" произносятся одинаково: "ту". "From me 2 You". Также 4 читается как "for". А. Богатырев, 1992-95 - 160 - Си в UNIX int fread(addr, size, count, fp) register char *addr; unsigned size, count; FILE *fp; { register c; unsigned ndone=0, sz; if(size) for( ; ndone < count ; ndone++){ sz = size; do{ if((c = getc(fp)) >= 0 ) *addr++ = c; else return ndone; }while( --sz ); } return ndone; }

count


size


функция fwrite для записи в файл. Пример: #include <stdio.h> #define MAXPTS 200 #define N 127 char filename[] = "pts.dat"; struct point { int x,y; } pts[MAXPTS], pp= { -1, -2}; main(){ int n, i; FILE *fp = fopen(filename, "w"); for(i=0; i < N; i++) /* генерация точек */ pts[i].x = i, pts[i].y = i * i; /* запись массива из N точек в файл */ fwrite((char *)pts, sizeof(struct point), N, fp); fwrite((char *)&pp, sizeof pp, 1, fp); fp = freopen(filename, "r", fp); /* или fclose(fp); fp=fopen(filename, "r"); */ /* чтение точек из файла в массив */ n = fread(pts, sizeof pts[0], MAXPTS, fp); for(i=0; i < n; i++) printf("Точка #%d(%d,%d)\n",i,pts[i].x,pts[i].y); } Файлы, созданные fwrite, не переносимы на машины другого типа, поскольку в них хра-

текст


изображений


а содержит "сырые" байты. Поэтому чаще пользуются функциями работы с текстовыми фай- лами: fprintf, fscanf, fputs, fgets. Данные, хранимые в виде текста, имеют еще одно преимущество помимо переносимости: их легко при нужде подправить текстовым редакто- ром. Зато они занимают больше места! Аналогом системного вызова lseek служит функция fseek:

fp


Она полностью аналогична lseek, за исключением возвращаемого ею значения. Она НЕ возвращает новую позицию указателя чтения/записи! Чтобы узнать эту позицию применя- ется специальная функция

fp


fp


fp


А. Богатырев, 1992-95 - 161 - Си в UNIX 4.37. Найдите ошибку в программе (программа распечатывает корневой каталог в "ста- ром" формате каталогов - с фиксированной длиной имен): #include <stdio.h> #include <sys/types.h> #include <sys/dir.h> main(){ FILE *fp; struct direct d; char buf[DIRSIZ+1]; buf[DIRSIZ] = '\0'; fp = fopen( '/', "r" ); while( fread( &d, sizeof d, 1, fp) == 1 ){ if( !d.d_ino ) continue; /* файл стерт */ strncpy( buf, d.d_name, DIRSIZ); printf( "%s\n", buf ); } fclose(fp); } Указание: смотри в fopen(). Внимательнее к строкам и символам! '/' и "/" - это совершенно разные вещи (хотя синтаксической ошибки нет!). Переделайте эту программу, чтобы название каталога поступало из аргументов main (а если название не задано - используйте текущий каталог "."). 4.38. Функциями

строка


формат


fp


формат


жит для них признаком конца строки. Однако такой байт может понадобиться в файле, если мы формируем некоторые нетекстовые данные, например управляющую последователь- ность переключения шрифтов для принтера. Как быть? Есть много вариантов решения.

fp


сделать это посимвольно:

fp


fp


(можно просто в цикле), либо использовать один из способов:

fp


fp


fp


где 4 - количество выводимых байтов. 4.39. Напишите функции для "быстрого доступа" к строкам файла. Идея такова: сначала прочитать весь файл от начала до конца и смещения начал строк (адреса по файлу)

_


n


stdio


#define MAXLINES 2000 /* Максим. число строк в файле*/ FILE *fp; /* Указатель на файл */ int nlines; /* Число строк в файле */ long offsets[MAXLINES];/* Адреса начал строк */ extern long ftell();/*Выдает смещение от начала файла*/ А. Богатырев, 1992-95 - 162 - Си в UNIX char buffer[256]; /* Буфер для чтения строк */ /* Разметка массива адресов начал строк */ void getSeeks(){ int c; offsets[0] =0L; while((c = getc(fp)) != EOF) if(c =='\n') /* Конец строки - начало новой */ offsets[++nlines] = ftell(fp); /* Если последняя строка файла не имеет \n на конце, */ /* но не пуста, то ее все равно надо посчитать */ if(ftell(fp) != offsets[nlines]) nlines++; printf( "%d строк в файле\n", nlines); } char *getLine(n){ /* Прочесть строку номер n */ fseek(fp, offsets[n], 0); return fgets(buffer, sizeof buffer, fp); } void main(){ /* печать файла задом-наперед */ int i; fp = fopen("INPUT", "r"); getSeeks(); for( i=nlines-1; i>=0; --i) printf( "%3d:%s", i, getLine(i)); } 4.40. Что будет выдано на экран в результате выполнения программы?

stdio


main(){ printf( "Hello, " ); printf( "sunny " ); write( 1, "world", 5 ); }

Hello


stdout


умолчанию с терминалом. Увы, эта догадка верна лишь отчасти! Будет напечатано

worldHello


буферизован, а при помощи сисвызова write - нет. printf помещает строку сначала в

stdout


затем по окончании программы буфер выталкивается на экран. Чтобы получить правильный эффект, следует перед write() написать вызов явного

stdout


stdout


stdout


можно написать

stdout


stderr


поэтому выдаваемые в него сообщения печатаются немедленно. Мораль: надо быть очень осторожным при смешанном использовании буферизованного и небуферизованного обмена. А. Богатырев, 1992-95 - 163 - Си в UNIX Некоторые каналы буферизуются так, что буфер выталкивается не только при запол-

stdout


именно таков:

Hello


stdout


буферизации можно так:

fp


fp


Учтите, что любое изменение способа буферизации должно быть сделано ДО первого обра- щения к каналу! 4.41. Напишите программу, выдающую три звуковых сигнала. Гудок на терминале вызыва- ется выдачей символа '\7' ('\a' по стандарту ANSI). Чтобы гудки звучали раздельно, надо делать паузу после каждого из них. (Учтите, что вывод при помощи printf() и putchar() буферизован, поэтому после выдачи каждого гудка (в буфер) надо вызывать функцию fflush() для сброса буфера). Ответ: Способ 1: register i; for(i=0; i<3; i++){ putchar( '\7' ); fflush(stdout); sleep(1); /* пауза 1 сек. */ } Способ 2: register i; for(i=0; i<3; i++){ write(1, "\7", 1 ); sleep(1); } 4.42. Почему задержка не ощущается? printf( "Пауза..."); sleep ( 5 ); /* ждем 5 сек. */ printf( "продолжаем\n" );

stdout


заполнился, не выдается на экран. Дальше программа "молчаливо" ждет 5 секунд. Обе фразы будут выданы уже после задержки! Чтобы первый printf() выдал свою фразу ДО

stdout


stderr


решить и так:

stderr


4.43. Еще один пример про буферизацию. Почему программа печатает EOF?

stdio


fwr


b


main(){

fwr


А. Богатырев, 1992-95 - 164 - Си в UNIX

frd


fwr


s


s


}

fwr


пуст! Надо вставить

fwr


после fprintf(). Вот еще подобный случай:

fp


fp


system("sort users | uniq > 00; mv 00 users");

fp


за время работы буферов) может быть еще не вытолкнут в файл. Следует либо закрыть

fp


fp


4.44. В UNIX многие внешние устройства (практически все!) с точки зрения программ

имена


драйверы


открытии такого файла-устройства мы на самом деле инициализируем драйвер этого уст- ройства, и в дальнейшем он выполняет наши запросы read, write, lseek аппаратно- зависимым образом. Для операций, специфичных для данного устройства, предусмотрен сисвызов ioctl (input/output control):

fd


аргумент


РОД


свой собственный


некоторое мнемоническое обозначение. В качестве примера приведем операцию TCGETA, применимую только к терминалам и узнающую текущие моды драйвера терминала (см. главу "Экранные библиотеки"). То, что эта операция неприменима к другим устройствам и к обычным файлам (не устройствам), позволяет нам использовать ее для проверки - является ли открытый файл терминалом (или клавиатурой):

termio


fd


fd


} main(){ printf("%s\n", isatty(0 /* STDIN */)? "term":"no"); } Функция isatty является стандартной функцией|-. Есть "псевдоустройства", которые представляют собой драйверы логических уст- ройств, не связанных напрямую с аппаратурой, либо связанных лишь косвенно. Примером

псевдотерминал


ребительны два псевдоустройства: /dev/null Это устройство, представляющее собой "черную дыру". Чтение из него немедленно выдает признак конца файла: read(...)==0; а записываемая в него информация нигде не сохраняется (пропадает). Этот файл используется, например, в том случае, когда мы хотим проигнорировать вывод какой-либо программы (сообщения об ошибках, трассировку), нигде его не сохраняя. Тогда мы просто перенаправляем ее вывод в /dev/null: А. Богатырев, 1992-95 - 165 - Си в UNIX $ a.out > /dev/null & Еще один пример использования:

dev


Содержимое всего винчестера копируется "в никуда". При этом, если на диске есть сбойные блоки - система выдает на консоль сообщения об ошибках чтения. Так мы можем быстро выяснить, есть ли на диске плохие блоки. /dev/tty

управляющий


терминал


перенаправлены в какие-то другие файлы|=. Поэтому, если мы хотим выдать сообще-

экране


stdio


s


fptty


fptty


fptty


} main(){ message("Tear down the wall!"); } Это устройство доступно и для записи (на экран) и для чтения (с клавиатуры).

_


и просто игнорируется. Поэтому невозможно случайно уничтожить файл-устройство (к при-

dev


fd


Файлы-устройства создаются вызовом mknod, а уничтожаются обычным unlink-ом. Более подробно про это - в главе "Взаимодействие с UNIX". 4.45. Эмуляция основ библиотеки STDIO, по мотивам 4.2 BSD. #include <fcntl.h> #define BUFSIZ 512 /* стандартный размер буфера */ #define _NFILE 20 #define EOF (-1) /* признак конца файла */ #define NULL ((char *) 0) #define IOREAD 0x0001 /* для чтения */ #define IOWRT 0x0002 /* для записи */ #define IORW 0x0004 /* для чтения и записи */ #define IONBF 0x0008 /* не буферизован */ #define IOTTY 0x0010 /* вывод на терминал */ #define IOALLOC 0x0020 /* выделен буфер malloc-ом */ #define IOEOF 0x0040 /* достигнут конец файла */ #define IOERR 0x0080 /* ошибка чтения/записи */ ____________________

fd


имя этого устройства вызовом стандартной функции extern char *ttyname();

tname


01


А. Богатырев, 1992-95 - 166 - Си в UNIX extern char *malloc(); extern long lseek(); typedef unsigned char uchar; uchar sibuf[BUFSIZ], sobuf[BUFSIZ]; typedef struct _iobuf { int cnt; /* счетчик */ uchar *ptr, *base; /* указатель в буфер и на его начало */ int bufsiz, flag, file; /* размер буфера, флаги, дескриптор */ } FILE; FILE iob[_NFILE] = { { 0, NULL, NULL, 0, IOREAD, 0 }, { 0, NULL, NULL, 0, IOWRT|IOTTY, 1 }, { 0, NULL, NULL, 0, IOWRT|IONBF, 2 }, }; #define stdin (&iob[0]) #define stdout (&iob[1]) #define stderr (&iob[2]) #define putchar(c) putc((c), stdout) #define getchar() getc(stdin) #define fileno(fp) ((fp)->file) #define feof(fp) (((fp)->flag & IOEOF) != 0) #define ferror(fp) (((fp)->flag & IOERR) != 0) #define clearerr(fp) ((void) ((fp)->flag &= ~(IOERR | IOEOF))) #define getc(fp) (--(fp)->cnt < 0 ? \ filbuf(fp) : (int) *(fp)->ptr++) #define putc(x, fp) (--(fp)->cnt < 0 ? \ flsbuf((uchar) (x), (fp)) : \ (int) (*(fp)->ptr++ = (uchar) (x))) int fputc(int c, FILE *fp){ return putc(c, fp); } int fgetc( FILE *fp){ return getc(fp); } ____________________ NULL. ____________________ |= Ссылка на управляющий терминал процесса хранится в u-area каждого процесса:

u


открыть для вас. Если разные процессы открывают /dev/tty, они могут открыть в итоге

одно


UNIX. А. Богатырев, 1992-95 - 167 - Си в UNIX /* Открытие файла */ FILE *fopen(char *name, char *how){ register FILE *fp; register i, rw; for(fp = iob, i=0; i < _NFILE; i++, fp++) if(fp->flag == 0) goto found; return NULL; /* нет свободного слота */ found: rw = how[1] == '+'; if(*how == 'r'){ if((fp->file = open(name, rw ? O_RDWR:O_RDONLY)) < 0) return NULL; fp->flag = IOREAD; } else { if((fp->file = open(name, (rw ? O_RDWR:O_WRONLY)| O_CREAT | (*how == 'a' ? O_APPEND : O_TRUNC), 0666 )) < 0) return NULL; fp->flag = IOWRT; } if(rw) fp->flag = IORW; fp->bufsiz = fp->cnt = 0; fp->base = fp->ptr = NULL; return fp; } /* Принудительный сброс буфера */ void fflush(FILE *fp){ uchar *base; int full= 0; if((fp->flag & (IONBF|IOWRT)) == IOWRT && (base = fp->base) != NULL && (full=fp->ptr - base) > 0){ fp->ptr = base; fp->cnt = fp->bufsiz; if(write(fileno(fp), base, full) != full) fp->flag |= IOERR; } } /* Закрытие файла */ void fclose(FILE *fp){ if((fp->flag & (IOREAD|IOWRT|IORW)) == 0 ) return; fflush(fp); close(fileno(fp)); if(fp->flag & IOALLOC) free(fp->base); fp->base = fp->ptr = NULL; fp->cnt = fp->bufsiz = fp->flag = 0; fp->file = (-1); } /* Закрытие файлов при exit()-е */

_


register i; for(i=0; i < _NFILE; i++) fclose(iob + i); } /* Завершить текущий процесс */ void exit(uchar code){ _cleanup(); _exit(code); /* Собственно системный вызов */ } А. Богатырев, 1992-95 - 168 - Си в UNIX /* Прочесть очередной буфер из файла */ int filbuf(FILE *fp){ static uchar smallbuf[_NFILE]; if(fp->flag & IORW){ if(fp->flag & IOWRT){ fflush(fp); fp->flag &= ~IOWRT; } fp->flag |= IOREAD; /* операция чтения */ } if((fp->flag & IOREAD) == 0 || feof(fp)) return EOF; while( fp->base == NULL ) /* отвести буфер */ if( fp->flag & IONBF ){ /* небуферизованный */ fp->base = &smallbuf[fileno(fp)]; fp->bufsiz = sizeof(uchar); } else if( fp == stdin ){ /* статический буфер */ fp->base = sibuf; fp->bufsiz = sizeof(sibuf); } else if((fp->base = malloc(fp->bufsiz = BUFSIZ)) == NULL) fp->flag |= IONBF; /* не будем буферизовать */ else fp->flag |= IOALLOC; /* буфер выделен */ if( fp == stdin && (stdout->flag & IOTTY)) fflush(stdout); fp->ptr = fp->base; /* сбросить на начало буфера */ if((fp->cnt = read(fileno(fp), fp->base, fp->bufsiz)) == 0 ){ fp->flag |= IOEOF; if(fp->flag & IORW) fp->flag &= ~IOREAD; return EOF; } else if( fp->cnt < 0 ){ fp->flag |= IOERR; fp->cnt = 0; return EOF; } return getc(fp); } А. Богатырев, 1992-95 - 169 - Си в UNIX /* Вытолкнуть очередной буфер в файл */ int flsbuf(int c, FILE *fp){ uchar *base; int full, cret = c; if( fp->flag & IORW ){ fp->flag &= ~(IOEOF|IOREAD); fp->flag |= IOWRT; /* операция записи */ } if((fp->flag & IOWRT) == 0) return EOF; tryAgain: if(fp->flag & IONBF){ /* не буферизован */ if(write(fileno(fp), &c, 1) != 1) { fp->flag |= IOERR; cret=EOF; } fp->cnt = 0; } else { /* канал буферизован */ if((base = fp->base) == NULL){ /* буфера еще нет */ if(fp == stdout){ if(isatty(fileno(stdout))) fp->flag |= IOTTY; else fp->flag &= ~IOTTY; fp->base = fp->ptr = sobuf; /* статический буфер */ fp->bufsiz = sizeof(sobuf); goto tryAgain; } if((base = fp->base = malloc(fp->bufsiz = BUFSIZ))== NULL){ fp->bufsiz = 0; fp->flag |= IONBF; goto tryAgain; } else fp->flag |= IOALLOC; } else if ((full = fp->ptr - base) > 0) if(write(fileno(fp), fp->ptr = base, full) != full) { fp->flag |= IOERR; cret = EOF; } fp->cnt = fp->bufsiz - 1; *base++ = c; fp->ptr = base; } return cret; } /* Вернуть символ в буфер */ int ungetc(int c, FILE *fp){ if(c == EOF || fp->flag & IONBF || fp->base == NULL) return EOF; if((fp->flag & IOREAD)==0 || fp->ptr <= fp->base) if(fp->ptr == fp->base && fp->cnt == 0) fp->ptr++; else return EOF; fp->cnt++; return(* --fp->ptr = c); } /* Изменить размер буфера */ void setbuffer(FILE *fp, uchar *buf, int size){ fflush(fp); if(fp->base && (fp->flag & IOALLOC)) free(fp->base); fp->flag &= ~(IOALLOC|IONBF); if((fp->base = fp->ptr = buf) == NULL){ fp->flag |= IONBF; fp->bufsiz = 0; } else fp->bufsiz = size; fp->cnt = 0; } А. Богатырев, 1992-95 - 170 - Си в UNIX /* "Перемотать" файл в начало */ void rewind(FILE *fp){ fflush(fp); lseek(fileno(fp), 0L, 0); fp->cnt = 0; fp->ptr = fp->base; clearerr(fp); if(fp->flag & IORW) fp->flag &= ~(IOREAD|IOWRT); } /* Позиционирование указателя чтения/записи */ #ifdef COMMENT base ptr случай IOREAD | |<----cnt---->| 0L |б у |ф е р | |=======######@@@@@@@@@@@@@@======== файл file | |<-p->|<-dl-->| |<----pos---->| | | |<----offset(new)-->| | |<----RWptr---------------->| где pos = RWptr - cnt; // указатель с поправкой offset = pos + p = RWptr - cnt + p = lseek(file,0L,1) - cnt + p отсюда: (для SEEK_SET) p = offset+cnt-lseek(file,0L,1); или (для SEEK_CUR) dl = RWptr - offset = p - cnt lseek(file, dl, 1); Условие, что указатель можно сдвинуть просто в буфере: if( cnt > 0 && p <= cnt && base <= ptr + p ){ ptr += p; cnt -= p; } #endif /*COMMENT*/ А. Богатырев, 1992-95 - 171 - Си в UNIX int fseek(FILE *fp, long offset, int whence){ register resync, c; long p = (-1); clearerr(fp); if( fp->flag & (IOWRT|IORW)){ fflush(fp); if(fp->flag & IORW){ fp->cnt = 0; fp->ptr = fp->base; fp->flag &= ~IOWRT; } p = lseek(fileno(fp), offset, whence); } else if( fp->flag & IOREAD ){ if(whence < 2 && fp->base && !(fp->flag & IONBF)){ c = fp->cnt; p = offset; if(whence == 0) /* SEEK_SET */ p += c - lseek(fileno(fp), 0L, 1); else offset -= c; if(!(fp->flag & IORW) && c > 0 && p <= c && p >= fp->base - fp->ptr ){ fp->ptr += (int) p; fp->cnt -= (int) p; return 0; /* done */ } resync = offset & 01; } else resync = 0; if(fp->flag & IORW){ fp->ptr = fp->base; fp->flag &= ~IOREAD; resync = 0; } p = lseek(fileno(fp), offset-resync, whence); fp->cnt = 0; /* вынудить filbuf(); */ if(resync) getc(fp); } return (p== -1 ? -1 : 0); } /* Узнать текущую позицию указателя */ long ftell(FILE *fp){ long tres; register adjust; if(fp->cnt < 0) fp->cnt = 0; if(fp->flag & IOREAD) adjust = -(fp->cnt); else if(fp->flag & (IOWRT|IORW)){ adjust = 0; if(fp->flag & IOWRT && fp->base && !(fp->flag & IONBF)) /* буферизован */ adjust = fp->ptr - fp->base; } else return (-1L); if((tres = lseek(fileno(fp), 0L, 1)) < 0) return tres; return (tres + adjust); } А. Богатырев, 1992-95 - 172 - Си в UNIX

5. Структуры данных.


раз-


ного


struct {

x


s


s1


Структурный тип может иметь имя:

XYS


x


str


}; Здесь мы объявили тип, но не отвели ни одной переменной этого типа (хотя могли бы). Теперь опишем переменную этого типа и указатель на нее:

s2


Доступ к полям структуры производится по имени поля (а не по индексу, как у масси- вов):

имя


указатель


то есть не а

вес


x


x


Например

s1


s2


sptr


другого


struct XYS_Z {

xys


z


a1


a1


того же самого


ссылки


списки


struct node {

value


next


}; Очень часто используются массивы структур: А. Богатырев, 1992-95 - 173 - Си в UNIX

array


array


j


Статические структуры можно описывать с инициализацией, перечисляя значения их полей в {} через запятую:

n2


n1


n2


n3


n2


n1


Структуры одинакового типа можно присваивать целиком (что соответствует присваи- ванию каждого из полей):

s1


s2


в отличие от массивов, которые присваивать целиком нельзя:

a


Пример обращения к полям структуры: typedef struct _Point { short x, y; /* координаты точки */ char *s; /* метка точки */ } Point; Point p; Point *pptr; short *iptr; struct _Curve { Point points[25]; /* вершины ломанной */ int color; /* цвет линии */ } aLine[10], *linePtr = & aLine[0]; ... pptr = &p; /* указатель на структуру p */ p.x = 1; p.y = 2; p.s = "Grue"; linePtr->points[2].x = 54; aLine[5].points[0].y = 17; В ы р а ж е н и е значение ---------+------------+------------+-----------+----------- p.x | pptr->x | (*pptr).x | (&p)->x | 1 ---------+------------+------------+-----------+----------- &p->x | ошибка -----------+----------------+------------------+----------- iptr= &p.x | iptr= &pptr->x | iptr= &(pptr->x) | адрес поля -----------+----------------+--------+---------+----------- *pptr->s | *(pptr->s) | *p.s | p.s[0] | 'G' -----------+----------------+--------+---------+----------- pptr->s[1] | (&p)->s[1] | p.s[1] | 'r' -----------+----------------+------------------+----------- &p->s[1] | ошибка -----------+----------------+------------------+----------- (*pptr).s | pptr->s | p.s | "Grue" -----------+----------------+------------------+----------- *pptr.s | ошибка -----------------------------------------------+----------- А. Богатырев, 1992-95 - 174 - Си в UNIX Вообще (&p)->field = p.field pptr->field = (*pptr).field Объединения - это агрегаты данных, которые могут хранить в себе значения данных

на одном и том же месте


a


b


Структура: ________________________ A: | A.x int | Три поля ------------------------ расположены подряд. | A.y int | Получается как бы ------------------------ "карточка" с графами. | A.s char * | ------------------------ А у объединений поля расположены "параллельно", на одном месте в памяти. _______________________________________________________ B: | B.i int | B.s char * | B.aa : B.aa.x int | -----------| | struct a : B.aa.y int | ---------------| : B.aa.s char * | |___________________________| Это как бы "ящик" в который можно поместить значение любого типа из перечисленных, но не ВСЕ ВМЕСТЕ ("и то и это", как у структур), а ПО ОЧЕРЕДИ ("или/или"). Размер его достаточно велик, чтоб вместить самый большой из перечисленных типов данных.

другой


это иногда используется в машинно-зависимых программах. Вот пример, выясняющий поря- док байтов в short числах: union lb {

s


x


hi


x


hi


hi


или так: #include <stdio.h> union {

i


s


} u; void main(){ unsigned char *p; int n;

i


s


printf("%02X ", *p); } putchar('\n'); } А. Богатырев, 1992-95 - 175 - Си в UNIX или порядок слов в long числах:

xx


l


ab


a


b


ab


c


main(){ /* На IBM PC 80386 печатает 00020001 */

c


} 5.1. Найдите ошибки в описании структурного шаблона: structure { int arr[12], char string, int *sum } 5.2. Разработайте структурный шаблон, который содержал бы название месяца, трехбук- венную аббревиатуру месяца, количество дней в месяце и номер месяца. Инициализируйте его для невисокосного года. struct month { char name[10]; /* или char *name; */ char abbrev[4]; /* или char *abbrev; */ int days; int num; }; struct month months[12] = { /* индекс */ {"Январь" , "Янв", 31, 1 }, /* 0 */ {"Февраль", "Фев", 28, 2 }, /* 1 */ ... {"Декабрь", "Дек", 31, 12}, /* 11 */ }, *mptr = & months[0]; /* или *mptr = months */ main(){ struct month *mptr; printf( "%s\n", mptr[1].name ); printf( "%s %d\n", mptr->name, mptr->num ); }

months


файла. Используйте fprintf и fscanf.

name


name


Ответ: во втором случае для сохранения прочитанной строки надо заказывать память динамически при помощи malloc() и сохранять в ней строку при помощи strcpy(), т.к. память для хранения самой строки в структуре не зарезервирована (а только для указа- теля на нее). Найдите ошибку в операторах функции main(). Почему печатается не "Февраль", а

mptr


"неизвестно куда" - это локальная переменная (причем не получившая начального значе-

mptr


mptr


А. Богатырев, 1992-95 - 176 - Си в UNIX Заметим, что для распечатки всех или нескольких полей структуры следует ЯВНО перечислить в printf() все нужные поля и указать форматы, соответствующие типам этих полей. Не существует формата или стандартной функции, позволяющей распечатать все поля сразу (однако такая функция может быть написана вами для конкретного типа струк- тур). Также не существует формата для scanf(), который вводил бы структуру целиком. Вводить можно только по частям - каждое поле отдельно. 5.3. Напишите программу, которая по номеру месяца возвращает общее число дней года вплоть до этого месяца. 5.4. Переделайте предыдущую программу таким образом, чтобы она по написанному бук- вами названию месяца возвращала общее число дней года вплоть до этого месяца. В прог- рамме используйте функцию strcmp(). 5.5. Переделайте предыдущую программу таким образом, чтобы она запрашивала у пользо- вателя день, месяц, год и выдавала общее количество дней в году вплоть до данного дня. Месяц может обозначаться номером, названием месяца или его аббревиатурой. 5.6. Составьте структуру для учетной картотеки служащего, которая содержала бы сле- дующие сведения: фамилию, имя, отчество; год рождения; домашний адрес; место работы, должность; зарплату; дату поступления на работу. 5.7. Что печатает программа? struct man { char name[20]; int salary; } workers[] = { { "Иванов", 200 }, { "Петров", 180 }, { "Сидоров", 150 } }, *wptr, chief = { "начальник", 550 }; main(){ struct man *ptr, *cptr, save; ptr = wptr = workers + 1; cptr = &chief; save = workers[2]; workers[2] = *wptr; *wptr = save; wptr++; ptr--; ptr->salary = save.salary; printf( "%c %s %s %s %s\n%d %d %d %d\n%d %d %c\n", *workers[1].name, workers[2].name, cptr->name, ptr[1].name, save.name, wptr->salary, chief.salary, (*ptr).salary, workers->salary, wptr - ptr, wptr - workers, *ptr->name ); } Ответ: С Петров начальник Сидоров Сидоров 180 550 150 150 2 2 И 5.8. Разберите следующий пример: #include <stdio.h> struct man{ А. Богатырев, 1992-95 - 177 - Си в UNIX char *name, town[4]; int salary; int addr[2]; } men[] = { { "Вася", "Msc", 100, { 12, 7 } }, { "Гриша", "Len", 120, { 6, 51 } }, { "Петя", "Rig", 140, { 23, 84 } }, { NULL, "" , -1, { -1, -1 } } }; main(){ struct man *ptr, **ptrptr; int i; ptrptr = &ptr; *ptrptr = &men[1]; /* men+1 */ printf( "%s %d %s %d %c\n", ptr->name, ptr->salary, ptr->town, ptr->addr[1], ptr[1].town[2] ); (*ptrptr)++; /* копируем *ptr в men[0] */ men[0].name = ptr->name; /* (char *) #1 */ strcpy( men[0].town, ptr->town ); /* char [] #2 */ men[0].salary = ptr->salary; /* int #3 */ for( i=0; i < 2; i++ ) men[0].addr[i] = ptr->addr[i]; /* массив #4 */ /* распечатываем массив структур */ for(ptr=men; ptr->name; ptr++ ) printf( "%s %s %d\n", ptr->name, ptr->town, ptr->addr[0]); } Обратите внимание на такие моменты:

ptrptr


2) При копировании структур отдельными полями, поля скалярных типов (int, char, long, ..., указатели) копируются операцией присваивания (см. строки с пометками #1 и #3). Поля векторных типов (массивы) копируются при помощи цикла, поэле- ментно пересылающего массив (строка #4). Строки (массивы букв) пересылаются стандартной функцией strcpy (строка #2). Все это относится не только к полям структур, но и к переменным таких типов. Структуры можно также копировать не по

men


3) Запись аргументов функции printf() лесенкой позволяет лучше видеть, какому фор- мату соответствует каждый аргумент. 4) При распечатке массива структур мы печатаем не определенное их количество (рав-

name


туры как признаком конца массива.

town


4х байт. Это необходимо потому, что строка "Msc" состоит не из 3х, а из 4х бай- тов: 'M','s','c','\0'. При работе со структурами и указателями большую помощь могут оказать рисунки. Вот как

men


А. Богатырев, 1992-95 - 178 - Си в UNIX --ptr-- --ptrptr-- ptr | * |<------|---* | ---|--- ---------- | / =========men[0]== / men:|name | *---|-----> "Вася" | |---------------| | |town |M|s|c|\0| | |---------------| | |salary| 100 | | |---------------| | |addr | 12 | 7 | \ ----------------- \ =========men[1]== \-->|name | *---|-----> "Гриша" ............ 5.9. Составьте программу "справочник по таблице Менделеева", которая по названию химического элемента выдавала бы его характеристики. Таблицу инициализируйте массивом структур. 5.10. При записи данных в файл (да и вообще) используйте структуры вместо массивов, если элементы массива имеют разное смысловое назначение. Не воспринимайте структуру просто как средство объединения данных разных типов, она может быть и средством объе- динения данных одного типа, если это добавляет осмысленности нашей программе. Чем плох фрагмент? int data[2]; data[0] = my_key; data[1] = my_value; write(fd, (char *) data, 2 * sizeof(int)); Во-первых, тогда уж лучше указать размер всего массива сразу (хотя бы на тот случай, если мы изменим его размер на 3 и забудем поправить множитель с 2 на 3). write(fd, (char *) data, sizeof data);

data


адрес). Во-вторых, элементы массива имеют разный смысл, так не использовать ли тут структуру? struct _data { int key; int value; } data; data.key = my_key; data.value = my_value; write(fd, &data, sizeof data); 5.11. Что напечатает следующая программа? Нарисуйте расположение указателей по окон- чании данной программы. #include <stdio.h> struct lnk{ char c; А. Богатырев, 1992-95 - 179 - Си в UNIX struct lnk *prev, *next; } chain[20], *head = chain; add(c) char c; { head->c = c; head->next = head+1; head->next->prev = head; head++; } main(){ char *s = "012345"; while( *s ) add( *s++ ); head->c = '-'; head->next = (struct lnk *)NULL; chain->prev = chain->next; while( head->prev ){ putchar( head->prev->c ); head = head->prev; if( head->next ) head->next->prev = head->next->next; } } 5.12. Напишите программу, составлящую двунаправленный список букв, вводимых с клави- атуры. Конец ввода - буква '\n'. После третьей буквы вставьте букву '+'. Удалите пятую букву. Распечатайте список в обратном порядке. Оформите операции вставки/удаления как функции. Элемент списка должен иметь вид: struct elem{ char letter; /* буква */ char *word; /* слово */ struct elem *prev; /* ссылка назад */ struct elem *next; /* ссылка вперед */ }; struct elem *head, /* первый элемент списка */ *tail, /* последний элемент */ *ptr, /* рабочая переменная */ *prev; /* предыдущий элемент при просмотре */ int c, cmp; ... while((c = getchar()) != '\n' ) Insert(c, tail); for(ptr=head; ptr != NULL; ptr=ptr->next) printf("буква %c\n", ptr->letter); Память лучше отводить не из массива, а функцией calloc(), которая аналогична функции malloc(), но дополнительно расписывает выделенную память байтом '\0' (0, NULL). Вот функции вставки и удаления: extern char *calloc(); /* создать новое звено списка для буквы c */ struct elem *NewElem(c) char c; { struct elem *p = (struct elem *) calloc(1, sizeof(struct elem)); /* calloc автоматически обнуляет все поля, * в том числе prev и next */ p->letter = c; return p; } А. Богатырев, 1992-95 - 180 - Си в UNIX /* вставка после ptr (обычно - после tail) */ Insert(c, ptr) char c; struct elem *ptr; { struct elem *newelem = NewElem(c), *right; if(head == NULL){ /* список был пуст */ head=tail=newelem; return; } right = ptr->next; ptr->next = newelem; newelem->prev = ptr; newelem->next = right; if( right ) right->prev = newelem; else tail = newelem; } /* удалить ptr из списка */ Delete( ptr ) struct elem *ptr; { struct elem *left=ptr->prev, *right=ptr->next; if( right ) right->prev = left; if( left ) left->next = right; if( tail == ptr ) tail = left; if( head == ptr ) head = right; free((char *) ptr); }

слов


struct elem *NewElem(char *s) { struct elem *p = (struct elem *) calloc(1, sizeof(struct elem)); p->word = strdup(s); return p; } void DeleteElem(struct elem *ptr){ free(ptr->word); free(ptr); }

алфавитном порядке


функцию strcmp(), просматривайте список так: struct elem *newelem; if (head == NULL){ /* список пуст */

новое


return; } /* поиск места в списке */ for(cmp= -1, ptr=head, prev=NULL; ptr; prev=ptr, ptr=ptr->next )

новое


break;

cmp


ptr


новое


prev


ptr


head ==> "a" ==> "b" ==> "d" ==> NULL | | prev "c" ptr А. Богатырев, 1992-95 - 181 - Си в UNIX if(cmp == 0) return; /* слово уже есть */

новое


if(prev == NULL){ /* в начало */ newelem->next = head; newelem->prev = NULL; head->prev = newelem; head = newelem; } else if(ptr == NULL){ /* в конец */ newelem->next = NULL; newelem->prev = tail; tail->next = newelem; tail = newelem; } else { /* между prev и ptr */ newelem->next = ptr; newelem->prev = prev; prev->next = newelem; ptr ->prev = newelem; } 5.13. Напишите функции для работы с комплексными числами struct complex { double re, im; }; Например, сложение выглядит так: struct complex add( c1, c2 ) struct complex c1, c2; { struct complex sum; sum.re = c1.re + c2.re; sum.im = c1.im + c2.im; return sum; } struct complex a = { 12.0, 14.0 }, b = { 13.0, 2.0 }; main(){ struct complex c; c = add( a, b ); printf( "(%g,%g)\n", c.re, c.im ); } 5.14. Массивы в Си нельзя присваивать целиком, зато структуры - можно. Иногда используют такой трюк: структуру из единственного поля-массива typedef struct { int ai[5]; } intarray5; intarray5 a, b = { 1, 2, 3, 4, 5 }; и теперь законно a = b; Зато доступ к ячейкам массива выглядит теперь менее изящно: А. Богатырев, 1992-95 - 182 - Си в UNIX a.ai[2] = 14; for(i=0; i < 5; i++) printf( "%d\n", a.ai[i] );

копию


Даже если мы напишем: typedef int ARR16[16]; ARR16 d; void f(ARR16 a){ printf( "%d %d\n", a[3], a[15]); a[3] = 2345; } void main(void){ d[3] = 9; d[15] = 98; f(d); printf("Now it is %d\n", d[3]); }

адрес


a


это можно, использовав тот же трюк, поскольку при передаче структуры в качестве пара- метра передается уже не ее адрес, а копия всей структуры (как это и принято в Си во всех случаях, кроме массивов).

битовые поля


часть


число


информации в структурах (для экономии места). struct XYZ { /* битовые поля должны быть unsigned */ unsigned x:2; /* 0 .. 2**2 - 1 */ unsigned y:5; /* 0 .. 2**5 - 1 */ unsigned z:1; /* YES=1 NO=0 */ } xyz; main(){ printf("%u\n", sizeof(xyz)); /* == sizeof(int) */ xyz.z = 1; xyz.y = 21; xyz.x = 3; printf("%u %u %u\n", xyz.x, ++xyz.y, xyz.z); /* Значение битового поля берется по модулю

число


*/ xyz.y = 32 /* максимум */ + 7; xyz.x = 16+2; xyz.z = 11; printf("%u %u %u\n", xyz.x, xyz.y, xyz.z); /* 2 7 1 */ } Поле ширины 1 часто используется в качестве битового флага: вместо #define FLAG1 01 #define FLAG2 02 #define FLAG3 04

x


x


используется struct flags {

flag1


x


x


А. Богатырев, 1992-95 - 183 - Си в UNIX Следует однако учесть, что машинный код для работы с битовыми полями более сложен и занимает больше команд (т.е. медленнее и длиннее). К битовым полям нельзя применить операцию взятия адреса "&", у них нет адресов и смещений! 5.16. Пример на использование структур с полем переменного размера. Часть перемен- ной длины может быть лишь одна и обязана быть последним полем структуры. Внимание: это программистский трюк, использовать осторожно! #include <stdio.h> #define SZ 5 extern char *malloc(); #define VARTYPE char struct obj { struct header { /* постоянная часть */ int cls; int size; /* размер переменной части */ } hdr; VARTYPE body [1]; /* часть переменного размера: в описании ровно ОДИН элемент массива */ } *items [SZ]; /* указатели на структуры */ #define OFFSET(field, ptr) ((char *) &ptr->field - (char *)ptr) int body_offset; /* создание новой структуры */ struct obj *newObj( int cl, char *s ) { char *ptr; struct obj *op; int n = strlen(s); /* длина переменной части (штук VARTYPE) */ int newsize = sizeof(struct header) + n * sizeof(VARTYPE); printf("[n=%d newsize=%d]\n", n, newsize); /* newsize = (sizeof(struct obj) - sizeof(op->body)) + n * sizeof(op->body); При использовании этого размера не учитывается, что struct(obj) выровнена на границу sizeof(int). Но в частности следует учитывать и то, на границу чего выровнено начало поля op->body. То есть самым правильным будет newsize = body_offset + n * sizeof(op->body); */ /* отвести массив байт без внутренней структуры */ ptr = (char *) malloc(newsize); /* наложить поверх него структуру */ op = (struct obj *) ptr; op->hdr.cls = cl; op->hdr.size = n; strncpy(op->body, s, n); return op; } А. Богатырев, 1992-95 - 184 - Си в UNIX void printobj( struct obj *p ) { register i; printf( "OBJECT(cls=%d,size=%d)\n", p->hdr.cls, p->hdr.size); for(i=0; i < p->hdr.size; i++ ) putchar( p->body[i] ); putchar( '\n' ); } char *strs[] = { "a tree", "a maple", "an oak", "the birch", "the fir" }; int main(int ac, char *av[]){ int i; printf("sizeof(struct header)=%d sizeof(struct obj)=%d\n", sizeof(struct header), sizeof(struct obj)); { struct obj *sample; printf("offset(cls)=%d\n", OFFSET(hdr.cls, sample)); printf("offset(size)=%d\n", OFFSET(hdr.size, sample)); printf("offset(body)=%d\n", body_offset = OFFSET(body, sample)); } for( i=0; i < SZ; i++ ) items[i] = newObj( i, strs[i] ); for( i=0; i < SZ; i++ ){ printobj( items[i] ); free( items[i] ); items[i] = NULL; } return 0; } 5.17. Напишите программу, реализующую список со "старением". Элемент списка, к которому обращались последним, находится в голове списка. Самый старый элемент вытесняется к хвосту списка и в конечном счете из списка удаляется. Такой алгоритм использует ядро UNIX для кэширования блоков файла в оперативной памяти: блоки, к которым часто бывают обращения оседают в памяти (а не на диске). /* Список строк, упорядоченных по времени их добавления в список, * т.е. самая "свежая" строка - в начале, самая "древняя" - в конце. * Строки при поступлении могут и повторяться! По подобному принципу * можно организовать буферизацию блоков при обмене с диском. */ #include <stdio.h> extern char *malloc(), *gets(); #define MAX 3 /* максимальная длина списка */ int nelems = 0; /* текущая длина списка */ struct elem { /* СТРУКТУРА ЭЛЕМЕНТА СПИСКА */ char *key; /* Для блоков - это целое - номер блока */ struct elem *next; /* следующий элемент списка */ /* ... и может что-то еще ... */ } *head; /* голова списка */ void printList(), addList(char *), forget(); А. Богатырев, 1992-95 - 185 - Си в UNIX void main(){ /* Введите a b c d b a c */ char buf[128]; while(gets(buf)) addList(buf), printList(); } /* Распечатка списка */ void printList(){ register struct elem *ptr; printf( "В списке %d элементов\n", nelems ); for(ptr = head; ptr != NULL; ptr = ptr->next ) printf( "\t\"%s\"\n", ptr->key ); } /* Добавление в начало списка */ void addList(char *s) { register struct elem *p, *new; /* Анализ - нет ли уже в списке */ for(p = head; p != NULL; p = p->next ) if( !strcmp(s, p->key)){ /* Есть. Перенести в начало списка */ if( head == p ) return; /* Уже в начале */ /* Удаляем из середины списка */ new = p; /* Удаляемый элемент */ for(p = head; p->next != new; p = p->next ); /* p указывает на предшественника new */ p->next = new->next; goto Insert; } /* Нет в списке */ if( nelems >= MAX ) forget(); /* Забыть старейший */ if((new = (struct elem *) malloc(sizeof(struct elem)))==NULL) goto bad; if((new->key = malloc(strlen(s) + 1)) == NULL) goto bad; strcpy(new->key, s); nelems++; Insert: new->next = head; head = new; return; bad: printf( "Нет памяти\n" ); exit(13); } /* Забыть хвост списка */ void forget(){ struct elem *prev = head, *tail; if( head == NULL ) return; /* Список пуст */ /* Единственный элемент ? */ if((tail = head->next) == NULL){ tail=head; head=NULL; goto Del; } for( ; tail->next != NULL; prev = tail, tail = tail->next ); prev->next = NULL; Del: free(tail->key); free(tail); nelems--; } А. Богатырев, 1992-95 - 186 - Си в UNIX

* 6. Системные вызовы и взаимодействие с UNIX. *


В этой главе речь пойдет о процессах. Скомпилированная программа хранится на диске как обычный нетекстовый файл. Когда она будет загружена в память компьютера и

процессом


UNIX - многозадачная система (мультипрограммная). Это означает, что одновре-

разделения


времени


параллельного


сов (на многопроцессорных машинах параллельность истинная). Процессам, ожидающим некоторого события, время процессора не выделяется. Более того, "спящий" процесс может быть временно откачан (т.е. скопирован из памяти машины) на диск, чтобы освобо- дить память для других процессов. Когда "спящий" процесс дождется события, он будет "разбужен" системой, переведен в ранг "готовых к выполнению" и, если был откачан - будет возвращен с диска в память (но, может быть, на другое место в памяти!). Эта

swapping


одного и того же


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

интерпретатор команд


рамму из файла /bin/csh (или /bin/sh). Процесс представляет собой изолированный "мир", общающийся с другими "мирами" во Вселенной при помощи: a) Аргументов функции main:

argc


Если мы наберем команду

a1 a2 a3


a


argc


argv


argv


argv


argv


программа|-.

envp


в предопределенной переменной extern char **environ; Окружение состоит из строк вида

ИМЯПЕРЕМЕННОЙ


argv


ИМЯ


ИМЯ


значение


c) Открытых файлов. По умолчанию (неявно) всегда открыты 3 канала: ВВОД В Ы В О Д

stdin stdout stderr


fd


связан с клавиатурой дисплеем ____________________ |- Именно это имя показывает команда ps -ef #include <stdio.h> main(ac, av) char **av; { execl("/bin/sleep", "Take it easy", "1000", NULL); } А. Богатырев, 1992-95 - 187 - Си в UNIX Эти каналы достаются процессу "в наследство" от запускающего процесса и связаны с дисплеем и клавиатурой, если только не были перенаправлены. Кроме того, прог- рамма может сама явно открывать файлы (при помощи open, creat, pipe, fopen). Всего программа может одновременно открыть до 20 файлов (считая стандартные каналы), а в некоторых системах и больше (например, 64). В MS DOS есть еще 2

stdaux


stdprn


d) Процесс имеет уникальный номер, который он может узнать вызовом

pid


а также узнать номер "родителя" вызовом

ppid


Процессы могут по этому номеру посылать друг другу сигналы:

pid


и реагировать на них

sig


e) Существуют и другие средства коммуникации процессов: семафоры, сообщения, общая память, сетевые коммуникации. f) Существуют некоторые другие параметры (контекст) процесса: например, его текущий каталог, который достается в наследство от процесса-"родителя", и может быть затем изменен системным вызовом

имя


собственный


MS DOS, где текущий каталог одинаков для всех задач). К "прочим" характеристи-

pgrp


uid


и маски, заданные на различные сигналы; и.т.п. g) Издания других запросов (системных вызовов) к операционной системе ("богу") для выполнения различных "внешних" операций. h) Все остальные действия происходят внутри процесса и никак не влияют на другие процессы и устройства ("миры"). В частности, один процесс НИКАК не может полу- чить доступ к памяти другого процесса, если тот не позволил ему это явно (меха-

shared memory


(равно и пространство ядра изолировано от памяти процессов).

коммуникационной среды


"миры"-процессы, "миры"-внешние устройства (включая терминал пользователя); а также в качестве распорядителя ресурсов "Вселенной", в частности - времени (по очереди выде- ляемого активным процессам) и пространства (в памяти компьютера и на дисках). Мы уже неоднократно упоминали "системные вызовы". Что же это такое? С точки зрения Си-программиста - это обычные функции. В них передают аргументы, они возвра- щают значения. Внешне они ничем не отличаются от написанных нами или библиотечных функций и вызываются из программ одинаковым с ними способом. С точки же зрения реализации - есть глубокое различие. Тело функции-сисвызова расположено не в нашей программе, а в резидентной (т.е. постоянно находящейся в

ядром операционной системы


____________________ |- Собственно, операционная система характеризуется набором предоставляемых ею сис-

только


разным


одинаковый интерфейс


поведение), то это все-таки одна и та же система! Ядра могут не просто отличаться, но и быть построенными на совершенно различных принципах: так обстоит дело с UNIX-ами на однопроцессорных и многопроцессорных машинах. Но для нас ядро - это "черный

поведением


фор


маты данных


в


физической памяти


цессора). Как правило, программа пишется так, чтобы использовать соглашения, приня- тые в данной системе, для чего она просто включает ряд стандартных include-файлов с

Имена


А. Богатырев, 1992-95 - 188 - Си в UNIX Сам термин "системный вызов" как раз означает "вызов системы для выполнения дейст-

привелегированном режиме


в котором имеет доступ к некоторым системным таблицам|=, регистрам и портам внешних устройств и диспетчера памяти, к которым обычным программам доступ аппаратно запрещен (в отличие от MS DOS, где все таблицы ядра доступны пользовательским программам, что создает раздолье для вирусов). Системный вызов происходит в 2 этапа: сначала в поль- зовательской программе вызывается библиотечная функция-"корешок", тело которой напи-

программного прерывания


ное отличие от нормальных Си-функций - вызов по прерыванию. Вторым этапом является реакция ядра на прерывание: 1. переход в привелегированный режим; 2. разбирательство, КТО обратился к ядру, и подключение u-area этого процесса к

context switching


3. извлечение аргументов из памяти запросившего процесса;

номер


системного вызова); 5. проверка корректности остальных аргументов; 6. проверка прав процесса на допустимость выполнения такого запроса; 7. вызов тела требуемого системного вызова - это обычная Си-функция в ядре; 8. возврат ответа в память процесса; 9. выключение привелегированного режима; 10. возврат из прерывания. Во время системного вызова (шаг 7) процесс может "заснуть", дожидаясь некоторого события (например, нажатия кнопки на клавиатуре). В это время ядро передаст управле- ние другому процессу. Когда наш процесс будет "разбужен" (событие произошло) - он продолжит выполнение шагов системного вызова. Большинство системных вызовов возвращают в программу в качестве своего значения признак успеха: 0 - все сделано, (-1) - сисвызов завершился неудачей; либо некоторое содержательное значение при успехе (вроде дескриптора файла в open(), и (-1) при неу- даче. В случае неудачного завершения в предопределенную переменную errno заносится номер ошибки, описывающий причину неудачи (коды ошибок предопределены, описаны в

errno


не изменяется


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

errno


extern int errno;

_


value


value


_


errno );

errno


} ____________________ Поведение всех программ в системе вытекает из поведения системных вызовов, кото- рыми они пользуются. Даже то, что UNIX является многозадачной системой, непосредст- венно вытекает из наличия системных вызовов fork, exec, wait и спецификации их функ- ционирования! То же можно сказать про язык Си - мобильность программы зависит в основном от

библиотечных функций


стандарту


темы предоставляют все эти функции (которые могут быть по-разному реализованы, но должны делать одно и то же), то программа будет компилироваться и работать в обоих

одинаково


|= Таким как таблица процессов, таблица открытых файлов (всех вместе и для каждого процесса), и.т.п. А. Богатырев, 1992-95 - 189 - Си в UNIX

_


строки-расшифровку смысла ошибок (по-английски). Посмотрите описание функции per- ror().

6.1. Файлы и каталоги.


6.1.1. Используя системный вызов stat, напишите программу, определяющую тип файла: обычный файл, каталог, устройство, FIFO-файл. Ответ:

sys


sys


name


type


name


name


return 0; }

st


type


_


printf( "Обычный файл размером %ld байт\n",

st


_


printf( "Каталог\n" ); break;

_


_


printf( "Устройство\n" ); break;

_


printf( "FIFO-файл\n" ); break; default: printf( "Другой тип\n" ); break;

type


} 6.1.2. Напишите программу, печатающую: свои аргументы, переменные окружения, инфор- мацию о всех открытых ею файлах и используемых трубах. Для этой цели используйте системный вызов

st


fd


used


... } Программа может использовать дескрипторы файлов с номерами 0..NOFILE-1 (обычно

fd


дескриптор не связан с открытым файлом (т.е. не используется). NOFILE определено в

sys


6.1.3. Напишите упрощенный аналог команды ls, распечатывающий содержимое текущего каталога (файла с именем ".") без сортировки имен по алфавиту. Предусмотрите чтение каталога, чье имя задается как аргумент программы. Имена "." и ".." не выдавать.

sys


лядит так: каталог - это файл, состоящий из структур direct, каждая описывает одно имя файла, входящего в каталог: А. Богатырев, 1992-95 - 190 - Си в UNIX struct direct {

d


d


}; В семействе BSD формат каталога несколько иной - там записи имеют разную длину, зави- сящую от длины имени файла, которое может иметь длину от 1 до 256 символов.

любых


конца имени и '/', служащего разделителем. В имени допустимы пробелы, управляющие

любое


единственная


даже непечатные (т.е. управляющие) символы! Если имя файла имеет длину 14 (DIRSIZ)

не оканчивается


возможны три подхода: 1. Выводить символы при помощи putchar()-а в цикле. Цикл прерывать по индексу рав- ному DIRSIZ, либо по достижению байта '\0'.

d


buf


buf


buf


Этот способ лучший, если имя файла надо не просто напечатать, но и запомнить на будущее, чтобы использовать в своей программе. 3. Использовать такую особенность функции printf():

sys


sys


struct direct d; ...

d


d


поэтому I-узлы нумеруются начиная с 1, а не с 0). При удалении файла содержимое его (блоки) уничтожается, I-узел освобождается, но имя в каталоге не затирается физи-

d


d


уже уничтоженных файлов. При создании нового имени (creat, link, mknod) система просматривает каталог и

d


сывая новое имя в него (только в этот момент старое имя-призрак окончательно исчезнет физически). Если пустых мест нет - каталог удлиняется.

всегда


каталог (на его собственный I-node), ".." - на вышележащий каталог. У корневого

d


Имя каталога


...

читать


никто


только ядро


на запросы в виде системных вызовов creat, unlink, link, mkdir, rmdir, rename, mknod. Коды доступа для каталога интерпретируются следующим образом:

запись


_


____________________

superuser


тель, который имеет право делать ВСЕ. Ему доступны любые сисвызовы и файлы, несмотря на коды доступа и.т.п. А. Богатырев, 1992-95 - 191 - Си в UNIX помощи этих вызовов. То есть: право создавать, удалять и переименовывать файлы в каталоге. Отметим, что для переименования или удаления файла вам не требуется иметь доступ по записи к самому файлу - достаточно иметь доступ по записи к каталогу, содержащему его имя!

чтение


_


ниже): благодаря этому мы можем получить список имен файлов, содержащихся в каталоге. Однако, если мы ЗАРАНЕЕ знаем имена файлов в каталоге, мы МОЖЕМ рабо-

выполнение


выполнение


_


файла, перехода в другой каталог (chdir), система выполняет следующие действия (осуществляемые функцией namei() в ядре): чтение каталога и поиск в нем указан- ного имени файла или каталога; найденному имени соответствует номер I-узла

d


выполнение


такого просмотра каталога системой. Если каталог имеет доступ на чтение - мы можем получить список файлов (т.е. применить команду ls); но если он при этом не имеет кода доступа "выполнение" - мы не сможем получить доступа ни к одному из файлов каталога (ни открыть, ни удалить, ни создать, ни сделать stat, ни chdir).

чтение


выполнение


еще более точно - к I-nodам файлов этого каталога.

sticky bit


_


данном каталоге могут только: владелец каталога, владелец данного файла, супер- пользователь. И никто другой. Это исключает удаление файлов чужими. Совет: для каталога полезно иметь такие коды доступа:

каталог


В системах BSD используется, как уже было упомянуто, формат каталога с переменной длиной записей. Чтобы иметь удобный доступ к именам в каталоге, возникли специальные функции чтения каталога: opendir, closedir, readdir. Покажем, как простейшая команда ls реализуется через эти функции. А. Богатырев, 1992-95 - 192 - Си в UNIX #include <stdio.h> #include <sys/types.h> #include <dirent.h>

dirname


dirbuf


fddir


_


fddir


stderr


return 1; } /* Без сортировки по алфавиту */

dirbuf


dirbuf


dirbuf


dot


continue;

dirbuf


dotdot


continue;

dirbuf


}

fddir


dot


dotdot


dot


return 0; }

ac


i


ac


else listdir("."); return 0; }

_


кольку его предоставляет нам сама функция readdir().

argv


stat, чтобы определить тип файла (файл/каталог). Программа должна отказываться уда- лять файлы устройств. Для удаления пустого каталога (не содержащего иных имен, кроме "." и "..") сле- дует использовать сисвызов

имя


errno


(не каталогов)

имя


Программа должна запрашивать подтверждение на удаление каждого файла, выдавая его имя, тип, размер в килобайтах и вопрос "удалить ?". 6.1.5. Напишите функцию рекурсивного обхода дерева подкаталогов и печати имен всех файлов в нем. Ключ U42 означает файловую систему с длинными именами файлов (BSD 4.2). А. Богатырев, 1992-95 - 193 - Си в UNIX /*#!/bin/cc -DFIND -DU42 -DMATCHONLY treemk.c match.c -o tree -lx * Обход поддерева каталогов (по мотивам Керниган & Ритчи). * Ключи компиляции: * BSD-4.2 BSD-4.3 -DU42 * XENIX с канонической файл.сист. ничего * XENIX с библиотекой -lx -DU42 * программа поиска файлов -DFIND * программа рекурсивного удаления -DRM_REC * программа подсчета используемого места на диске БЕЗ_КЛЮЧА */ #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/param.h> /* для MAXPATHLEN */ #if defined(M_XENIX) && defined(U42) # include <sys/ndir.h> /* XENIX + U42 эмуляция */ #else # include <dirent.h> # define stat(f,s) lstat(f,s) /* не проходить по символьным ссылкам */ # define d_namlen d_reclen #endif /* проверка: каталог ли это */ #define isdir(st) ((st.st_mode & S_IFMT) == S_IFDIR) struct stat st; /* для сисвызова stat() */ char buf[MAXPATHLEN+1]; /* буфер для имени файла */ #define FAILURE (-1) /* код неудачи */ #define SUCCESS 1 /* код успеха */ #define WARNING 0 /* нефатальная ошибка */ /* Сообщения об ошибках во время обхода дерева: */ #ifndef ERR_CANT_READ # define ERR_CANT_READ(name) \ fprintf( stderr, "\tНе могу читать \"%s\"\n", name), WARNING # define ERR_NAME_TOO_LONG() \ fprintf( stderr, "\tСлишком длинное полное имя\n" ), WARNING #endif /* Прототипы для предварительного объявления функций. */ extern char *strrchr(char *, char); int directory (char *name, int level, int (*enter)(char *full, int level, struct stat *st), int (*leave)(char *full, int level), int (*touch)(char *full, int level, struct stat *st)); /* Функции-обработчики enter, leave, touch должны * возвращать (-1) для прерывания просмотра дерева, * либо значение >= 0 для продолжения. */ А. Богатырев, 1992-95 - 194 - Си в UNIX /* Обойти дерево с корнем в rootdir */ int walktree ( char *rootdir, /* корень дерева */ int (*enter)(char *full, int level, struct stat *st), int (*leave)(char *full, int level), int (*touch)(char *full, int level, struct stat *st) ){ /* проверка корректности корня */ if( stat(rootdir, &st) < 0 || !isdir(st)){ fprintf( stderr, "\tПлохой корень дерева \"%s\"\n", rootdir ); return FAILURE; /* неудача */ } strcpy (buf, rootdir); return act (buf, 0, enter, leave, touch); } /* Оценка файла с именем name. */ int act (char *name, int level, int (*enter)(char *full, int level, struct stat *st), int (*leave)(char *full, int level), int (*touch)(char *full, int level, struct stat *st)) { if (stat (name, &st) < 0) return WARNING; /* ошибка, но не фатальная */ if(isdir(st)){ /* позвать обработчик каталогов */ if(enter) if( enter(name, level, &st) == FAILURE ) return FAILURE; return directory (name, level+1, enter, leave, touch); } else { /* позвать обработчик файлов */ if(touch) return touch (name, level, &st); else return SUCCESS; } } А. Богатырев, 1992-95 - 195 - Си в UNIX /* Обработать каталог: прочитать его и найти подкаталоги */ int directory (char *name, int level, int (*enter)(char *full, int level, struct stat *st), int (*leave)(char *full, int level), int (*touch)(char *full, int level, struct stat *st)) { #ifndef U42 struct direct dirbuf; int fd; #else register struct dirent *dirbuf; DIR *fd; extern DIR *opendir(); #endif char *nbp, *tail, *nep; int i, retcode = SUCCESS; #ifndef U42 if ((fd = open (name, 0)) < 0) { #else if ((fd = opendir (name)) == NULL) { #endif return ERR_CANT_READ(name); } tail = nbp = name + strlen (name); /* указатель на закрывающий \0 */ if( strcmp( name, "/" )) /* если не "/" */ *nbp++ = '/'; *nbp = '\0'; #ifndef U42 if (nbp + DIRSIZ + 2 >= name + MAXPATHLEN) { *tail = '\0'; return ERR_NAME_TOO_LONG(); } #endif #ifndef U42 while (read(fd, (char *) &dirbuf, sizeof(dirbuf)) == sizeof(dirbuf)){ if (dirbuf.d_ino == 0) /* стертый файл */ continue; if (strcmp (dirbuf.d_name, "." ) == 0 || strcmp (dirbuf.d_name, "..") == 0) /* не интересуют */ continue; for (i = 0, nep = nbp; i < DIRSIZ; i++) *nep++ = dirbuf.d_name[i]; # else /*U42*/ while ((dirbuf = readdir (fd)) != NULL ) { if (dirbuf->d_ino == 0) continue; if (strcmp (dirbuf->d_name, "." ) == 0 || strcmp (dirbuf->d_name, "..") == 0) continue; for (i = 0, nep = nbp; i < dirbuf->d_namlen ; i++) *nep++ = dirbuf->d_name[i]; #endif /*U42*/ *nep = '\0'; if( act(name, level, enter, leave, touch) == FAILURE) { retcode = FAILURE; break; } } А. Богатырев, 1992-95 - 196 - Си в UNIX #ifndef U42 close (fd); #else closedir(fd); #endif *tail = '\0'; /* восстановить старое name */ if(retcode != FAILURE && leave) if( leave(name, level) == FAILURE) retcode = FAILURE; return retcode; } /* -------------------------------------------------------------- */ /* Disk Usage -- Оценка места, занимаемого файлами поддерева */ /* -------------------------------------------------------------- */ /* Пересчет байтов в килобайты */ #define KB(s) (((s)/1024L) + ((s)%1024L ? 1L:0L)) /* или #define KB(s) (((s) + 1024L - 1) / 1024L) */ long size; /* общий размер */ long nfiles; /* всего файлов */ long ndirs; /* из них каталогов */ #define WARNING_LIMIT 150L /* подозрительно большой файл */

_


long sz; size += (sz = KB(st->st_size)); /* размер файла в Кб. */ nfiles++; #ifndef TREEONLY if( sz >= WARNING_LIMIT ) fprintf(stderr,"\tВнимание! \"%s\" очень большой: %ld Кб.\n", name, sz); #endif /*TREEONLY*/ return SUCCESS; }

_


#ifndef TREEONLY fprintf( stderr, "Каталог \"%s\"\n", name ); #endif size += KB(st->st_size); /* размер каталога в Кб. */ nfiles++; ++ndirs; return SUCCESS; } long du (char *name){ size = nfiles = ndirs = 0L; walktree(name, du_enter, NULL, du_touch ); return size; } А. Богатырев, 1992-95 - 197 - Си в UNIX /* -------------------------------------------------------------- */ /* Рекурсивное удаление файлов и каталогов */ /* -------------------------------------------------------------- */ int deleted; /* сколько файлов и каталогов удалено */

_


if( rmdir(name) >= 0){ deleted++; return SUCCESS; } fprintf(stderr, "Не могу rmdir '%s'\n", name); return WARNING; }

_


if( unlink(name) >= 0){ deleted++; return SUCCESS; } fprintf(stderr, "Не могу rm '%s'\n", name); return WARNING; } int recrmdir(char *name){ int ok_code; deleted = 0; ok_code = walktree(name, NULL, recrm_dir, recrm_file); printf("Удалено %d файлов и каталогов в %s\n", deleted, name); return ok_code; } /* -------------------------------------------------------------- */ /* Поиск файлов с подходящим именем (по шаблону имени) */ /* -------------------------------------------------------------- */ char *find_PATTERN;

_


char *basename = strrchr(fullname, '/'); if(basename) basename++; else basename = fullname; if( match(basename, find_PATTERN)) printf("Level#%02d %s\n", level, fullname); if( !strcmp( basename, "core")){ printf("Найден дамп %s, поиск прекращен.\n", fullname); return FAILURE; } return SUCCESS; } void find (char *root, char *pattern){ find_PATTERN = pattern; walktree(root, find_check, NULL, find_check); } А. Богатырев, 1992-95 - 198 - Си в UNIX /* -------------------------------------------------------------- */ #ifndef TREEONLY void main(int argc, char *argv[]){ #ifdef FIND if(argc != 3){ fprintf(stderr, "Arg count\n"); exit(1); } find(argv[1], argv[2]); #else # ifdef RM_REC for(argv++; *argv; argv++) recrmdir(*argv); # else du( argc == 1 ? "." : argv[1] ); printf( "%ld килобайт в %ld файлах.\n", size, nfiles ); printf( "%ld каталогов.\n", ndirs ); # endif #endif exit(0); } #endif /*TREEONLY*/ 6.1.6. Используя предыдущий алгоритм, напишите программу рекурсивного копирования поддерева каталогов в другое место. Для создания новых каталогов используйте систем- ный вызов

имя


6.1.7. Используя тот же алгоритм, напишите программу удаления каталога, которая уда- ляет все файлы в нем и, рекурсивно, все его подкаталоги. Таким образом, удаляется дерево каталогов. В UNIX подобную операцию выполняет команда

имя


6.1.8. Используя все тот же алгоритм обхода, напишите аналог команды find, который будет позволять: - находить все файлы, чьи имена удовлетворяют заданному шаблону (используйте функ- цию match() из главы "Текстовая обработка");

_


st


Как уже ясно, следует пользоваться вызовом stat для проверки каждого файла.

6.2. Время в UNIX.


6.2.1. Напишите функцию, переводящую год, месяц, день, часы, минуты и секунды в число секунд, прошедшее до указанного момента с 00 часов 00 минут 00 секунд 1 Января

_


Эта функция облегчит вам сравнение двух моментов времени, заданных в общеприня- том "человеческом" формате, поскольку сравнить два long числа гораздо проще, чем сравнивать по очереди годы, затем, если они равны - месяцы, если месяцы равны - даты, и.т.д.; а также облегчит измерение интервала между двумя событиями - он вычисляется просто как разность двух чисел. В системе UNIX время обрабатывается и хранится именно в виде числа секунд; в частности текущее астрономическое время можно узнать системным вызовом

sys


time


_


Функция

tm


А. Богатырев, 1992-95 - 199 - Си в UNIX разлагает число секунд на отдельные составляющие, содержащиеся в int-полях структуры:

_


_


_


_


_


_


_


_


Номера месяца и дня недели начинаются с нуля, чтобы вы могли использовать их в качестве индексов:

months


months


Пример использования этих функций есть в приложении. Установить время в системе может суперпользователь вызовом

t


6.2.2. Напишите функцию печати текущего времени в формате ЧЧ:ММ:СС ДД-МЕС-ГГ. Используйте системный вызов time() и функцию localtime(). Существует стандартная функция ctime(), которая печатает время в формате: /* Mon Mar 25 18:56:36 1991 */ #include <stdio.h> #include <time.h> main(){ /* команда date */

t


s


s


}

s


6.2.3. Структура stat, заполняемая системным вызовом stat(), кроме прочих полей

_


содержимого I-узла файла, время последнего изменения файла и время последнего доступа к файлу.

st


мени) при применении к файлу вызовов creat, chmod, chown, link, unlink, mknod, utime|-, write (т.к. изменяется длина файла); Это поле следует рассматривать как время модификации прав доступа к файлу;

st


модификации содержимого файла (данных);

st


чтения содержимого файла (данных). Модифицируйте функцию typeOf(), чтобы она печатала еще и эти даты. ____________________ |- Время модификации файла можно изменить на текущее астрономическое время и не производя записи в файл. Для этого используется вызов

имяФайла


Он используется для взаимодействия с программой make - в команде touch. Изменить время можно только своему файлу. А. Богатырев, 1992-95 - 200 - Си в UNIX 6.2.4. Напишите аналог команды ls -tm, выдающей список имен файлов текущего ката-

st


файлы выдаются первыми. Для каждого прочитанного из каталога имени надо сделать stat; имена файлов и времена следует сохранить в массиве структур, а затем отсортиро- вать его. 6.2.5. Напишите аналогичную программу, сортирующую файлы в порядке возрастания их

st


6.2.6. Напишите аналог команды ls -l, выдающий имена файлов каталога и их коды дос-

rwxrw


имяФайла


кодыДоступа


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

имя


Можно изменять коды доступа, соответствующие битовой маске

_


sys


нить коды доступа к файлу может только его владелец.

d


stat. 6.2.7. Вот программа, которая каждые 2 секунды проверяет - не изменилось ли содержи- мое текущего каталога: #include <sys/types.h> #include <sys/stat.h> extern char *ctime(); main(){ time_t last; struct stat st; for( stat(".", &st), last=st.st_mtime; ; sleep(2)){ stat(".", &st); if(last != st.st_mtime){ last = st.st_mtime; printf("Был создан или удален какой-то файл: %s", ctime(&last)); } } }

какое


этого надо при запуске программы прочитать и запомнить содержимое каталога, а при обнаружении модификации - перечитать каталог и сравнить его с прежним содержимым). 6.2.8. Напишите по аналогии программу, которая выдает сообщение, если указанный вами файл был кем-то прочитан, записан или удален. Вам следует отслеживать изменение полей

st


завершается. 6.2.9. Современные UNIX-машины имеют встроенные таймеры (как правило несколько) с довольно высоким разрешением. Некоторые из них могут использоваться как "будильники" с обратным отсчетом времени: в таймер загружается некоторое значение; таймер ведет обратный отсчет, уменьшая загруженный счетчик; как только это время истекает - посы- лается сигнал процессу, загрузившему таймер. А. Богатырев, 1992-95 - 201 - Си в UNIX Вот как, к примеру, выглядит функция задержки в микросекундах (миллионных долях секунды). Примечание: эту функцию не следует использовать вперемежку с функциями sleep и alarm (смотри статью про них ниже, в главе про сигналы). #include <sys/types.h> #include <signal.h> #include <sys/time.h> void do_nothing() {}

usec


usec


struct itimerval new, old; /* struct itimerval содержит поля: struct timeval it_interval; struct timeval it_value; Где struct timeval содержит поля: long tv_sec; -- число целых секунд long tv_usec; -- число микросекунд */ struct sigaction new_vec, old_vec; if (usec == 0) return; /* Поле tv_sec содержит число целых секунд. Поле tv_usec содержит число микросекунд. it_value - это время, через которое В ПЕРВЫЙ раз таймер "прозвонит", то есть пошлет нашему процессу сигнал SIGALRM. Время, равное нулю, немедленно остановит таймер. it_interval - это интервал времени, который будет загружаться в таймер после каждого "звонка" (но не в первый раз). Время, равное нулю, остановит таймер после его первого "звонка". */ new.it_interval.tv_sec = 0; new.it_interval.tv_usec = 0; new.it_value.tv_sec = usec / 1000000; new.it_value.tv_usec = usec % 1000000; А. Богатырев, 1992-95 - 202 - Си в UNIX /* Сохраняем прежнюю реакцию на сигнал SIGALRM в old_vec, заносим в качестве новой реакции do_nothing() */ new_vec.sa_handler = do_nothing; sigemptyset(&new_vec.sa_mask); new_vec.sa_flags = 0; sighold(SIGALRM); sigaction(SIGALRM, &new_vec, &old_vec); /* Загрузка интервального таймера значением new, начало отсчета. * Прежнее значение спасти в old. * Вместо &old можно также NULL - не спасать. */

_


/* Ждать прихода сигнала SIGALRM */ sigpause(SIGALRM); /* Восстановить реакцию на SIGALRM */ sigaction(SIGALRM, &old_vec, (struct sigaction *) 0); sigrelse(SIGALRM); /* Восстановить прежние параметры таймера */ setitimer(ITIMER_REAL, &old, (struct itimerval *) 0); } 6.2.10. Второй пример использования таймера - это таймер, отсчитывающий текущее время суток (а также дату). Чтобы получить значение этого таймера используется вызов функции gettimeofday #include <time.h> void main(){

timenow


timenow


printf("%u sec, %u msec\n", timenow.tv_sec, timenow.tv_usec ); printf("%s", ctime(&timenow.tv_sec)); exit(0); }

_


момента; в чем полностью соответствует системному вызову time. Однако плюс к тому

_


всегда меньше 1000000). 6.2.11. К данному параграфу вернитесь, изучив раздел про fork() и exit(). Каждый процесс может пребывать в двух фазах: системной (внутри тела системного вызова - его выполняет для нас ядро операционной системы) и пользовательской (внутри кода самой программы). Время, затраченное процессом в каждой фазе, может быть измеряно системным вызовом times(). Кроме того, этот вызов позволяет узнать суммарное время, затраченное порожденными процессами (порожденными при помощи fork). Системный вызов заполняет структуру А. Богатырев, 1992-95 - 203 - Си в UNIX struct tms {

_


_


_


_


}; и возвращает значение #include <sys/times.h>

time


real


Все времена измеряются в "тиках" - некоторых долях секунды. Число тиков в секунде можно узнать таким системным вызовом (в системе Solaris): #include <unistd.h>

HZ


В старых системах, где таймер работал от сети переменного тока, это число получалось равным 60 (60 Герц - частота сети переменного тока). В современных системах это 100. Поля структуры содержат:

_


время, затраченное вызывающим процессом в пользовательской фазе.

_


время, затраченное вызывающим процессом в системной фазе.

_


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

tms


ние).

_


время, затраченное порожденными процессами в системной фазе: оно равно сумме

tms


_


время, соответствующее астрономическому времени системы. Имеет смысл мерять только их разность. Вот пример программы: #include <stdio.h> #include <unistd.h> /* _SC_CLK_TCK */ #include <signal.h> /* SIGALRM */ #include <sys/time.h> /* не используется */ #include <sys/times.h> /* struct tms */ struct tms tms_stop, tms_start; clock_t real_stop, real_start; clock_t HZ; /* число ticks в секунде */ А. Богатырев, 1992-95 - 204 - Си в UNIX /* Засечь время момента старта процесса */ void hello(void){ real_start = times(&tms_start); } /* Засечь время окончания процесса */ void bye(int n){ real_stop = times(&tms_stop); #ifdef CRONO /* Разность времен */ tms_stop.tms_utime -= tms_start.tms_utime; tms_stop.tms_stime -= tms_start.tms_stime; #endif /* Распечатать времена */ printf("User time = %g seconds [%lu ticks]\n", tms_stop.tms_utime / (double)HZ, tms_stop.tms_utime); printf("System time = %g seconds [%lu ticks]\n", tms_stop.tms_stime / (double)HZ, tms_stop.tms_stime); printf("Children user time = %g seconds [%lu ticks]\n", tms_stop.tms_cutime / (double)HZ, tms_stop.tms_cutime); printf("Children system time = %g seconds [%lu ticks]\n", tms_stop.tms_cstime / (double)HZ, tms_stop.tms_cstime); printf("Real time = %g seconds [%lu ticks]\n", (real_stop - real_start) / (double)HZ, real_stop - real_start); exit(n); } /* По сигналу SIGALRM - завершить процесс */ void onalarm(int nsig){ printf("Выход #%d ================\n", getpid()); bye(0); } /* Порожденный процесс */ void dochild(int n){ hello(); printf("Старт #%d ================\n", getpid()); signal(SIGALRM, onalarm); /* Заказать сигнал SIGALRM через 1 + n*3 секунд */ alarm(1 + n*3); for(;;){} /* зациклиться в user mode */ } А. Богатырев, 1992-95 - 205 - Си в UNIX #define NCHLD 4 int main(int ac, char *av[]){ int i; /* Узнать число тиков в секунде */ HZ = sysconf(_SC_CLK_TCK); setbuf(stdout, NULL); hello(); for(i=0; i < NCHLD; i++) if(fork() == 0) dochild(i); while(wait(NULL) > 0); printf("Выход MAIN =================\n"); bye(0); return 0; } и ее выдача: Старт #3883 ================ Старт #3884 ================ Старт #3885 ================ Старт #3886 ================ Выход #3883 ================ User time = 0.72 seconds [72 ticks] System time = 0.01 seconds [1 ticks] Children user time = 0 seconds [0 ticks] Children system time = 0 seconds [0 ticks] Real time = 1.01 seconds [101 ticks] Выход #3884 ================ User time = 1.88 seconds [188 ticks] System time = 0.01 seconds [1 ticks] Children user time = 0 seconds [0 ticks] Children system time = 0 seconds [0 ticks] Real time = 4.09 seconds [409 ticks] Выход #3885 ================ User time = 4.41 seconds [441 ticks] System time = 0.01 seconds [1 ticks] Children user time = 0 seconds [0 ticks] Children system time = 0 seconds [0 ticks] Real time = 7.01 seconds [701 ticks] Выход #3886 ================ User time = 8.9 seconds [890 ticks] System time = 0 seconds [0 ticks] Children user time = 0 seconds [0 ticks] Children system time = 0 seconds [0 ticks] Real time = 10.01 seconds [1001 ticks] Выход MAIN ================= User time = 0.01 seconds [1 ticks] System time = 0.04 seconds [4 ticks] Children user time = 15.91 seconds [1591 ticks] Children system time = 0.03 seconds [3 ticks] Real time = 10.41 seconds [1041 ticks] Обратите внимание, что 72+188+441+890=1591 (поле tms_cutime для main). 6.2.12. Еще одна программа: хронометрирование выполнения другой программы. Пример: timer ls -l А. Богатырев, 1992-95 - 206 - Си в UNIX /* Хронометрирование выполнения программы */ #include <stdio.h> #include <unistd.h> #include <sys/times.h> extern errno; typedef struct _timeStamp { clock_t real_time; clock_t cpu_time; clock_t child_time; clock_t child_sys, child_user; } TimeStamp; TimeStamp TIME(){ struct tms tms; TimeStamp st; st.real_time = times(&tms); st.cpu_time = tms.tms_utime + tms.tms_stime + tms.tms_cutime + tms.tms_cstime; st.child_time = tms.tms_cutime + tms.tms_cstime; st.child_sys = tms.tms_cstime; st.child_user = tms.tms_cutime; return st; } void PRTIME(TimeStamp start, TimeStamp stop){ clock_t HZ = sysconf(_SC_CLK_TCK); clock_t real_time = stop.real_time - start.real_time; clock_t cpu_time = stop.cpu_time - start.cpu_time; clock_t child_time = stop.child_time - start.child_time; printf("%g real, %g cpu, %g child (%g user, %g sys), %ld%%\n", real_time / (double)HZ, cpu_time / (double)HZ, child_time / (double)HZ, stop.child_user / (double)HZ, stop.child_sys / (double)HZ, (child_time * 100L) / (real_time ? real_time : 1) ); } А. Богатырев, 1992-95 - 207 - Си в UNIX TimeStamp start, stop; int main(int ac, char *av[]){ char *prog = *av++; if(*av == NULL){ fprintf(stderr, "Usage: %s command [args...]\n", prog); return(1); } start = TIME(); if(fork() == 0){ execvp(av[0], av); perror(av[0]); exit(errno); } while(wait(NULL) > 0); stop = TIME(); PRTIME(start, stop); return(0); }

6.3. Свободное место на диске.


6.3.1. Системный вызов ustat() позволяет узнать количество свободного места в файло- вой системе, содержащей заданный файл (в примере ниже - текущий каталог):

sys


sys


ustat


st


ac


file


file


st


printf("На диске %*.*s\n" "%ld свободных блоков (%ld Кб)\n" "%d свободных I-узлов\n",

ust


ust


ust


ust


ust


} Обратите внимание на запись длинной строки в printf: строки, перечисленные последова- тельно, склеиваются ANSI C компилятором в одну длинную строку: char s[] = "This is" " a line " "of words"; совпадает с char s[] = "This is a line of words"; 6.3.2. Более правильно, однако, пользоваться сисвызовом statvfs - статистика по вир- туальной файловой системе. Рассмотрим его в следующем примере: копирование файла с проверкой на наличие свободного места. А. Богатырев, 1992-95 - 208 - Си в UNIX #include <stdio.h> #include <string.h> #include <stdlib.h> #include <stdarg.h> #include <fcntl.h> /* O_RDONLY */ #include <sys/types.h> #include <sys/stat.h> #include <sys/statvfs.h> #include <sys/param.h> /* MAXPATHLEN */ char *progname; /* имя программы */ void error(char *fmt, ...){ va_list args; va_start(args, fmt); fprintf(stderr, "%s: ", progname); vfprintf(stderr, fmt, args); fputc('\n', stderr); va_end(args); } int copyFile(char *to, char *from){ /* куда, откуда */ char newname[MAXPATHLEN+1]; char answer[20]; struct stat stf, stt; int fdin, fdout; int n, code = 0; char iobuf[64 * 1024]; char *dirname = NULL, *s; if((fdin = open(from, O_RDONLY)) < 0){ error("Cannot read %s", from); return (-1); } fstat(fdin, &stf); if((stf.st_mode & S_IFMT) == S_IFDIR){ close(fdin); error("%s is a directory", from); return (-2); } А. Богатырев, 1992-95 - 209 - Си в UNIX if(stat(to, &stt) >= 0){ /* Файл уже существует */ if((stt.st_mode & S_IFMT) == S_IFDIR){ /* И это каталог */ /* Выделить последнюю компоненту пути from */ if((s = strrchr(from, '/')) && s[1]) s++; else s = from; dirname = to; /* Целевой файл - файл в этом каталоге */ sprintf(newname, "%s/%s", to, s); to = newname; if(stat(to, &stt) < 0) goto not_exist; } if(stt.st_dev == stf.st_dev && stt.st_ino == stf.st_ino){ error("%s: cannot copy file to itself", from); return (-3); } switch(stt.st_mode & S_IFMT){ case S_IFBLK: case S_IFCHR: case S_IFIFO: break; default: printf("%s already exists, overwrite ? ", to); fflush(stdout); *answer = '\0'; gets(answer); if(*answer != 'y'){ /* NO */ close(fdin); return (-4); } break; } } А. Богатырев, 1992-95 - 210 - Си в UNIX not_exist: printf("COPY %s TO %s\n", from, to); if((stf.st_mode & S_IFMT) == S_IFREG){ /* Проверка наличия свободного места в каталоге dirname */

fs


char tmpbuf[MAXPATHLEN+1]; if(dirname == NULL){ /* То 'to' - это имя файла, а не каталога */ strcpy(tmpbuf, to); if(s = strrchr(tmpbuf, '/')){ if(*tmpbuf != '/' || s != tmpbuf){ /* Имена "../xxx" * и второй случай: * абсолютные имена не в корне, * то есть не "/" и не "/xxx" */ *s = '\0'; }else{ /* "/" или "/xxx" */ if(s[1]) s[1] = '\0'; } dirname = tmpbuf; } else dirname = "."; }

dirname


size_t size = (geteuid() == 0 ) ? /* Доступно суперпользователю: байт */

fs


/* Доступно обычному пользователю: байт */

fs


if(size < stf.st_size){ error("Not enough free space on %s: have %lu, need %lu", dirname, size, stf.st_size); close(fdin); return (-5); } } } if((fdout = creat(to, stf.st_mode)) < 0){ error("Can't create %s", to); close(fdin); return (-6); } else { fchmod(fdout, stf.st_mode); fchown(fdout, stf.st_uid, stf.st_gid); } А. Богатырев, 1992-95 - 211 - Си в UNIX while (n = read (fdin, iobuf, sizeof iobuf)) { if(n < 0){ error ("read error"); code = (-7); goto done; } if(write (fdout, iobuf, n) != n) { error ("write error"); code = (-8); goto done; } } done: close (fdin); close (fdout); /* Проверить: соответствует ли результат ожиданиям */ if(stat(to, &stt) >= 0 && (stt.st_mode & S_IFMT) == S_IFREG){ if(stf.st_size < stt.st_size){ error("File has grown at the time of copying"); } else if(stf.st_size > stt.st_size){ error("File too short, target %s removed", to); unlink(to); code = (-9); } } return code; } int main(int argc, char *argv[]){ int i, code = 0; progname = argv[0]; if(argc < 3){ error("Usage: %s from... to", argv[0]); return 1; } for(i=1; i < argc-1; i++) code |= copyFile(argv[argc-1], argv[i]) < 0 ? 1 : 0; return code; } Возвращаемая структура struct statvfs содержит такие поля (в частности): Типа long:

f


f


f


f


f


f


f


Типа char *

f


А. Богатырев, 1992-95 - 212 - Си в UNIX По два значения дано потому, что операционная система резервирует часть файловой сис- темы для использования ТОЛЬКО суперпользователем (чтобы администратор смог распихать файлы в случае переполнения диска, и имел резерв на это). ufs - это UNIX file system из BSD 4.x

6.4. Сигналы.


Процессы в UNIX используют много разных механизмов взаимодействия. Одним из них

сигналы


асинхронные


синхронные


опрос


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

может


заказывал


Схема с синхронными событиями очень распространена. Кассир сидит у кассы и ожи- дает, пока к нему в окошечко не заглянет клиент. Поезд периодически проезжает мимо светофора и останавливается, если горит красный. Функция Си пассивно "спит" до тех пор, пока ее не вызовут; однако она всегда готова выполнить свою работу (обслужить

сервер


После выполнения заказа сервер вновь переходит в состояние ожидания вызова. Итак,

ожидается


некий вызов для ОПРОСА) - это синхронные события. Канонический пример - функция gets, которая задержит выполнение программы, пока с клавиатуры не будет введена

синхронны


пает для программ пользователей в роли сервера, выполняющего сисвызовы (хотя и не только в этой роли - ядро иногда предпринимает и активные действия: передача процес- сора другому процессу через определенное время (режим разделения времени), убивание процесса при ошибке, и.т.п.). Сигналы - это асинхронные события. Они приходят неожиданно, в любой момент вре- мени - вроде телефонного звонка. Кроме того, их не требуется заказывать - сигнал процессу может поступить совсем без повода. Аналогия из жизни такова: человек сидит и пишет письмо. Вдруг его окликают посреди фразы - он отвлекается, отвечает на воп-

не ожидал


готов


нал мог поступить когда он писал 5-ое предложение, а мог - когда 34-ое. Момент вре- мени, в который произойдет прерывание, не фиксирован.

номера


допустимых сигналов. Номера и мнемонические имена сигналов перечислены в include-

signal


NSIG определено в этом файле. При получении сигнала мы узнаем его номер, но не

от кого


Просто "звонит телефон". Чтобы получить дополнительную информацию, наш процесс должен взять ее из другого известного места; например - прочесть заказ из некоторого файла, об имени которого все наши программы заранее "договорились". Сигналы процессу могут поступать тремя путями:

явно


pid


pid


Послать сигнал можно только родственному процессу - запущенному тем же пользова- телем. - От операционной системы. Система может посылать процессу ряд сигналов, сигнали- зирующих об ошибках, например при обращении программы по несуществующему адресу или при ошибочном номере системного вызова. Такие сигналы обычно прекращают наш процесс. - От пользователя - с клавиатуры терминала можно нажимом некоторых клавиш послать

драйвером терминала


получении им с клавиатуры определенных символов. Так можно прервать зациклившу- юся или надоевшую программу. Процесс-получатель должен как-то отреагировать на сигнал. Программа может: А. Богатырев, 1992-95 - 213 - Си в UNIX - проигнорировать сигнал (не ответить на звонок); - перехватить сигнал (снять трубку), выполнить какие-то действия, затем продолжить прерванное занятие; - быть убитой сигналом (звонок был подкреплен броском гранаты в окно); В большинстве случаев сигнал по умолчанию убивает процесс-получатель. Однако процесс может изменить это умолчание и задать свою реакцию явно. Это делается вызовом signal:

signal


sig


react


_


sig


невозможно перехватить или проигнорировать.

_


восстановить реакцию по умолчанию (обычно - смерть получателя).

имя


Например

gotsig


sig


sig


системой


gotsig


нескольких


sig1


Перед


вызовом функции-обработчика реакция автоматически сбрасывается в реакцию по

_


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

убьет


некоторых


Колонки таблицы: G - может быть перехвачен; D - по умолчанию убивает процесс (k), игнорируется (i); C - образуется дамп памяти процесса: файл core, который затем может быть исследован отладчиком adb; F - реакция на сигнал сбрасывается; S - посылается обычно системой, а не явно. сигнал G D C F S смысл SIGTERM + k - + - завершить процесс SIGKILL - k - + - убить процесс SIGINT + k - + - прерывание с клавиш SIGQUIT + k + + - прерывание с клавиш SIGALRM + k - + + будильник SIGILL + k + - + запрещенная команда SIGBUS + k + + + обращение по неверному SIGSEGV + k + + + адресу SIGUSR1, USR2 + i - + - пользовательские SIGCLD + i - + + смерть потомка - Сигнал SIGILL используется иногда для эмуляции команд с плавающей точкой, что происходит примерно так: при обнаружении "запрещенной" команды для отсутствую- щего процессора "плавающей" арифметики аппаратура дает прерывание и система посылает процессу сигнал SIGILL. По сигналу вызывается функция-эмулятор плаваю- щей арифметики (подключаемая к выполняемому файлу автоматически), которая и обрабатывает требуемую команду. Это может происходить много раз, именно поэтому А. Богатырев, 1992-95 - 214 - Си в UNIX

не сбрасывается


- SIGALRM посылается в результате его заказа вызовом alarm() (см. ниже). - Сигнал SIGCLD посылается процессу-родителю при выполнении процессом-потомком сисвызова exit (или при смерти вследствие получения сигнала). Обычно процесс- родитель при получении такого сигнала (если он его заказывал) реагирует, выпол- няя в обработчике сигнала вызов wait (см. ниже). По-умолчанию этот сигнал игно- рируется.

_


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

f


- Синхронное ожидание (сисвызов) может иногда быть прервано асинхронным событием (сигналом), но об этом ниже. Некоторые версии UNIX предоставляют более развитые средства работы с сигналами. Опишем некоторые из средств, имеющихся в BSD (в других системах они могут быть смоде- лированы другими способами). Пусть у нас в программе есть "критическая секция", во время выполнения которой приход сигналов нежелателен. Мы можем "заморозить" (заблокировать) сигнал, отложив момент его поступления до "разморозки": |

sig


| :

sig


СЕКЦИЯ : но он не вызывает реакцию немедленно, | : а "висит", ожидая разрешения. | :

sig


sig


| накопившиеся сигналы доходят, | вызывается реакция.

несколько


только один


просто отмечается в специальной битовой шкале в паспорте процесса (примерно так):

mask


sig


один такой сигнал (система вызывает функцию реакции). То есть sighold заставляет приходящие сигналы "накапливаться" в специальной маске, вместо того, чтобы немедленно вызывать реакцию на них. А sigrelse разрешает "нако- пившимся" сигналам (если они есть) прийти и вызывает реакцию на них. Функция

sig


аналогична функции signal, за исключением того, что на время работы обработчика сиг-

react


ется sighold, а при выходе из обработчика - sigrelse. Это значит, что если во время работы обработчика сигнала придет такой же сигнал, то программа не будет убита, а "запомнит" пришедший сигнал, и обработчик будет вызван повторно (когда сработает sigrelse). Функция

sig


вызывается внутри "рамки"

sig


...

sig


...

sig


А. Богатырев, 1992-95 - 215 - Си в UNIX

sig


sig


sig


В UNIX стандарта POSIX для управления сигналами есть вызовы sigaction, sigproc- mask, sigpending, sigsuspend. Посмотрите в документацию!

etc


Продолжать


должить выдачу; по 'n' - завершить программу; по 'r' - начать выдавать файл с начала:

fd


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

signal


sig


sig


... запрос и действия ... }

onintr


Сигнал прерывания можно игнорировать. Это делается так:

_


_


ется при приходе сигнала. 6.4.2. Системный вызов, находящийся в состоянии ожидания какого-то события (read ждущий нажатия кнопки на клавиатуре, wait ждущий окончания процесса-потомка, и.т.п.),

прерван


тайма-


ута


sec


sec


signal


oldaction


/* прозвонил будильник */

alarmed


... /* установить реакцию на сигнал */

oldaction


/* заказать будильник через TIMEOUT сек. */

alarmed


_


// если нас сбил сигнал, то по сигналу будет // еще вызвана реакция на него - onalarm

alarmed


// событие так и не произошло. // вызов прерван сигналом т.к. истекло время. }else{ alarm(0); /* отменить заказ сигнала */ // событие произошло, сисвызов успел // завершиться до истечения времени. }

oldaction


Напишите программу, которая ожидает ввода с клавиатуры в течение 10 секунд. Если

Нет ввода


можно использовать как вызов read, так и функцию gets (или getchar), поскольку А. Богатырев, 1992-95 - 216 - Си в UNIX функция эта все равно внутри себя издает системный вызов read. Исследуйте, какое значение возвращает fgets (gets) в случае прерывания ее системным вызовом. /* Копирование стандартного ввода на стандартный вывод * с установленным тайм-аутом. * Это позволяет использовать программу для чтения из FIFO-файлов * и с клавиатуры. * Небольшая модификация позволяет использовать программу * для копирования "растущего" файла (т.е. такого, который в * настоящий момент еще продолжает записываться). * Замечание: * В ДЕМОС-2.2 сигнал НЕ сбивает чтение из FIFO-файла, * а получение сигнала откладывается до выхода из read() * по успешному чтению информации. Пользуйтесь open()-ом * с флагом O_NDELAY, чтобы получить требуемый эффект. * * Вызов: a.out /dev/tty * * По мотивам книги М.Дансмура и Г.Дейвиса. */ #define WAIT_TIME 5 /* ждать 5 секунд */ #define MAX_TRYS 5 /* максимум 5 попыток */ #define BSIZE 256 #define STDIN 0 /* дескриптор стандартного ввода */ #define STDOUT 1 /* дескриптор стандартного вывода */ #include <signal.h> #include <errno.h> #include <stdio.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> char buffer [ BSIZE ]; extern int errno; /* код ошибки */ void timeout(nsig){ signal( SIGALRM, timeout ); } void main(argc, argv) char **argv;{ int fd, n, trys = 0; struct stat stin, stout; if( argc != 2 ){ fprintf(stderr, "Вызов: %s файл\n", argv[0]); exit(1); } if((fd = !strcmp(argv[1],"-")? STDIN : open(argv[1],O_RDONLY)) < 0){ fprintf(stderr, "Не могу читать %s\n", argv[1]); exit(2); } /* Проверить, что ввод не совпадает с выводом, * hardcat aFile >> aFile * кроме случая, когда вывод - терминал. * Такая проверка полезна для программ-фильтров (STDIN->STDOUT), * чтобы исключить порчу исходной информации */ fstat(fd, &stin); fstat(STDOUT, &stout); if( !isatty(STDOUT) && stin.st_ino == stout.st_ino && stin.st_dev == stout.st_dev ){ fprintf(stderr, "\aВвод == выводу, возможно потеряна информация в %s.\n",argv[1]); exit(33); } А. Богатырев, 1992-95 - 217 - Си в UNIX signal( SIGALRM, timeout ); while( trys < MAX_TRYS ){ alarm( WAIT_TIME ); /* заказать сигнал через 5 сек */ /* и ждем ввода ... */ n = read( fd, buffer, BSIZE ); alarm(0); /* отменили заказ сигнала */ /* (хотя, возможно, он уже получен) */ /* проверяем: почему мы слезли с вызова read() ? */

errno


/* Мы были сбиты сигналом SIGALRM, * код ошибки EINTR - сисвызов прерван * неким сигналом. */ fprintf( stderr, "\7timed out (%d раз)\n", ++trys ); continue; } if( n < 0 ){ /* ошибка чтения */ fprintf( stderr, "read error.\n" ); exit(4); } if( n == 0 ){ /* достигнут конец файла */ fprintf( stderr, "Достигнут EOF.\n\n" ); exit(0); } /* копируем прочитанную информацию */ write( STDOUT, buffer, n ); trys = 0; } fprintf( stderr, "Все попытки провалились.\n" ); exit(5); } Если мы хотим, чтобы сисвызов не мог прерываться сигналом, мы должны защитить его: #include <signal.h>

fsaved


...

fsaved


_


sig


или так:

sig


_


sig


не все


n


n


любого


нии во время ожидания другого сигнала, нежели SIGALRM. Сохраняйте заказ alarm, сде- ланный до вызова sleep (alarm выдает число секунд, оставшееся до завершения предыду- щего заказа). На самом деле есть такая СТАНДАРТНАЯ функция. Ответ: А. Богатырев, 1992-95 - 218 - Си в UNIX #include <sys/types.h> #include <stdio.h> #include <signal.h> int got; /* пришел ли сигнал */ void onalarm(int sig) { printf( "Будильник\n" ); got++; } /* сигнал получен */

n


time_t time(), start = time(NULL); void (*save)(); int oldalarm, during = n; if( n <= 0 ) return; got = 0; save = signal(SIGALRM, onalarm); oldalarm = alarm(3600); /* Узнать старый заказ */ if( oldalarm ){ printf( "Был заказан сигнал, который придет через %d сек.\n", oldalarm ); if(oldalarm > n) oldalarm -= n; else { during = n = oldalarm; oldalarm = 1; } } printf( "n=%d oldalarm=%d\n", n, oldalarm ); while( n > 0 ){ printf( "alarm(%d)\n", n );

n


pause(); if(got) break; /* иначе мы сбиты с pause другим сигналом */ n = during - (time(NULL) - start); /* прошло времени */ } printf( "alarm(%d) при выходе\n", oldalarm ); alarm(oldalarm); /* alarm(0) - отмена заказа сигнала */ signal(SIGALRM, save); /* восстановить реакцию */ } void onintr(int nsig){ printf( "Сигнал SIGINT\n"); signal(SIGINT, onintr); } void onOldAlarm(int nsig){ printf( "Звонит старый будильник\n"); } void main(){ int time1 = 0; /* 5, 10, 20 */ setbuf(stdout, NULL); signal(SIGINT, onintr); signal(SIGALRM, onOldAlarm); alarm(time1); sleep(10); if(time1) pause(); printf("Чао!\n"); } А. Богатырев, 1992-95 - 219 - Си в UNIX 6.4.4. Напишите "часы", выдающие текущее время каждые 3 секунды. #include <signal.h> #include <time.h> #include <stdio.h>

nsig


tim


signal (SIGALRM, tick);

tim


s


s


stderr


} main(){ tick(0); for(;;) pause(); }

6.5. Жизнь процессов.


6.5.1. Какие классы памяти имеют данные, в каких сегментах программы они располо- жены? char x[] = "hello"; int y[25]; char *p; main(){ int z = 12; int v; static int w = 25; static int q; char s[20]; char *pp; ... v = w + z; /* #1 */ } Ответ: Переменная Класс памяти Сегмент Начальное значение x static data/DATA "hello" y static data/BSS {0, ..., 0} p static data/BSS NULL z auto stack 12 v auto stack не определено w static data/DATA 25 q static data/BSS 0 s auto stack не определено pp auto stack не определено main static text/TEXT Большими буквами обозначены сегменты, хранимые в выполняемом файле:

инициализированные


ния). Они помещаются компилятором в файл в виде готовых констант, а при запуске программы (при ее загрузке в память машины), просто копируются в память из файла. BSS (Block Started by Symbol) - неинициализированные статические данные. Они по умолчанию имеют начальное зна- чение 0 (NULL, "", '\0'). Эта память расписывается нулями при запуске прог-

размер


А. Богатырев, 1992-95 - 220 - Си в UNIX TEXT - сегмент, содержащий машинные команды (код).

заголовок


содержатся размеры перечисленных сегментов и их местоположение в файле; и еще - в

таблицу имен


используемых в программе, и их адреса. Эта таблица используется отладчиками adb и sdb, а также при сборке программы из нескольких объектных файлов программой ld. Просмотреть ее можно командой

имяФайла


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

имяФайла


Размеры сегментов можно узнать командой

имяФайла


Программа, загруженная в память компьютера (т.е. процесс), состоит из 3x сегментов, относящихся непосредственно к программе: stack - стек для локальных переменных функций (автоматических переменных). Этот сег- мент существует только у выполняющейся программы, поскольку отведение памяти в стеке производится выполнением некоторых машинных команд (поэтому описание авто-

выполняемые


автоматически


(если мы вызываем новые и новые функции, отводящие переменные в стеке). За этим следит аппаратура диспетчера памяти. data - сегмент, в который склеены сегменты статических данных DATA и BSS, загруженные из файла. Этот сегмент также может изменять свой размер, но делать это надо явно - системными вызовами sbrk или brk. В частности, функция malloc() для раз- мещения динамически отводимых данных увеличивает размер этого сегмента. text - это выполняемые команды, копия сегмента TEXT из файла. Так строка с меткой #1 содержится в виде машинных команд именно в этом сегменте. Кроме того, каждый процесс имеет еще: proc - это резидентная часть паспорта процесса в таблице процессов в ядре операцион- ной системы; user - это 4-ый сегмент процесса - нерезидентная часть паспорта (u-area). К этому сегменту имеет доступ только ядро, но не сама программа. Паспорт процесса был поделен на 2 части только из соображений экономии памяти в ядре: контекст процесса (таблица открытых файлов, ссылка на I-узел текущего каталога, таб- лица реакций на сигналы, ссылка на I-узел управляющего терминала, и.т.п.) нужен ядру только при обслуживании текущего активного процесса. Когда активен другой процесс - эта информация в памяти ядра не нужна. Более того, если процесс из-за нехватки места в памяти машины был откачан на диск, эта информация также может быть откачана на диск и подкачана назад лишь вместе с процессом. Поэтому контекст был выделен в отдельный сегмент, и сегмент этот подключается к адресному пространству ядра лишь при выполне- нии процессом какого-либо системного вызова (это подключение называется "переключение

context switch


машины не обязательно подряд - между ними могут лежать сегменты других процессов. Схема составных частей процесса: П Р О Ц Е С С таблица процессов: паспорт в ядре сегменты в памяти struct proc[]

stack


data


text


контекст: struct user 4 А. Богатырев, 1992-95 - 221 - Си в UNIX

p


ней также хранятся: адреса сегментов процесса в памяти машины (или на диске, если

p


p


p


sys


новый


шего вызов. Отличие этих процессов состоит только в возвращаемом fork-ом значении: 0 - в новом процессе.

pid


Вызов fork может завершиться неудачей если таблица процессов переполнена. Простейший способ сделать это: main(){ while(1) if( ! fork()) pause(); } Одно гнездо таблицы процессов зарезервировано - его может использовать только супер- пользователь (в целях жизнеспособности системы: хотя бы для того, чтобы запустить программу, убивающую все эти процессы-варвары). Вызов fork создает копию всех 4х сегментов процесса и выделяет порожденному про- цессу новый паспорт и номер. Иногда сегмент text не копируется, а используется про-

разделяемый сегмент


наследуется


ниже). Проведите опыт, доказывающий что порожденный системным вызовом fork() процесс и породивший его - равноправны. Повторите несколько раз программу: #include <stdio.h> int pid, i, fd; char c; main(){ fd = creat( "TEST", 0644); if( !(pid = fork())){ /* сын: порожденный процесс */ c = 'a'; for(i=0; i < 5; i++){ write(fd, &c, 1); c++; sleep(1); } printf("Сын %d окончен\n", getpid()); exit(0); } /* else процесс-отец */ c = 'A'; for(i=0; i < 5; i++){ write(fd, &c, 1); c++; sleep(1); } printf("Родитель %d процесса %d окончен\n", getpid(), pid ); }

TEST


aABbCcDdEe или AaBbcdCDEe

любой


сов. Если же опыт дает устойчиво строки, начинающиеся с одной и той же буквы - значит ____________________

pid


А. Богатырев, 1992-95 - 222 - Си в UNIX в данной реализации системы один из процессов все же запускается раньше. Но не стоит использовать этот эффект - при переносе на другую систему его может не быть! Данный опыт основан на следующем свойстве системы UNIX: при системном вызове fork() порожденный процесс получает все открытые порождающим процессом файлы "в нас- ледство" - это соответствует тому, что таблица открытых процессом файлов копируется в процесс-потомок. Именно так, в частности, передаются от отца к сыну стандартные каналы 0, 1, 2: порожденному процессу не нужно открывать стандартные ввод, вывод и вывод ошибок явно. Изначально же они открываются специальной программой при вашем входе в систему. до вызова fork(); таблица открытых файлов процесса 0 ## ---<--- клавиатура 1 ## --->--- дисплей 2 ## --->--- дисплей ... ##

TEST


... ## после fork(); ПРОЦЕСС-ПАПА ПРОЦЕСС-СЫН 0 ## ---<--- клавиатура --->--- ## 0 1 ## --->--- дисплей ---<--- ## 1 2 ## --->--- дисплей ---<--- ## 2 ... ## ## ...

TEST


... ## | ## ...

RWptr


Ссылки из таблиц открытых файлов в процессах указывают на структуры "открытый файл" в

одной и


той же


fd


одним и тем же указателем R/W, т.е. информация от обоих процессов записывается после- довательно. На принципе наследования и совместного использования открытых файлов основан также системный вызов pipe. Порожденный процесс наследует также: реакции на сигналы (!!!), текущий каталог, управляющий терминал, номер владельца процесса и группу владельца, и.т.п.

программу


на программу из указанного файла) все открытые каналы также достаются в наследство новой программе (а не закрываются). 6.5.3. Процесс-копия это хорошо, но не совсем то, что нам хотелось бы. Нам хочется запустить программу, содержащуюся в выполняемом файле (например a.out). Для этого существует системный вызов exec, который имеет несколько разновидностей. Рассмотрим только две:

path


argv


path


path


программу


path


именем файла от текущего каталога: /usr/bin/vi a.out ../mybin/xkick А. Богатырев, 1992-95 - 223 - Си в UNIX Файл должен иметь код доступа "выполнение". Первые два байта файла (в его заго- ловке), рассматриваемые как short int, содержат так называемое "магическое число"

_


в начало выполняемого файла редактор связей ld при компоновке программы из объектных файлов. Это число должно быть правильным, иначе система откажется запускать прог- рамму из этого файла. Бывает несколько разных магических чисел, обозначающих разные способы организации программы в памяти. Например, есть вариант, в котором сегменты text и data склеены вместе (тогда text не разделяем между процессами и не защищен от модификации программой), а есть - где данные и текст находятся в раздельных адресных пространствах и запись в text запрещена (аппаратно).

arg0


программы. Во второй форме вызова аргументы не перечисляются явно, а заносятся в мас- сив. Это позволяет формировать произвольный массив строк-аргументов во время работы программы:

argv


argv


argv


либо execl( "/bin/ls", "ls","-l","-i", NULL): В результате этого вызова текущая программа завершается (но не процесс!) и вместо нее запускается программа из заданного файла: сегменты stack, data, text старой программы

новые


дится сегмент stack (первоначально - не очень большого размера); сегмент user сохра-

_


_


argv


argc


argc


argc


ется.

Процесс


pid


рытыми (с теми же дескрипторами); текущий каталог также наследуется от старой прог- раммы; сигналы, которые игнорировались ею, также будут игнорироваться (остальные

_


иную


другой программе


качестве входных.

path


либо вы не имеете права его выполнять (такие коды доступа), либо он не является выполняемой программой (неверное магическое число), либо слишком велик для данной машины (системы), либо файл открыт каким-нибудь процессом (например еще записывается компилятором). В этом случае продолжится выполнение прежней программы. Если же вызов успешен - возврата из exec не происходит вообще (поскольку управление переда- ется в другую программу).

argv


колько имен (в файловой системе), может выбрать ЧТО она должна делать. Так программа

одна и та же


argv


envp


задан - передается окружение текущей программы (наследуется содержимое массива, на который указывает переменная environ); если же задан явно (например, окружение скопи- ровано в какой-то массив и часть переменных подправлена или добавлены новые перемен- ные) - новая программа получит новое окружение. Напомним, что окружение можно про- честь из предопределенной переменной char **environ, либо из третьего аргумента функ- ции main (см. начало главы), либо функцией getenv(). А. Богатырев, 1992-95 - 224 - Си в UNIX Системные вызовы fork и exec не склеены в один вызов потому, что между fork и exec в процессе-сыне могут происходить некоторые действия, нарушающие симметрию процесса-отца и порожденного процесса: установка реакций на сигналы, перенаправление ввода/вывода, и.т.п. Смотри пример "интерпретатор команд" в приложении. В MS DOS, не

один


порождающем


перед spawn, а после него - восстанавливать все как было.

процесс


retcode


Из этого вызова не бывает возврата. Процесс завершается: сегменты stack, data, text, user уничтожаются (при этом все открытые процессом файлы закрываются); память, кото- рую они занимали, считается свободной и в нее может быть помещен другой процесс. Причина смерти отмечается в паспорте процесса - в структуре proc в таблице процессов внутри ядра. Но паспорт еще не уничтожается! Это состояние процесса называется "зомби" - живой мертвец.

retcode


процессом-родителем (тем, кто создал этот процесс вызовом fork). Принято, что код 0 означает успешное завершение процесса, а любое положительное значение 1..255 означает неудачное завершение с таким кодом ошибки. Коды ошибок заранее не предопределены: это личное дело процессов отца и сына - установить между собой какие-то соглашения по этому поводу. В старых программах иногда писалось exit(-1); Это некорректно - код ответа должен быть неотрицателен; код -1 превращается в код 255. Часто используется

errno


явно


бами: - если происходит возврат управления из функции main(), т.е. она кончилась - то

retcode


убит


ответа в процесс-родитель, а выдает признак "процесс убит". 6.5.5. В действительности exit() - это еще не сам системный вызов завершения, а

функция


лить функцию exit() так, чтобы по окончании программы происходили некоторые действия:

code


/* Добавленный мной дополнительный оператор: */ printf("Закончить работу, "

code


/* Стандартные операторы: */

_


* Это стандартная функция |= */

_


} int f(){ return 17; } void main(){ printf("aaaa\n"); printf("bbbb\n"); f(); /* потом откомментируйте это: exit(77); */ }

неявно


компилятор. Дело в том, что при запуске программы exec-ом, первым начинает выпол-

стартера


/lib/crt0.o. Он выглядит примерно так (в действительности он написан на ассемблере):

argc


argc


exit(); А. Богатырев, 1992-95 - 225 - Си в UNIX или так (взято из проекта GNU|-|-):

errno


environ


_


{ /* OS and Compiler dependent!!!! */

argv


envp


/* ... возможно еще какие-то инициализации,

_


argc


} Где должно быть

argc


... return 0; /* вместо exit(0); */ }

_


адрес, на который система должна передать управление после загрузки программы в память (точка входа).

явный


exit или return) - непредсказуемо. На IBM PC в вышенаписанном примере этот код равен 17, то есть значению, возвращенному последней вызывавшейся функцией. Однако это не какое-то специальное соглашение, а случайный эффект (так уж устроен код, создаваемый этим компилятором). 6.5.6. Процесс-отец может дождаться окончания своего потомка. Это делается систем- ным вызовом wait и нужно по следующей причине: пусть отец - это интерпретатор команд.

оба


попытки читать ввод с клавиатуры терминала - интерпретатор ждет команд, а запущенная программа ждет данных. Кому из них будет поступать набираемый нами текст - непредс- казуемо! Вывод: интерпретатор команд должен "заснуть" на то время, пока работает порожденный им процесс:

pid


...

pid


/* порожденный процесс */ ... // перенаправления ввода-вывода. ... // настройка сигналов. exec(....); perror("exec не удался"); exit(1); } /* иначе это породивший процесс */

pid


printf("Окончился сын pid=%d с кодом %d\n",

pid


printf( "Больше нет сыновей\n"); ____________________

_


копленные в буферах, в файл. При аварийном завершении программы файлы все равно зак-

_


утеряно


____________________ |-|- GNU - программы, распространяемые в исходных текстах из Free Software Founda- А. Богатырев, 1992-95 - 226 - Си в UNIX

любого


порожденных им процессов (ведь можно было запустить и нескольких сыновей!). Как

pid


потомка. Когда никого из живых "сыновей" не осталось - он выдаст (-1). Ясно, что процессы могут оканчиваться не в том порядке, в котором их порождали. В переменную

status


сигнала, которым он был убит. #include <sys/types.h> #include <sys/wait.h> ...

status


...

pid


status


printf( "Процесс %d умер с кодом %d\n",

pid


status


printf( "Процесс %d убит сигналом %d\n",

pid


status


/* core - образ памяти процесса для отладчика adb */

status


printf( "Процесс %d остановлен сигналом %d\n",

pid


status


printf( "Процесс %d продолжен\n",

pid


} } ... Если код ответа нас не интересует, мы можем писать wait(NULL). Если у нашего процесса не было или больше нет живых сыновей - вызов wait ничего не ждет, а возвращает значение (-1). В написанном примере цикл while позволяет дож-

всех


В тот момент, когда процесс-отец получает информацию о причине смерти потомка,

вычеркивается


переиспользован новым процессом. До того, он хранится в таблице процессов в состоя- нии "zombie" - "живой мертвец". Только для того, чтобы кто-нибудь мог узать статус его завершения.

раньше


вычеркнет паспорт? Это сделает процесс номер 1: /etc/init. Если отец умер раньше процессов-сыновей, то система заставляет процесс номер 1 "усыновить" эти процессы. init обычно находится в цикле, содержащем в начале вызов wait(), то есть ожидает ____________________ tion (FSF). Среди них - C++ компилятор g++ и редактор emacs. Смысл слов GNU - "gen- erally not UNIX" - проект был основан как противодействие начавшейся коммерциализации UNIX и закрытию его исходных текстов. "Сделать как в UNIX, но лучше". |- "Живой" процесс может пребывать в одном из нескольких состояний: процесс ожидает наступления какого-то события ("спит"), при этом ему не выделяется время процессора, т.к. он не готов к выполнению; процесс готов к выполнению и стоит в очереди к процес- сору (поскольку процессор выполняет другой процесс); процесс готов и выполняется про- цессором в данный момент. Последнее состояние может происходить в двух режимах - пользовательском (выполняются команды сегмента text) и системном (процессом был издан системный вызов, и сейчас выполняется функция в ядре). Ожидание события бывает только в системной фазе - внутри системного вызова (т.е. это "синхронное" ожидание). Неак- тивные процессы ("спящие" или ждущие ресурса процессора) могут быть временно откачаны на диск. А. Богатырев, 1992-95 - 227 - Си в UNIX окончания любого из своих сыновей (а они у него всегда есть, о чем мы поговорим под- робнее чуть погодя). Таким образом init занимается чисткой таблицы процессов, хотя это не единственная его функция. Вот схема, поясняющая жизненный цикл любого процесса: |pid=719,csh | if(!fork())------->--------* pid=723,csh | | загрузить

status


: main(...){ с диска : | :pid=719,csh | pid=723,a.out спит(ждет) работает : |

status


: } проснулся <---проснись!--RIP | |pid=719,csh

следующим


больше


до


6.5.7. Кроме того, wait позволяет отслеживать остановку процесса. Процесс может быть приостановлен при помощи посылки ему сигналов SIGSTOP, SIGTTIN, SIGTTOU, SIGTSTP. Последние три сигнала посылает при определенных обстоятельствах драйвер терминала, к примеру SIGTSTP - при нажатии клавиши CTRL/Z. Продолжается процесс посылкой ему сигнала SIGCONT. В данном контексте, однако, нас интересуют не сами эти сигналы, а другая схема

явно


тема может посылать процессу-родителю сигнал SIGCLD в момент изменения статуса любого

немедленно


отразить изменение состояние процесса-потомка в своих внутренних списках. Данная схема программируется так: void pchild(){

pid


sighold(SIGCLD);

pid


dorecord: записать_информацию_об_изменениях; } sigrelse(SIGCLD); /* Reset */ signal(SIGCLD, pchild); } ... main(){ ... /* По сигналу SIGCLD вызывать функцию pchild */ signal(SIGCLD, pchild); ... главный_цикл; } Секция с вызовом waitpid (разновидность вызова wait), прикрыта парой функций sighold-sigrelse, запрещающих приход сигнала SIGCLD внутри этой критической секции. А. Богатырев, 1992-95 - 228 - Си в UNIX Сделано это вот для чего: если процесс начнет модифицировать таблицы или списки в районе метки dorecord:, а в этот момент придет еще один сигнал, то функция pchild будет вызвана рекурсивно и тоже попытается модифицировать таблицы и списки, в которых еще остались незавершенными перестановки ссылок, элементов, счетчиков. Это приведет к разрушению данных. Поэтому сигналы должны приходить последовательно, и функции pchild вызываться также последовательно, а не рекурсивно. Функция sighold откладывает доставку сигнала (если он случится), а sigrelse - разрешает доставить накопившиеся сигналы (но если их

один


цикл вокруг waitpid). Флаг WNOHANG - означает "не ждать внутри вызова wait", если ни один из потомков не изменил своего состояния; а просто вернуть код (-1)". Это позволяет вызывать pchild даже без получения сигнала: ничего не произойдет. Флаг WUNTRACED - означает "выдавать информацию также об остановленных процессах". 6.5.8. Как уже было сказано, при exec все открытые файлы достаются в наследство новой программе (в частности, если между fork и exec были перенаправлены вызовом dup2 стандартные ввод и вывод, то они останутся перенаправленными и у новой программы).

все


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

fd


они занимают). Во-первых, ненужные дескрипторы можно явно закрыть close в промежутке между fork-ом и exec-ом. Однако не всегда мы помним номера дескрипторов для этой операции. Более радикальной мерой является тотальная чистка: for(f = 3; f < NOFILE; f++) close(f); Есть более элегантный путь. Можно пометить дескриптор файла специальным флагом,

автоматически


(режим file-close-on-exec - fclex):

fcntl


fd


fd


Отменить этот режим можно так:

fd


Здесь есть одна тонкость: этот флаг устанавливается не для структуры file - "открытый

дескриптора


u


может ожидать сюрприз: ... fcntl (fd, F_SETFD, 1); ... close(fd); ... int fd1 = open( ... );

fd1


fd


управляющий терминал


цессу в наследство от родителя (при fork и exec) и обычно совпадает с терминалом, с на котором работает данный пользователь.

группе процессов


pgrp


pgrp


Вызов

sig


sig


А. Богатырев, 1992-95 - 229 - Си в UNIX процесса. Процесс может узнать свою группу:

pgrp


а может стать "лидером" новой группы. Вызов setpgrp(); делает следующие операции: /* У процесса больше нет управл. терминала: */

p


/* Группа процесса полагается равной его ид-у: */

p


t


ние устанавливается равным группе процесса, первым открывшего этот терминал: /* часть процедуры открытия терминала */

p


u


t


u


t


} Таким процессом обычно является процесс регистрации пользователя в системе (который спрашивает у вас имя и пароль). При закрытии терминала всеми процессами (что бывает

t


При нажатии на клавиатуре терминала некоторых клавиш:

c


c


драйвер терминала посылает соответственно сигналы SIGINT и SIGQUIT всем процессам группы терминала, т.е. как бы делает

t


Именно поэтому мы можем прервать процесс нажатием клавиши DEL. Поэтому, если процесс сделал setpgrp(), то сигнал с клавиатуры ему послать невозможно (т.к. он имеет свой уникальный номер группы != группе терминала). Если процесс еще не имеет управляющего терминала (или уже его не имеет после

любой


ляющим для себя. Первый же файл-устройство, являющийся интерфейсом драйвера термина- лов, который будет открыт этим процессом, станет для него управляющим терминалом. Так процесс может иметь каналы 0, 1, 2 связанные с одним терминалом, а прерывания полу- чать с клавиатуры другого (который он сделал управляющим для себя). Процесс регистрации пользователя в системе - /etc/getty (название происходит от

get tty


каждом из терминалов, зарегистрированных в системе, когда - система только что была запущена; - либо когда пользователь на каком-то терминале вышел из системы (интерпретатор команд завершился). В сильном упрощении getty может быть описан так:

ac


f


f


/* Отказ от управляющего терминала, * основание новой группы процессов. */ setpgrp(); /* Первоначальное явное открытие терминала */ А. Богатырев, 1992-95 - 230 - Си в UNIX

av


av


av


f


// ... Считывание параметров терминала из файла // /etc/gettydefs. Тип требуемых параметров линии

av


tmodes


// значениями ... и установка мод терминала.

f


// ... запрос имени и пароля ...

домашний


execl ("/bin/csh", "-csh", NULL); /* Запуск интерпретатора команд. Группа процессов, * управл. терминал, дескрипторы 0,1,2 наследуются. */ } Здесь последовательные вызовы open занимают последовательные ячейки в таблице откры- тых процессом файлов (поиск каждой новой незанятой ячейки производится с начала таб- лицы) - в итоге по дескрипторам 0,1,2 открывается файл-терминал. После этого деск- рипторы 0,1,2 наследуются всеми потомками интерпретатора команд. Процесс init запус- кает по одному процессу getty на каждый терминал, как бы делая

dev


dev


... и ожидает окончания любого из них. После входа пользователя в систему на каком-то

pid


сохраняется). Как только кто-то из них умрет - init перезапустит getty на соответст- вующем терминале (все они - его сыновья, поэтому он знает - на каком именно терми- нале).

6.6. Трубы и FIFO-файлы.


Процессы могут обмениваться между собой информацией через файлы. Существуют

first in


разделены


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

пустой


вызов read (и издавший его процесс) до тех пор, пока кто-нибудь не запишет в FIFO-

неприме-


нима


sys


sys


имяФайла


где 0666 - коды доступа к файлу. При помощи FIFO-файла могут общаться даже неродст- венные процессы.

безымянный


обмена информацией между процессом-отцом и процессом-сыном. Такой файл - канал связи как раз и называется термином "труба" или pipe. Он создается вызовом pipe:

conn


PIPEFILE


А. Богатырев, 1992-95 - 231 - Си в UNIX

PIPEFILE


conn


conn


PIPEFILE


При вызове fork каждому из двух процессов достанется в наследство пара дескрипторов:

conn


fork();

conn


FIFO

conn


процесс A процесс B Пусть процесс A будет посылать информацию в процесс B. Тогда процесс A сделает:

conn


// т.к. не собирается ничего читать

conn


а процесс B

conn


// т.к. не собирается ничего писать

conn


Получаем в итоге:

conn


процесс A процесс B Обычно поступают еще более элегантно, перенаправляя стандартный вывод A в канал

conn


conn


write(1, ... ); /* или printf */

conn


conn


read(0, ... ); /* или gets */ Это соответствует конструкции $ A | B записанной на языке СиШелл. Файл, выделяемый под pipe, имеет ограниченный размер (и поэтому обычно целиком оседает в буферах в памяти машины). Как только он заполнен целиком - процесс, пишу- щий в трубу вызовом write, приостанавливается до появления свободного места в трубе. Это может привести к возникновению тупиковой ситуации, если писать программу неакку- ратно. Пусть процесс A является сыном процесса B, и пусть процесс B издает вызов

conn


получаем ситуацию, когда оба процесса спят: A потому что труба переполнена, а процесс B ничего из нее не читает, так как ждет окончания A; B потому что процесс-сын A не окончился, а он не может окончиться пока не допишет свое сообщение. Решением служит запрет процессу B делать вызов wait до тех пор, пока он не прочитает

conn


А. Богатырев, 1992-95 - 232 - Си в UNIX процесс B имеет право сделать wait.

conn


закончит запись в нее, то при вызове write в процессе A, система пришлет процессу A сигнал SIGPIPE - "запись в канал, из которого никто не читает". 6.6.1. Открытие FIFO файла приведет к блокированию процесса ("засыпанию"), если в буфере FIFO файла пусто. Процесс заснет внутри вызова open до тех пор, пока в буфере что-нибудь не появится. Чтобы избежать такой ситуации, а, например, сделать что-нибудь иное полезное в

опросить


_


fd


Если open ведет к блокировке процесса внутри вызова, вместо этого будет возвращено значение (-1). Если же файл может быть немедленно открыт - возвращается нормальный дескриптор со значением >=0, и файл открыт.

_


примеру, можно использовать его с файлами устройств, например именами, ведущими к последовательным портам. Эти файлы устройств (порты) обладают тем свойством, что одновременно их может открыть только один процесс (так устроена реализация функции open внутри драйвера этих устройств). Поэтому, если один процесс уже работает с пор- том, а в это время второй пытается его же открыть, второй "заснет" внутри open, и будет дожидаться освобождения порта close первым процессом. Чтобы не ждать - следует

_


#include <stdio.h> #include <fcntl.h> /* Убрать больше не нужный O_NDELAY */ void nondelay(int fd){ fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) & ~O_NDELAY); } int main(int ac, char *av[]){ int fd; char *port = ac > 1 ? "/dev/term/a" : "/dev/cua/a"; retry: if((fd = open(port, O_RDWR|O_NDELAY)) < 0){ perror(port); sleep(10); goto retry; } printf("Порт %s открыт.\n", port); nondelay(fd); printf("Работа с портом, вызови эту программу еще раз!\n"); sleep(60); printf("Все.\n"); return 0; } Вот протокол: А. Богатырев, 1992-95 - 233 - Си в UNIX su# a.out & a.out xxx [1] 22202 Порт /dev/term/a открыт. Работа с портом, вызови эту программу еще раз! /dev/cua/a: Device busy /dev/cua/a: Device busy /dev/cua/a: Device busy /dev/cua/a: Device busy /dev/cua/a: Device busy /dev/cua/a: Device busy Все. Порт /dev/cua/a открыт. Работа с портом, вызови эту программу еще раз! su#

6.7. Нелокальный переход.


нелокальный переход


установить в программе "контрольную точку"|-, а функция longjmp осуществляет прыжок в

сразу из нескольких


надо)|=. Эти функции не являются системными вызовами, но поскольку они реализуются машинно-зависимым образом, а используются чаще всего как реакция на некоторый сигнал, речь о них идет в этом разделе. Вот как, например, выглядит рестарт программы по прерыванию с клавиатуры:

signal


setjmp


_


/* прыгнуть в контрольную точку */

nsig


main(){

n


n


n


onintr


printf("Начали\n"); ... }

запоминании


точку при помощи longjmp, мы оказываемся снова в функции setjmp, и эта функция возв-

nsig


Прыжок в контрольную точку очень удобно использовать в алгоритмах перебора с

backtracking


если ветвь перебора зашла в тупик - прыжок в точку ветвления и выбор другой альтерна- тивы. При этом можно делать прыжки и в рекурсивных вызовах одной и той же функции: с

_


лучше делать автоматической переменной - своей для каждого уровня вызова функции). ____________________ |- В некотором буфере запоминается текущее состояние процесса: положение вершины

stack pointer


instruction pointer


|= Это достигается восстановлением состояния процесса из буфера. Изменения, проис- шедшие за время между setjmp и longjmp в статических данных не отменяются (т.к. они не сохранялись). А. Богатырев, 1992-95 - 234 - Си в UNIX 6.7.1. Перепишите следующий алгоритм при помощи longjmp. #define FOUND 1 /* ответ найден */ #define NOTFOUND 0 /* ответ не найден */ int value; /* результат */ main(){ int i; for(i=2; i < 10; i++){ printf( "пробуем i=%d\n", i); if( test1(i) == FOUND ){ printf("ответ %d\n", value); break; } } } test1(i){ int j; for(j=1; j < 10 ; j++ ){ printf( "пробуем j=%d\n", j); if( test2(i,j) == FOUND ) return FOUND; /* "сквозной" return */ } return NOTFOUND; } test2(i, j){ printf( "пробуем(%d,%d)\n", i, j); if( i * j == 21 ){ printf( " Годятся (%d,%d)\n", i,j); value = j; return FOUND; } return NOTFOUND; } Вот ответ, использующий нелокальный переход вместо цепочки return-ов: #include <setjmp.h> jmp_buf jmp; main(){ int i; if( i = setjmp(jmp)) /* после прыжка */ printf("Ответ %d\n", --i); else /* установка точки */ for(i=2; i < 10; i++) printf( "пробуем i=%d\n", i), test1(i); } test1(i){ int j; for(j=1; j < 10 ; j++ ) printf( "пробуем j=%d\n", j), test2(i,j); } test2(i, j){ printf( "пробуем(%d,%d)\n", i, j); if( i * j == 21 ){ printf( " Годятся (%d,%d)\n", i,j); longjmp(jmp, j + 1); } } Обратите внимание, что при возврате ответа через второй аргумент longjmp мы прибавили

j


установки


рольной точки). 6.7.2. В чем ошибка?

setjmp


А. Богатырев, 1992-95 - 235 - Си в UNIX

_


main(){ g();

jmp


} g(){ printf("Вызвана g\n"); f(); printf("Выхожу из g\n"); } f(){ static n; printf( "Вызвана f\n");

jmp


printf( "Выхожу из f %d-ый раз\n", ++n); } Ответ: longjmp делает прыжок в функцию f(), из которой уже произошел возврат управле- ния. При переходе в тело функции в обход ее заголовка не выполняются машинные команды "пролога" функции - функция остается "неактивированной". При возврате из вызванной таким "нелегальным" путем функции возникает ошибка, и программа падает. Мораль: в функцию, которая НИКЕМ НЕ ВЫЗВАНА, нельзя передавать управление. Обратный прыжок - из f() в main() - был бы законен, поскольку функция main() является активной, когда управление находится в теле функции f(). Т.е. можно "прыгать" из вызванной функции в вызывающую: из f() в main() или в g(); и из g() в main(); -- -- | f | стек прыгать | g | вызовов сверху вниз | main | функций можно - это соответствует ---------- выкидыванию нескольких верхних слоев стека но нельзя наоборот: из main() в g() или f(); а также из g() в f(). Можно также совершать прыжок в пределах одной и той же функции: f(){ ...

jmp


...

jmp


/* это как бы goto A; */ }

6.8. Хозяин файла, процесса, и проверка привелегий.


UNIX - многопользовательская система. Это значит, что одновременно на разных

разные


свой


являющийся потомком процесса /etc/init. 6.8.1. Теперь - про функции, позволяющие узнать некоторые данные про любого пользо-

номер


user id


рает для входа в систему. Вся информация о пользователях хранится в файле /etc/passwd. Существуют функции, позволяющие по номеру пользователя узнать регистра- ционное имя и наоборот, а заодно получить еще некоторую информацию из passwd: А. Богатырев, 1992-95 - 236 - Си в UNIX

stdio


pwd


p


uid


uname


uid


p


...

p


Эти функции возвращают указатели на статические структуры, скрытые внутри этих функ- ций. Структуры эти имеют поля:

p


p


и ряд полей типа char[]

p


p


(каталога, становящегося текущим при входе в систему);

p


(если "", то имеется в виду /bin/sh);

p


p


p


Истинный


p


uid


uid


st


наш аналог программы ls, чтобы он выдавал в текстовом виде имя владельца каждого файла в каталоге. 6.8.2. Владелец файла может изменить своему файлу идентификаторы владельца и группы вызовом

имяФайла


т.е. "подарить" файл другому пользователю. Забрать чужой файл себе невозможно. При

_


поэтому создать "Троянского коня" и, сделав его хозяином суперпользователя, получить неограниченные привелегии - не удастся!

di


i


u


ruid


"эффективным идентификатором". При вызове exec() заменяется программа, выполняемая данным процессом: ____________________ |- При открытии файла и вообще при любой операции с файлом, в таблицах ядра заво-

копия


Если I-узел в памяти будет изменен, то при закрытии файла (а также периодически через некоторые промежутки времени) эта копия будет записана обратно на диск. Структура

sys


sys


А. Богатырев, 1992-95 - 237 - Си в UNIX старая программа exec новая программа

ruid


uid


| выполняемый файл

i


Как видно из этой схемы, реальный идентификатор хозяина процесса наследуется. Эффек- тивный идентификатор обычно также наследуется, за исключением одного случая: если в

i


u


/* ... во время exec ... */

p


i


i


gid


группы владельца


цесса - реальный и эффективный). Зачем все это надо? Во-первых затем, что ПРАВА процесса на доступ к какому-либо

эффективного


файл имеет коды доступа

mode


/* rwx rwx rwx */

i


таком порядке:

u


то доступ разрешен;

u


mode


u


mode


mode


Процесс может узнать свои параметры:

uid


ruid


gid


rgid


а также установить их:

newuid


u


этот вызов):

u


u


u


u


else неудача;

p


который был у нее до exec-а. А. Богатырев, 1992-95 - 238 - Си в UNIX Во-вторых, все это надо для следующего случая: пусть у меня есть некоторый файл

BASE


только


менее, я хочу дать другим пользователям возможность работать с этим файлом, однако

программу


BASE


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

PROG


задаю этому файлу коды доступа 0711 (rwx--x--x) - всем можно выполнять эту программу.

BASE


PROG


PROG


другого


BASE


жен работать как бы от моего имени. Для этого я должен вызовом chmod либо командой

PROG


PROG


PROG


моему


BASE


это в протокол, если надо). Программы такого типа - не редкость в UNIX, если владельцем программы (файла ее содержащего) является суперпользователь. В таком случае программа, имеющая бит дос-

_


вия, запрещенные обычным пользователям. При этом программа внутри себя делает всячес- кие проверки и периодически спрашивает пароли, то есть при работе защищает систему от дураков и преднамеренных вредителей. Простейшим примером служит команда ps, которая считывает таблицу процессов из памяти ядра и распечатывает ее. Доступ к физической памяти машины производится через файл-псевдоустройство /dev/mem, а к памяти ядра -

только


раммы "общего пользования", обращающиеся к этим файлам, должны иметь бит set-uid.

uid


цесса? Они берутся из процесса регистрации пользователя в системе: /etc/getty. Этот процесс запускается на каждом терминале как процесс, принадлежащий суперпользователю

u


stdio


pwd


signal


p


userName


extern char *getpass(), *crypt(); ... /* Не прерываться по сигналам с клавиатуры */

_


for(;;){ /* Запросить имя пользователя: */

userName


/* Запросить пароль (без эха): */

pass


/* Проверить имя: */

p


/* есть такой пользователь */

crpass


pass


crpass


break; /* верный пароль */ } printf("Login incorrect.\a\n"); }

_


А. Богатырев, 1992-95 - 239 - Си в UNIX Затем он выполняет: // ... запись информации о входе пользователя в систему // в файлы /etc/utmp (кто работает в системе сейчас)

всех


...

p


p


// эти параметры будут унаследованы // интерпретатором команд. ...

envp


p


p


// PATH = нечто по умолчанию, вроде :/bin:/usr/bin

p


// TERM = считывается из файла

av


// Делается это как-то подобно // char *envp[MAXENV], buffer[512]; int envc = 0; // ... // sprintf(buffer, "HOME=%s", p->pw_dir); // envp[envc++] = strdup(buffer); // ... // envp[envc] = NULL; ... // настройка кодов доступа к терминалу. Имя устройства

av


av


av


// теперь доступ к данному терминалу имеют только // вошедший в систему пользователь и суперпользователь. // В случае смерти интерпретатора команд, // которым заменится getty, процесс init сойдет // с системного вызова ожидания wait() и выполнит

этот


этот


// и, если терминал числится в файле описания линий

активный


этом


// процесс getty при помощи пары вызовов fork() и exec(). ... // запуск интерпретатора команд:

p


envp


В результате он становится процессом пользователя, вошедшего в систему. Таковым же

p


/bin/sh или /bin/csh) и все его потомки. На самом деле, в описании регистрации пользователя при входе в систему, созна- тельно было допущено упрощение. Дело в том, что все то, что мы приписали процессу

двумя


Сначала процесс getty занимается настройкой параметров линии связи (т.е. терми-

имя


пользователя и заменяет себя (при помощи сисвызова exec) процессом login, передавая ему в качестве одного из аргументов полученное имя пользователя. Затем login запрашивает пароль, настраивает окружение, и.т.п., то есть именно он производит все операции, приведенные выше на схеме. В конце концов он заменяет себя интерпретатором команд. Такое разделение делается, в частности, для того, чтобы считанный пароль в слу- чае опечатки не хранился бы в памяти процесса getty, а уничтожался бы при очистке А. Богатырев, 1992-95 - 240 - Си в UNIX памяти завершившегося процесса login. Таким образом пароль в истинном, незашифрован- ном виде хранится в системе минимальное время, что затрудняет его подсматривание средствами электронного или программного шпионажа. Кроме того, это позволяет изме- нять систему проверки паролей не изменяя программу инициализации терминала getty. Имя, под которым пользователь вошел в систему на данном терминале, можно узнать вызовом стандартной функции char *getlogin();

uid


из файла /etc/utmp.

файла


(вызовами creat или mknod), и полагается равным эффективному идентификатору создаю- щего процесса.

di


6.8.4. Напишите программу, узнающую у системы и распечатывающую: номер процесса, номер и имя своего владельца, номер группы, название и тип терминала на котором она работает (из переменной окружения TERM).

6.9. Блокировка доступа к файлам.


В базах данных нередко встречается ситуация одновременного доступа к одним и тем же данным. Допустим, что в некотором файле хранятся данные, которые могут читаться и записываться произвольным числом процессов. - Допустим, что процесс A изменяет некоторую область файла, в то время как процесс B пытается прочесть ту же область. Итогом такого соревнования может быть то, что процесс B прочтет неверные данные. - Допустим, что процесс A изменяет некоторую область файла, в то время как процесс C также изменяет ту же самую область. В итоге эта область может содержать неверные данные (часть - от процесса A, часть - от C). Ясно, что требуется механизм синхронизации процессов, позволяющий не пускать другой процесс (процессы) читать и/или записывать данные в указанной области. Меха- низмов синхронизации в UNIX существует множество: от семафоров до блокировок областей файла. О последних мы и будем тут говорить.

необязательный


То есть, программа не использующая вызовов синхронизации, будет иметь доступ к данным без каких либо ограничений. Увы. Таким образом, программы, собирающиеся корректно

все


механизм синхронизации: заключить между собой "джентльменское соглашение". 6.9.1. Блокировка устанавливается при помощи вызова

_


fd


operation


_


lock


имеет такие поля:

l


l


l


l


l


l


l


тип блокировки: А. Богатырев, 1992-95 - 241 - Си в UNIX

_


_


_


l


описывают сегмент файла, на который ставится замок: от точки

l


_


если все три параметра равны 0, то будет заблокирован весь файл.

_


lock


замок на область, пересекающуюся с указанной уже кем-то установлен, то сперва дождаться снятия этого замка. Пытаемся | Нет Уже есть уже есть поставить | чужих замок замок замок на | замков на READ на WRITE -----------|--------------------------------------------------------------- READ | читать читать ждать;запереть;читать WRITE | записать ждать;запереть;записать ждать;запереть;записать UNLOCK | отпереть отпереть отпереть - Если кто-то читает сегмент файла, то другие тоже могут его читать свободно, ибо чтение не изменяет файла. - Если же кто-то записывает файл - то все остальные должны дождаться окончания записи и разблокировки. - Если кто-то читает сегмент, а другой процесс собрался изменить (записать) этот сегмент, то этот другой процесс обязан дождаться окончания чтения первым.

отпереть


ровно один из них получает доступ (может установить свою блокировку). Порядок - кто из них будет первым - вообще говоря не определен.

_


lock


lock


l


_


lock


l


l


ется через сетевые файловые системы). Замки автоматически снимаются при закрытии дескриптора файла. Замки не наследу- ются порожденным процессом при вызове fork. #include <stdio.h> #include <sys/types.h> #include <fcntl.h> #include <unistd.h> #include <time.h> #include <signal.h> char DataFile [] = "data.xxx"; char info [] = "abcdefghijklmnopqrstuvwxyz"; #define OFFSET 5 #define SIZE 12 #define PAUSE 2 int trial = 1; int fd, pid; char buffer[120], myname[20]; void writeAccess(), readAccess(); А. Богатырев, 1992-95 - 242 - Си в UNIX void fcleanup(int nsig){ unlink(DataFile); printf("cleanup:%s\n", myname); if(nsig) exit(0); } int main(){ int i; fd = creat(DataFile, 0644); write(fd, info, strlen(info)); close(fd); signal(SIGINT, fcleanup); sprintf(myname, fork() ? "B-%06d" : "A-%06d", pid = getpid()); srand(time(NULL)+pid); printf("%s:started\n", myname); fd = open(DataFile, O_RDWR|O_EXCL); printf("%s:opened %s\n", myname, DataFile); for(i=0; i < 30; i++){ if(rand()%2) readAccess(); else writeAccess(); } close(fd); printf("%s:finished\n", myname); wait(NULL); fcleanup(0); return 0; } А. Богатырев, 1992-95 - 243 - Си в UNIX void writeAccess(){ flock_t lock; printf("Write:%s #%d\n", myname, trial); lock.l_type = F_WRLCK; lock.l_whence = SEEK_SET; lock.l_start = (off_t) OFFSET; lock.l_len = (size_t) SIZE; if(fcntl(fd, F_SETLKW, &lock) <0) perror("F_SETLKW"); printf("\twrite:%s locked\n", myname); sprintf(buffer, "%s #%02d", myname, trial); printf ("\twrite:%s \"%s\"\n", myname, buffer); lseek (fd, (off_t) OFFSET, SEEK_SET); write (fd, buffer, SIZE); sleep (PAUSE); lock.l_type = F_UNLCK; if(fcntl(fd, F_SETLKW, &lock) <0) perror("F_SETLKW"); printf("\twrite:%s unlocked\n", myname); trial++; } void readAccess(){ flock_t lock; printf("Read:%s #%d\n", myname, trial); lock.l_type = F_RDLCK; lock.l_whence = SEEK_SET; lock.l_start = (off_t) OFFSET; lock.l_len = (size_t) SIZE; if(fcntl(fd, F_SETLKW, &lock) <0) perror("F_SETLKW"); printf("\tread:%s locked\n", myname); lseek(fd, (off_t) OFFSET, SEEK_SET); read (fd, buffer, SIZE); printf("\tcontents:%s \"%*.*s\"\n", myname, SIZE, SIZE, buffer); sleep (PAUSE); lock.l_type = F_UNLCK; if(fcntl(fd, F_SETLKW, &lock) <0) perror("F_SETLKW"); printf("\tread:%s unlocked\n", myname); trial++; } А. Богатырев, 1992-95 - 244 - Си в UNIX Исследуя выдачу этой программы, вы можете обнаружить, что READ-области могут перекры- ваться; но что никогда не перекрываются области READ и WRITE ни в какой комбинации. Если идет чтение процессом A - то запись процессом B дождется разблокировки A (чтение - не будет дожидаться). Если идет запись процессом A - то и чтение процессом B и запись процессом B дождутся разблокировки A. 6.9.2. UNIX SVR4 имеет еще один интерфейс для блокировки файлов: функцию lockf. #include <unistd.h>

fd


operation


_


Разблокировать указанный сегмент файла (это может снимать один или несколько замков).

_


_


Установить замок. При этом, если уже имеется чужой замок на запрашиваемую

_


ращает -1, errno устанавливается в EAGAIN). - Ожидание отпирания/запирания замка может быть прервано сигналом. - Замок устанавливается следующим образом: от текущей позиции указателя чтения-

fd


size


текущей позиции к началу файла. Нулевое значение - означает "от текущей позиции до конца файла". При этом "конец файла" понимается именно как конец, а не как текущий размер файла. Если файл изменит размер, запертая область все равно будет простираться до конца файла (уже нового). - Замки, установленные процессом, автоматически отпираются при завершении про- цесса.

_


Проверить наличие замка. Функция возвращает 0, если замка нет; -1 в противном случае (заперто). Если устанавливается замок, перекрывающийся с уже установленным, то замки объединя- ются. было: ___________#######____######__________ запрошено:______________##########______________ стало: ___________#################__________ Если снимается замок с области, покрывающей только часть заблокированной прежде, остаток области остается как отдельный замок. было: ___________#################__________ запрошено:______________XXXXXXXXXX______________ стало: ___________###__________####__________

6.10. Файлы устройств.


файловых систем


дальнейшем FS), т.е. логических и/или физических дисков. Каждая файловая система

свой


корневой каталог. Файлы в каждой FS имеют свои собственные I-узлы и собственную их нумерацию с 1. В начале каждой FS зарезервированы: А. Богатырев, 1992-95 - 245 - Си в UNIX - блок для загрузчика - программы, вызываемой аппаратно при включении машины (заг- рузчик записывает с диска в память машины программу /boot, которая в свою оче- редь загружает в память ядро /unix);

суперблок


блоках), размер блока (512, 1024, ...), количество I-узлов, начало списка сво- бодных блоков, и другие сведения об FS; - некоторая непрерывная область диска для хранения I-узлов - "I-файл".

монтирования


подключения корня файловой системы к какому-то из каталогов-"листьев" дерева другой FS. Файлы в объединенной иерархии адресуются при помощи двух способов: - имен, задающих путь в дереве каталогов:

usr


bin


bin


программ


телей); - внутренних адресов, используемых программами ядра и некоторыми системными прог- раммами.

собственная


иерархии должен адресоваться ДВУМЯ параметрами: - номером (кодом) устройства, содержащего файловую систему, в которой находится

_


_


имени файла


выполняет в ядре уже упоминавшаяся выше функция namei (при помощи просмотра катало- гов):

ip


i


самом диске не хранятся!). Рассмотрим некоторые алгоритмы работы ядра с файлами. Ниже они приведены чисто

схематично


названия


Опущены проверки на корректность, подсчет ссылок на структуры file и inode, блоки- ровка I-узлов и кэш-буферов от одновременного доступа, и многое другое. Пусть мы хотим открыть файл для чтения и прочитать из него некоторую информацию. Вызовы открытия и закрытия файла имеют схему (часть ее будет объяснена позже):

sys


sys


sys


fd


fd


u


// Найти файл по имени. Создается копия I-узла в памяти:

ip


// namei может выдать ошибку, если нет такого файла

u


// Выделяется структура "открытый файл":

fp


fp


А. Богатырев, 1992-95 - 246 - Си в UNIX

fp


fp


// Выделить новый дескриптор

fd


u


goto done;

u


done:

u


// Если это устройство - инициализировать его.

ip


dev


ip


cdevsw


ip


bdevsw


fd


}

fd


fp


ip


dev


ip


cdevsw


ip


bdevsw


u


// и удалить ненужные структуры из ядра. } Теперь рассмотрим функцию преобразования логических блоков файла в номера физических блоков в файловой системе. Для этого преобразования в I-узле файла содержится таблица адресов блоков. Она устроена довольно сложно - ее начало находится в узле, а продол- жение - в нескольких блоках в самой файловой системе (устройство это можно увидеть в примере "Фрагментированность файловой системы" в приложении). Мы для простоты будем

i


bno


bno


устройства


ческой файловой системе. Поэтому у устройств нет таблицы адресов блоков. Вместо

i


i


i


байто


которыми производится по одному байту (как с терминалом или с коммуникационным пор-

блочно


тип


файла


ip


А. Богатырев, 1992-95 - 247 - Си в UNIX одним из значений: IFCHR - байтовое; или IFBLK - блочное. Алгоритм вычисления номера блока:

u


u


sys


_


ip


offset


sz


// вычислить логический номер блока по позиции RWptr. // BSIZE - это размер блока файловой системы,

sys


bno


offset


u


offset


sz


// столько байт надо взять из этого блока,

u


count


u


Если файл представляет собой устройство, то трансляция логических блоков в физические не производится - устройство представляет собой "сырой" диск без файлов и каталогов, т.е. обращение происходит сразу по физическому номеру блока:

ip


bno


// иначе провести пересчет:

rem


// это остаток файла.

rem


// файл короче, чем заказано нами:

rem


u


// и, собственно, замена логич. номера на физич.

ip


}

u


даются как статические через вспомогательные переменные в u-area процесса.

fd


srccount


fp


ip


bp


bno


dev


которого


на котором


dev


А. Богатырев, 1992-95 - 248 - Си в UNIX

ip


ip


case IFCHR: // байто-ориентированное устройство

cdevsw


// прочие параметры передаются через u-area break; case IFREG: // обычный файл case IFDIR: // каталог case IFBLK: // блочно-ориентированное устройство do{

bno


u


bp


bp


Функция iomove копирует данные

bp


из адресного пространства ядра (из буфера в ядре) в адресное пространство процесса по адресам

u


u


_


производит обратное копирование - из памяти процесса в память ядра. Продолжим: // продвинуть счетчики и указатели:

u


u


fp


u


break; ...

srccount


} // end read Теперь обсудим некоторые места этого алгоритма. Сначала посмотрим, как происходит обращение к байтовому устройству. Вместо адресов блоков мы получаем код устройства

i


мажор


dev


dev


Мажор


веров (если у нас есть 8 терминалов, то их обслуживает один и тот же драйвер); а

минор


0..7). Миноры обычно служат индексами в некоторой таблице структур внутри выбранного драйвера. Мажор же служит индексом в переключательной таблице устройств. При этом блочно-ориентированные устройства выбираются в одной таблице - bdevsw[], а байто-

sys


block


выполняющих некоторые предопределенные операции способом, зависимым от устройства. Сами эти функции реализованы в драйверах устройств. Аргументом для этих функций

минор


А. Богатырев, 1992-95 - 249 - Си в UNIX

индекс


ройства данного типа; как индекс в массиве управляющих структур (содержащих текущее состояние, режимы работы, адреса функций прерываний, адреса очередей данных и.т.п. каждого конкретного устройства) для данного типа устройств. Эти управляющие структуры

различны


Каждая строка переключательной таблицы содержит адреса функций, выполняющих опе- рации open, close, read, write, ioctl, select. open служит для инициализации уст-

ip


ip


ройств поля для read и write объединены в функцию strategy, вызываемую с параметром

_


ройства. Операция select - для опроса: есть ли поступившие в устройство данные (нап- ример, есть ли в clist-е ввода с клавиатуры байты? см. главу "Экранные библиотеки"). Вызов select применим только к некоторым байтоориентированным устройствам и сетевым портам (socket-ам). Если данное устройство не умеет выполнять такую операцию, то есть запрос к этой операции должен вернуть в программу ошибку (например, операция read неприменима к принтеру), то в переключательной таблице содержится специальное имя функции nodev; если же операция допустима, но является фиктивной (как write для /dev/null) - имя nulldev. Обе эти функции-заглушки представляют собой "пустышки": {}. Теперь обратимся к блочно-ориентированным устройствам. UNIX использует внутри

буферизацию


bp


dev


кэш


"запасать"). Заголовки кэш-буферов (struct buf) организованы в список и имеют поля

sys


b


код устройства, с которого прочитан блок;

b


номер физического блока, хранящегося в буфере в данный момент;

b


флаги блока (см. ниже);

b


адрес участка памяти (как правило в самом ядре), в котором собственно и хранится содержимое блока. Буферизация блоков позволяет системе экономить число обращений к диску. При обраще-

dev


блок уже был ранее прочитан в кэш, то обращения к диску не происходит, поскольку копия содержимого дискового блока уже есть в памяти ядра. Если же блока еще нет в кэш-буферах, то в ядре выделяется чистый буфер, в заголовке ему прописываются нужные

b


bp


bdevsw


bno


из драйвера конкретного устройства. Когда мы что-то изменяем в файле вызовом write(), то изменения на самом деле происходят в кэш-буферах в памяти ядра, а не сразу на диске. При записи в блок буфер

измененный


b


____________________ |- Следует отличать эту системную буферизацию от буферизации при помощи библиотеки

процессе


ядра


А. Богатырев, 1992-95 - 250 - Си в UNIX и на диск немедленно не записывается. Измененные буфера физически записываются на диск в таких случаях: - Был сделан системный вызов sync(); - Ядру не хватает кэш-буферов (их число ограничено). Тогда самый старый буфер (к которому дольше всего не было обращений) записывается на диск и после этого используется для другого блока. - Файловая система была отмонтирована вызовом umount;

не измененные


диске и так содержатся те же самые данные). Даже если файл уже закрыт close, его блоки могут быть еще не записаны на диск - запись произойдет лишь при вызове sync. Это означает, что измененные блоки записываются на диск "массированно" - по многу блоков, но не очень часто, что позволяет оптимизировать и саму запись на диск: сорти- ровкой блоков можно достичь минимизации перемещения магнитных головок над диском. Отслеживание самых "старых" буферов происходит за счет реорганизации списка заголовков кэш-буферов. В большом упрощении это можно представить так: как только к блоку происходит обращение, соответствующий заголовок переставляется в начало списка. В итоге самый "пассивный" блок оказывается в хвосте - он то и переиспользуется при нужде. "Подвисание" файлов в памяти ядра значительно ускоряет работу программ, т.к. работа с памятью гораздо быстрее, чем с диском. Если блок надо считать/записать, а он уже есть в кэше, то реального обращения к диску не происходит. Зато, если случится сбой питания (или кто-то неаккуратно выключит машину), а некоторые буфера еще не были сброшены на диск - то часть изменений в файлах будет потеряна. Для принудительной записи всех измененных кэш-буферов на диск существует сисвызов "синхронизации" содер- жимого дисков и памяти sync(); // synchronize Вызов sync делается раз в 30 секунд специальным служебным процессом /etc/update, запускаемым при загрузке системы. Для работы с файлами, которые должны гарантиро- ванно быть корректными на диске, используется открытие файла

fd


немедленно


диск. Это делает работу надежнее, но существенно медленнее. Специальные файлы устройств не могут быть созданы вызовом creat, создающим только обычные файлы. Файлы устройств создаются вызовом mknod:

sys


dev


major


имяФайла


dev


тип


sys


mknod доступен для выполнения только суперпользователю (за исключением случая

_


ный с существующим диском, и читать информацию с него напрямую, в обход механизмов логической файловой системы и защиты файлов кодами доступа. Можно создать файл устройства с мажором и/или минором, не отвечающим никакому реальному устройству (нет такого драйвера или минор слишком велик). Открытие таких ____________________ |= Обычно к блочным устройствам (дискам) доступ разрешается только суперпользова- телю, в противном случае можно прочитать с "сырого" диска (в обход механизмов файло- вой системы) физические блоки любого файла и весь механизм защиты окажется неработаю- щим. А. Богатырев, 1992-95 - 251 - Си в UNIX устройств выдает код ошибки ENODEV. Из нашей программы мы можем вызовом stat() узнать код устройства, на котором

_


циальным файлом (интерфейсом драйвера устройства), то код самого этого устройства

_


два имени к одному и тому же файлу: #include <sys/types.h> #include <sys/stat.h> void main(ac, av) char *av[]; { struct stat st1, st2; int eq; if(ac != 3) exit(13); stat(av[1], &st1); stat(av[2], &st2); if(eq =

st


st


printf("%s и %s - два имени одного файла\n",av[1],av[2]); exit( !eq ); } Наконец, вернемся к склейке нескольких файловых систем в одну объединенную иерархию: ino=2 *------ корневая файловая система / \ /\ на диске /dev/hd0 / /\ /\ \

mnt


: * ino=2 FS на диске /dev/hd1 / \ (removable FS) /\ \ Для того, чтобы поместить корневой каталог файловой системы, находящейся на диске

dev


сисвызов

hd1


Для отключения смонтированной файловой системы мы должны вызвать

hd1


(каталог, к которому она смонтирована, уже числится в таблице ядра, поэтому его зада-

mnt


mnt


dev


быть выявлен по тому признаку, что "." и ".." в нем лежат на разных устройствах:

st1


st1


st1


st1


жащего корневую файловую систему. Операции монтирования и отмонтирования файловых систем доступны только суперпользователю. И напоследок - сравнение структур I-узла. на диске в памяти в вызове stat

sys


А. Богатырев, 1992-95 - 252 - Си в UNIX struct dinode struct inode struct stat // коды доступа и тип файла

di


// число имен файла

di


// номер I-узла

i


// идентификатор владельца

di


// идентификатор группы владельца

di


// размер файла в байтах

di


// время создания

di


// время последнего изменения (write)

di


// время последнего доступа (read/write)

di


// устройство, на котором расположен файл

i


// устройство, к которому приводит спец.файл

i


// адреса блоков

di


// счетчик ссылок на структуру в ядре

i


// и кое-что еще Минусы означают, что данное поле не хранится на диске, а вычисляется ядром. В совре- менных версиях UNIX могут быть легкие отличия от вышенаписанной таблицы. 6.10.1. Напишите программу pwd, определяющую полное имя текущего рабочего каталога. #define U42 определяет файловую систему с длинными именами, отсутствие этого флага - с короткими (14 символов). А. Богатырев, 1992-95 - 253 - Си в UNIX /* Команда pwd. * Текст getwd() взят из исходных текстов библиотеки языка Си. */ #include <stdio.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <dirent.h> #define ediag(e,r) (e) /* * getwd() возвращает полное имя текущего рабочего каталога. * При ошибке возвращается NULL, а в pathname копируется сообщение * об ошибке. */ #ifndef MAXPATHLEN #define MAXPATHLEN 128 #endif #define CURDIR "." /* имя текущего каталога */ #define PARENTDIR ".." /* имя родительского каталога */ #define PATHSEP "/" /* разделитель компонент пути */ #define ROOTDIR "/" /* корневой каталог */ #define GETWDERR(s) strcpy(pathname, (s)); #define CP(to,from) strncpy(to,from.d_name,DIRSIZ),to[DIRSIZ]='\0' char *strcpy(char *, char *); char *strncpy(char *, char *, int); char *getwd(char *pathname); static char *prepend(char *dirname, char *pathname); static int pathsize; /* длина имени */ #ifndef U42 char *getwd(char *pathname) { char pathbuf[MAXPATHLEN]; /* temporary pathname buffer */ char *pnptr = &pathbuf[(sizeof pathbuf)-1]; /* pathname pointer */ dev_t rdev; /* root device number */ int fil = (-1); /* directory file descriptor */ ino_t rino; /* root inode number */ struct direct dir; /* directory entry struct */ struct stat d ,dd; /* file status struct */ /* d - "." dd - ".." | dname */ char dname[DIRSIZ+1]; /* an directory entry */ pathsize = 0; *pnptr = '\0'; if (stat(ROOTDIR, &d) < 0) { GETWDERR(ediag("getwd: can't stat /", "getwd: нельзя выполнить stat /")); return (NULL); } rdev = d.st_dev; /* код устройства, на котором размещен корень */ rino = d.st_ino; /* номер I-узла, представляющего корневой каталог */ А. Богатырев, 1992-95 - 254 - Си в UNIX for (;;) { if (stat(CURDIR, &d) < 0) { CantStat: GETWDERR(ediag("getwd: can't stat .", "getwd: нельзя выполнить stat .")); goto fail; } if (d.st_ino == rino && d.st_dev == rdev) break; /* достигли корневого каталога */ if ((fil = open(PARENTDIR, O_RDONLY)) < 0) { GETWDERR(ediag("getwd: can't open ..", "getwd: нельзя открыть ..")); goto fail; } if (chdir(PARENTDIR) < 0) { GETWDERR(ediag("getwd: can't chdir to ..", "getwd: нельзя перейти в ..")); goto fail; } if (fstat(fil, &dd) < 0) goto CantStat; if (d.st_dev == dd.st_dev) { /* то же устройство */ if (d.st_ino == dd.st_ino) { /* достигли корня ".." == "." */ close(fil); break; } do { if (read(fil, (char *) &dir, sizeof(dir)) < sizeof(dir) ){ ReadErr: close(fil); GETWDERR(ediag("getwd: read error in ..", "getwd: ошибка чтения ..")); goto fail; } } while (dir.d_ino != d.st_ino); CP(dname,dir); } else /* ".." находится на другом диске: mount point */ do { if (read(fil, (char *) &dir, sizeof(dir)) < sizeof(dir)) goto ReadErr; if( dir.d_ino == 0 ) /* файл стерт */ continue; CP(dname,dir); if (stat(dname, &dd) < 0) { sprintf (pathname, "getwd: %s %s", ediag ("can't stat", "нельзя выполнить stat"), dname); goto fail; } } while(dd.st_ino != d.st_ino || dd.st_dev != d.st_dev); close(fil); pnptr = prepend(PATHSEP, prepend(dname, pnptr)); } А. Богатырев, 1992-95 - 255 - Си в UNIX if (*pnptr == '\0') /* текущий каталог == корневому */ strcpy(pathname, ROOTDIR); else { strcpy(pathname, pnptr); if (chdir(pnptr) < 0) { GETWDERR(ediag("getwd: can't change back to .", "getwd: нельзя вернуться в .")); return (NULL); } } return (pathname); fail: close(fil); chdir(prepend(CURDIR, pnptr)); return (NULL); } #else /* U42 */ extern char *strcpy (); extern DIR *opendir(); char *getwd (char *pathname) { char pathbuf[MAXPATHLEN];/* temporary pathname buffer */ char *pnptr = &pathbuf[(sizeof pathbuf) - 1];/* pathname pointer */ char *prepend (); /* prepend dirname to pathname */ dev_t rdev; /* root device number */ DIR * dirp; /* directory stream */ ino_t rino; /* root inode number */ struct dirent *dir; /* directory entry struct */ struct stat d, dd; /* file status struct */ pathsize = 0; *pnptr = '\0'; stat (ROOTDIR, &d); rdev = d.st_dev; rino = d.st_ino; for (;;) { stat (CURDIR, &d); if (d.st_ino == rino && d.st_dev == rdev) break; /* reached root directory */ if ((dirp = opendir (PARENTDIR)) == NULL) { GETWDERR ("getwd: can't open .."); goto fail; } if (chdir (PARENTDIR) < 0) { closedir (dirp); GETWDERR ("getwd: can't chdir to .."); goto fail; } А. Богатырев, 1992-95 - 256 - Си в UNIX fstat (dirp -> dd_fd, &dd); if (d.st_dev == dd.st_dev) { if (d.st_ino == dd.st_ino) { /* reached root directory */ closedir (dirp); break; } do { if ((dir = readdir (dirp)) == NULL) { closedir (dirp); GETWDERR ("getwd: read error in .."); goto fail; } } while (dir -> d_ino != d.st_ino); } else do { if ((dir = readdir (dirp)) == NULL) { closedir (dirp); GETWDERR ("getwd: read error in .."); goto fail; } stat (dir -> d_name, &dd); } while (dd.st_ino != d.st_ino || dd.st_dev != d.st_dev); closedir (dirp); pnptr = prepend (PATHSEP, prepend (dir -> d_name, pnptr)); } if (*pnptr == '\0') /* current dir == root dir */ strcpy (pathname, ROOTDIR); else { strcpy (pathname, pnptr); if (chdir (pnptr) < 0) { GETWDERR ("getwd: can't change back to ."); return (NULL); } } return (pathname); fail: chdir (prepend (CURDIR, pnptr)); return (NULL); } #endif А. Богатырев, 1992-95 - 257 - Си в UNIX /* * prepend() tacks a directory name onto the front of a pathname. */ static char *prepend ( register char *dirname, /* что добавлять */ register char *pathname /* к чему добавлять */ ) { register int i; /* длина имени каталога */ for (i = 0; *dirname != '\0'; i++, dirname++) continue; if ((pathsize += i) < MAXPATHLEN) while (i-- > 0) *--pathname = *--dirname; return (pathname); } #ifndef CWDONLY void main(){ char buffer[MAXPATHLEN+1]; char *cwd = getwd(buffer); printf( "%s%s\n", cwd ? "": "ERROR:", buffer); } #endif 6.10.2. Напишите функцию canon(), канонизирующую имя файла, т.е. превращающую его в

полное


usr


book


. -> /usr/abs/C-book .. -> /usr/abs ../.. -> /usr ////.. -> / /aa -> /aa /aa/../bb -> /bb cc//dd/../ee -> /usr/abs/C-book/cc/ee ../a/b/./d -> /usr/abs/a/b/d Ответ: #include <stdio.h> /* слэш, разделитель компонент пути */ #define SLASH '/' extern char *strchr (char *, char), *strrchr(char *, char); struct savech{ char *s, c; }; #define SAVE(sv, str) (sv).s = (str); (sv).c = *(str) #define RESTORE(sv) if((sv).s) *(sv).s = (sv).c /* Это структура для использования в таком контексте: void main(){ char *d = "hello"; struct savech ss; SAVE(ss, d+3); *(d+3) = '\0'; printf("%s\n", d); RESTORE(ss); printf("%s\n", d); } */ /* ОТСЕЧЬ ПОСЛЕДНЮЮ КОМПОНЕНТУ ПУТИ */ struct savech parentdir(char *path){

strrchr


А. Богатырев, 1992-95 - 258 - Си в UNIX

strchr


struct savech sp; sp.s = NULL; sp.c = '\0'; if( last == NULL ) return sp; /* не полное имя */ if( last[1] == '\0' ) return sp; /* корневой каталог */ if( last == first ) /* единственный слэш: /DIR */ last++; sp.s = last; sp.c = *last; *last = '\0'; return sp; } #define isfullpath(s) (*s == SLASH) /* КАНОНИЗИРОВАТЬ ИМЯ ФАЙЛА */ void canon( char *where, /* куда поместить ответ */ char *cwd, /* полное имя текущего каталога */ char *path /* исходное имя для канонизации */ ){ char *s, *slash; /* Сформировать имя каталога - точки отсчета */

isfullpath


strchr


strncpy


where[s - path + 1] = '\0';

strcpy


path = s+1; /* остаток пути без '/' в начале */

strcpy


/* Покомпонентный просмотр пути */

strchr


/* теперь path содержит очередную компоненту пути */

strcmp


/* то просто проигнорировать "." и лишние "///" */

strcmp


parentdir


strlen


/* добавить в конец разделяющий слэш */ if( where[len-1] != SLASH ){ where[len] = SLASH; where[len+1] = '\0'; }

strcat


/* +len чисто для ускорения поиска

strcat


} if(slash){ *slash = SLASH; /* восстановить */ path = slash + 1; } } while (slash != NULL); } char cwd[256], input[256], output[256]; void main(){ /* Узнать полное имя текущего каталога.

getcwd


popen


*/

getcwd


gets


canon


printf


} } А. Богатырев, 1992-95 - 259 - Си в UNIX В этом примере (изначально писавшемся для MS DOS) есть "странное" место, помеченное /*@*/. Дело в том, что в DOS функция isfullpath была способна распознавать имена фай-

C


6.11. Мультиплексирование ввода-вывода.


Данная глава посвящена системному вызову select, который, однако, мы предостав-

нес-


колько


новая информация - сообщать об этом нашей программе. Обычно это бывает связано с дескрипторами, ведущими к сетевым устройствам. 6.11.1. /* Пример использования вызова select() для мультиплексирования * нескольких каналов ввода. Этот вызов можно также использовать * для получения таймаута. * Вызов: войти на терминалах tty01 tty02 и набрать на каждом * sleep 30000 * затем на tty00 сказать select /dev/tty01 /dev/tty02 * и вводить что-либо на терминалах tty01 и tty02 * Сборка: cc select.c -o select -lsocket */ #include <stdio.h> #include <fcntl.h> #include <sys/types.h> /* fd_set, FD_SET, e.t.c. */ #include <sys/param.h> /* NOFILE */ #include <sys/select.h> #include <sys/time.h> #include <sys/filio.h> /* для FIONREAD */ #define max(a,b) ((a) > (b) ? (a) : (b)) char buf[512]; /* буфер чтения */ int fdin, fdout; /* дескрипторы каналов stdin, stdout */ int nready; /* число готовых каналов */ int nopen; /* число открытых каналов */ int maxfd = 0; /* максимальный дескриптор */ int nfds; /* сколько первых дескрипторов проверять */ int f; /* текущий дескриптор */ fd_set set, rset; /* маски */ /* таблица открытых нами файлов */ struct _fds { int fd; /* дескриптор */ char name[30]; /* имя файла */ } fds[ NOFILE ] = { /* NOFILE - макс. число открытых файлов на процесс */ { 0, "stdin" }, { 1, "stdout" }, { 2, "stderr" } /* все остальное - нули */ }; struct timeval timeout, rtimeout; /* выдать имя файла по дескриптору */ char *N( int fd ){ register i; for(i=0; i < NOFILE; i++) if(fds[i].fd == fd ) return fds[i].name; return "???"; } А. Богатырев, 1992-95 - 260 - Си в UNIX void main( int ac, char **av ){ nopen = 3; /* stdin, stdout, stderr */ for( f = 3; f < NOFILE; f++ ) fds[f].fd = (-1); fdin = fileno(stdin); fdout = fileno(stdout); setbuf(stdout, NULL); /* отмена буферизации */ FD_ZERO(&set); /* очистка маски */ for(f=1; f < ac; f++ ) if((fds[nopen].fd = open(av[f], O_RDONLY)) < 0 ){ fprintf(stderr, "Can't read %s\n", av[f] ); continue; } else { FD_SET(fds[nopen].fd, &set ); /* учесть в маске */ maxfd = max(maxfd, fds[nopen].fd ); strncpy(fds[nopen].name, av[f], sizeof(fds[0].name) - 1); nopen++; } if( nopen == 3 ){ fprintf(stderr, "Nothing is opened\n"); exit(1); } FD_SET(fdin, &set); /* учесть stdin */ maxfd = max(maxfd, fdin ); nopen -= 2; /* stdout и stderr не участвуют в select */ timeout.tv_sec = 10; /* секунд */ timeout.tv_usec = 0; /* миллисекунд */ /* nfds - это КОЛИЧЕСТВО первых дескрипторов, которые надо * просматривать. Здесь можно использовать * nfds = NOFILE; (кол-во ВСЕХ дескрипторов ) * или nfds = maxfd+1; (кол-во = номер последнего+1) * ( +1 т.к. нумерация fd идет с номера 0, а количество - с 1). */ nfds = maxfd + 1; while( nopen ){ rset = set; rtimeout = timeout; /* копируем, т.к. изменятся */ /* опрашивать можно FIFO-файлы, терминалы, pty, socket-ы, stream-ы */ nready = select( nfds, &rset, NULL, NULL, &rtimeout ); /* Если вместо &rtimeout написать NULL, то ожидание будет * бесконечным (пока не собьют сигналом) */ if( nready <= 0 ){ /* ничего не поступило */ fprintf(stderr, "Timed out, nopen=%d\n", nopen); continue; } А. Богатырев, 1992-95 - 261 - Си в UNIX /* опрос готовых дескрипторов */ for(f=0; f < nfds; f++ ) if( FD_ISSET(f, &rset)){ /* дескриптор f готов */ int n; /* Вызов FIONREAD позволяет запросить * число байт готовых к передаче * через дескриптор. */ if(ioctl(f, FIONREAD, &n) < 0) perror("FIONREAD"); else printf("%s have %d bytes.\n", N(f), n); if((n = read(f, buf, sizeof buf)) <= 0 ){ eof: FD_CLR(f, &set); /* исключить */ close(f); nopen--; fprintf(stderr, "EOF in %s\n", N(f)); } else { fprintf(stderr, "\n%d bytes from %s:\n", n, N(f)); write(fdout, buf, n); if( n == 4 && !strncmp(buf, "end\n", 4)) /* ncmp, т.к. buf может не оканчиваться \0 */ goto eof; } } } exit(0); } 6.11.2. В качестве самостоятельной работы предлагаем вам пример программы, ведущей протокол сеанса работы. Информацию о псевдотерминалах изучите самостоятельно. А. Богатырев, 1992-95 - 262 - Си в UNIX /* * script.c * Программа получения трассировки работы других программ. * Используется системный вызов опроса готовности каналов * ввода/вывода select() и псевдотерминал (пара ttyp+ptyp). */ #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <signal.h> #include <sys/param.h> /* NOFILE */ #include <sys/times.h> #include <sys/wait.h> #include <errno.h> #ifdef TERMIOS # include <termios.h> # define TERMIO struct termios # define GTTY(fd, tadr) tcgetattr(fd, tadr) # define STTY(fd, tadr) tcsetattr(fd, TCSADRAIN, tadr) #else # include <termio.h> # define TERMIO struct termio # define GTTY(fd, tadr) ioctl(fd, TCGETA, tadr) # define STTY(fd, tadr) ioctl(fd, TCSETAW, tadr) #endif А. Богатырев, 1992-95 - 263 - Си в UNIX #ifdef __SVR4 # include <stropts.h> /* STREAMS i/o */ extern char *ptsname(); #endif #if defined(ISC2_2) # include <sys/bsdtypes.h> #else # include <sys/select.h> #endif #ifndef BSIZE # define BSIZE 512 #endif #define LOGFILE "/usr/spool/scriptlog" #define max(a,b) ((a) > (b) ? (a) : (b)) extern int errno; TERMIO told, tnew, ttypmodes; FILE *fpscript = NULL; /* файл с трассировкой (если надо) */ int go = 0; int scriptflg = 0; int halfflag = 0; /* HALF DUPLEX */ int autoecho = 0; char *protocol = "typescript"; #define STDIN 0 /* fileno(stdin) */ #define STDOUT 1 /* fileno(stdout) */ #define STDERR 2 /* fileno(stderr) */ /* какие каналы связаны с терминалом? */ int tty_stdin, tty_stdout, tty_stderr; int TTYFD;

_


TERMIO t; tty_stdin = ( GTTY(STDIN, &t) >= 0 ); tty_stdout = ( GTTY(STDOUT, &t) >= 0 ); tty_stderr = ( GTTY(STDERR, &t) >= 0 ); if ( tty_stdin ) TTYFD = STDIN; else if( tty_stdout ) TTYFD = STDOUT; else if( tty_stderr ) TTYFD = STDERR; else { fprintf(stderr, "Cannot access tty\n"); exit(7); } } А. Богатырев, 1992-95 - 264 - Си в UNIX /* Описатель трассируемого процесса */ struct ptypair { char line[25]; /* терминальная линия: /dev/ttyp? */ int pfd; /* дескриптор master pty */ long in_bytes; /* прочтено байт с клавиатуры */ long out_bytes; /* послано байт на экран */ int pid; /* идентификатор процесса */ time_t t_start, t_stop; /* время запуска и окончания */ char *command; /* запущенная команда */ } PP; /* Эта функция вызывается при окончании трассируемого процесса - * по сигналу SIGCLD */ char Reason[128]; void ondeath(sig){ int pid; extern void wm_done(); int status; int fd; /* выявить причину окончания процесса */ while((pid = wait(&status)) > 0 ){ if( WIFEXITED(status)) sprintf( Reason, "Pid %d died with retcode %d", pid, WEXITSTATUS(status)); else if( WIFSIGNALED(status)) { sprintf( Reason, "Pid %d killed by signal #%d", pid, WTERMSIG(status)); #ifdef WCOREDUMP if(WCOREDUMP(status)) strcat( Reason, " Core dumped" ); #endif } else if( WIFSTOPPED(status)) sprintf( Reason, "Pid %d suspended by signal #%d", pid, WSTOPSIG(status)); } wm_done(0); }

_


wm_checkttys(); GTTY(TTYFD, &told); /* Сконструировать "сырой" режим для нашего _базового_ терминала */ tnew = told; tnew.c_cc[VINTR] = '\0'; tnew.c_cc[VQUIT] = '\0'; tnew.c_cc[VERASE] = '\0'; tnew.c_cc[VKILL] = '\0'; #ifdef VSUSP tnew.c_cc[VSUSP] = '\0'; #endif А. Богатырев, 1992-95 - 265 - Си в UNIX /* CBREAK */ tnew.c_cc[VMIN] = 1; tnew.c_cc[VTIME] = 0; tnew.c_cflag &= ~(PARENB|CSIZE); tnew.c_cflag |= CS8; tnew.c_iflag &= ~(ISTRIP|ICRNL); tnew.c_lflag &= ~(ICANON|ECHO|ECHOK|ECHOE|XCASE); tnew.c_oflag &= ~OLCUC; /* но оставить c_oflag ONLCR и TAB3, если они были */ /* моды для псевдотерминала */ ttypmodes = told; /* не выполнять преобразования на выводе: * ONLCR: \n --> \r\n * TAB3: \t --> пробелы */ ttypmodes.c_oflag &= ~(ONLCR|TAB3); (void) signal(SIGCLD, ondeath); }

_


STTY(TTYFD, &tnew); }

_


STTY(TTYFD, &told); } /* Подобрать свободный псевдотерминал для трассируемого процесса */

_


struct ptypair p; #ifdef __SVR4 p.pfd = (-1); p.pid = 0; p.in_bytes = p.out_bytes = 0; /* Открыть master side пары pty (еще есть slave) */ if((p.pfd = open( "/dev/ptmx", O_RDWR)) < 0 ){ /* Это клонируемый STREAMS driver. * Поскольку он клонируемый, то есть создающий новое псевдоустройство * при каждом открытии, то на master-стороне может быть только * единственный процесс! */ perror( "Open /dev/ptmx" ); goto err; } А. Богатырев, 1992-95 - 266 - Си в UNIX # ifdef notdef /* Сделать права доступа к slave-стороне моими. */ if( grantpt (p.pfd) < 0 ){ perror( "grantpt"); exit(errno); } # endif /* Разблокировать slave-сторону псевдотерминала: позволить первый open() для нее */ if( unlockpt(p.pfd) < 0 ){ perror( "unlockpt"); exit(errno); } /* Получить и записать имя нового slave-устройства-файла. */ strcpy( p.line, ptsname(p.pfd)); #else register i; char c; struct stat st; p.pfd = (-1); p.pid = 0; p.in_bytes = p.out_bytes = 0; strcpy( p.line, "/dev/ptyXX" ); for( c = 'p'; c <= 's'; c++ ){ p.line[ strlen("/dev/pty") ] = c; p.line[ strlen("/dev/ptyp")] = '0'; if( stat(p.line, &st) < 0 ) goto err; for(i=0; i < 16; i++){ p.line[ strlen("/dev/ptyp") ] = "0123456789abcdef" [i] ; if((p.pfd = open( p.line, O_RDWR )) >= 0 ){ p.line[ strlen("/dev/") ] = 't'; return p; } } } #endif err: return p; } А. Богатырев, 1992-95 - 267 - Си в UNIX /* Ведение статистики по вызовам script */

_


long in_bytes, out_bytes; time_t time_here; char *name; char *line; char *at; { FILE *fplog; struct flock lock; if((fplog = fopen( LOGFILE, "a" )) == NULL ) return; lock.l_type = F_WRLCK; lock.l_whence = 0; lock.l_start = 0; lock.l_len = 0; /* заблокировать весь файл */ fcntl ( fileno(fplog), F_SETLKW, &lock ); fprintf( fplog, "%s (%s) %ld bytes_in %ld bytes_out %ld secs %s %s %s", PP.command, Reason, in_bytes, out_bytes, time_here, name, line, at ); fflush ( fplog ); lock.l_type = F_UNLCK; lock.l_whence = 0; lock.l_start = 0; lock.l_len = 0; /* разблокировать весь файл */ fcntl ( fileno(fplog), F_SETLK, &lock ); fclose ( fplog ); }

_


char *getlogin(), *getenv(), *logname = getlogin(); time( &PP.t_stop ); /* запомнить время окончания */ wm_resettty(); /* восстановить режим базового терминала */ if( fpscript ) fclose(fpscript); if( PP.pid > 0 ) kill( SIGHUP, PP.pid ); /* "обрыв связи" */ if( go ) write_stat( PP.in_bytes, PP.out_bytes, PP.t_stop - PP.t_start, logname ? logname : getenv("LOGNAME"), PP.line, ctime(&PP.t_stop) ); printf( "\n" ); exit(0); } А. Богатырев, 1992-95 - 268 - Си в UNIX /* Запуск трассируемого процесса на псевдотерминале */

_


char **av; { int child, fd, sig; if( ac == 0 ){ static char *avshell[] = { "/bin/sh", "-i", NULL }; av = avshell; } if((child = fork()) < 0 ){ perror("fork"); wm_done(errno); } if( child == 0 ){ /* SON */ if( tty_stdin ) setpgrp(); /* отказ от управляющего терминала */ /* получить новый управляющий терминал */ if((fd = open( PP.line, O_RDWR )) < 0 ){ exit(errno); } /* закрыть лишние каналы */ if( fpscript ) fclose(fpscript); close( PP.pfd ); #ifdef __SVR4 /* Push pty compatibility modules onto stream */ ioctl(fd, I_PUSH, "ptem"); /* pseudo tty module */ ioctl(fd, I_PUSH, "ldterm"); /* line discipline module */ ioctl(fd, I_PUSH, "ttcompat"); /* BSD ioctls module */ #endif /* перенаправить каналы, связанные с терминалом */ if( fd != STDIN && tty_stdin ) dup2(fd, STDIN); if( fd != STDOUT && tty_stdout ) dup2(fd, STDOUT); if( fd != STDERR && tty_stderr ) dup2(fd, STDERR); if( fd > STDERR ) (void) close(fd); /* установить моды терминала */ STTY(TTYFD, &ttypmodes); /* восстановить реакции на сигналы */ for(sig=1; sig < NSIG; sig++) signal( sig, SIG_DFL ); execvp(av[0], av); system( "echo OBLOM > HELP.ME"); perror("execl"); exit(errno); А. Богатырев, 1992-95 - 269 - Си в UNIX } else { /* FATHER */ PP.pid = child; PP.command = av[0]; time( &PP.t_start ); PP.t_stop = PP.t_start; signal( SIGHUP, wm_done ); signal( SIGINT, wm_done ); signal( SIGQUIT, wm_done ); signal( SIGTERM, wm_done ); signal( SIGILL, wm_done ); signal( SIGBUS, wm_done ); signal( SIGSEGV, wm_done ); } } char buf[ BSIZE ]; /* буфер для передачи данных */ /* /dev/pty? /dev/ttyp? экран *--------* *--------* /||| | | PP.pfd | | |||||<-STDOUT--| мой |<---------| псевдо |<-STDOUT---| \||| |терминал| |терминал|<-STDERR---|трассируемый |(базовый) | | |процесс ------- | | STDIN | | | |.....|-STDIN--> |----------> |--STDIN--->| |_____| | | | | клавиатура *--------* *--------* master slave */ /* Опрос дескрипторов */

_


int nready; int nfds; int maxfd; int nopen; /* число опрашиваемых дескрипторов */ register f; fd_set set, rset; /* маски */ struct timeval timeout, rtimeout; FD_ZERO(&set); nopen = 0; /* очистка маски */ FD_SET (PP.pfd, &set); nopen++; /* учесть в маске */ FD_SET (STDIN, &set); nopen++; maxfd = max(PP.pfd, STDIN); timeout.tv_sec = 3600; /* секунд */ timeout.tv_usec = 0; /* миллисекунд */ А. Богатырев, 1992-95 - 270 - Си в UNIX nfds = maxfd + 1; while( nopen ){ rset = set; rtimeout = timeout; /* опросить дескрипторы */ if((nready = select( nfds, &rset, NULL, NULL, &rtimeout )) <= 0) continue; for(f=0; f < nfds; f++ ) if( FD_ISSET(f, &rset)){ /* дескриптор f готов */ int n; if((n = read(f, buf, sizeof buf)) <= 0 ){ FD_CLR(f, &set); nopen--; /* исключить */ close(f); } else { int fdout; /* учет и контроль */ if( f == PP.pfd ){ fdout = STDOUT; PP.out_bytes += n; if( fpscript ) fwrite(buf, 1, n, fpscript); } else if( f == STDIN ) { fdout = PP.pfd; PP.in_bytes += n; if( halfflag && fpscript ) fwrite(buf, 1, n, fpscript); if( autoecho ) write(STDOUT, buf, n); } write(fdout, buf, n); } } } } А. Богатырев, 1992-95 - 271 - Си в UNIX int main(ac, av) char **av; { while( ac > 1 && *av[1] == '-' ){ switch(av[1][1]){ case 's': scriptflg++; break; case 'f': av++; ac--; protocol = av[1]; scriptflg++; break; case 'h': halfflag++; break; case 'a': autoecho++; break; default: fprintf(stderr, "Bad key %s\n", av[1]); break; } ac--; av++; } if( scriptflg ){ fpscript = fopen( protocol, "w" ); } ac--; av++; wm_init(); PP = wm_ptypair(); if( PP.pfd < 0 ){ fprintf(stderr, "Cannot get pty. Please wait and try again.\n"); return 1; } wm_fixtty(); wm_startshell(ac, av); go++; wm_select(); wm_done(0); /* NOTREACHED */ return 0; }

6.12. Простой интерпретатор команд.


Данный раздел просто приводит исходный текст простого интерпретатора команд. Функция match описана в главе "Текстовая обработка". А. Богатырев, 1992-95 - 272 - Си в UNIX /* Примитивный интерпретатор команд. Распознает построчно * команды вида: CMD ARG1 ... ARGn <FILE >FILE >>FILE >&FILE >>&FILE * Сборка: cc -U42 -DCWDONLY sh.c match.c pwd.c -o sh */ #include <sys/types.h>/* определение типов, используемых системой */ #include <stdio.h> /* описание библиотеки ввода/вывода */ #include <signal.h> /* описание сигналов */ #include <fcntl.h> /* определение O_RDONLY */ #include <errno.h> /* коды системных ошибок */ #include <ctype.h> /* макросы для работы с символами */ #include <dirent.h> /* эмуляция файловой системы BSD 4.2 */ #include <pwd.h> /* работа с /etc/passwd */ #include <sys/wait.h> /* описание формата wait() */ char cmd[256]; /* буфер для считывания команды */ #define MAXARGS 256 /* макс. количество аргументов */ char *arg[MAXARGS]; /* аргументы команды */ char *fin, *fout; /* имена для перенаправления ввода/вывода */ int rout; /* флаги перенаправления вывода */ char *firstfound; /* имя найденной, но невыполняемой программы */ #define LIM ':' /* разделитель имен каталогов в path */ extern char *malloc(), *getenv(), *strcpy(), *getwd(); extern char *strchr(), *execat(); extern void callshell(), printenv(), setenv(), dowait(), setcwd(); extern struct passwd *getpwuid(); /* Предопределенные переменные */ extern char **environ; /* окружение: изначально смотрит на тот же * массив, что и ev из main() */ extern int errno; /* код ошибки системного вызова */ char *strdup(s)char *s; { char *p; return(p=malloc(strlen(s)+1), strcpy(p,s)); } /* strcpy() возвращает свой первый аргумент */ char *str3spl(s, p, q) char *s, *p, *q; { char *n = malloc(strlen(s)+strlen(p)+strlen(q)+1); strcpy(n, s); strcat(n, p); strcat(n, q); return n; } int cmps(s1, s2) char **s1, **s2; { return strcmp(*s1, *s2); } А. Богатырев, 1992-95 - 273 - Си в UNIX /* Перенаправить вывод */ #define APPEND 0x01 #define ERRTOO 0x02 int output (name, append, err_too, created) char *name; int *created; { int fd; *created = 0; /* Создан ли файл ? */ if( append ){ /* >>file */ /* Файл name существует? Пробуем открыть на запись */ if((fd = open (name, O_WRONLY)) < 0) { if (errno == ENOENT) /* Файл еще не существовал */ goto CREATE; else return 0; /* Не имеем права писать в этот файл */ } /* иначе fd == открытый файл, *created == 0 */ }else{ CREATE: /* Пытаемся создать (либо опустошить) файл "name" */ if((fd = creat (name, 0666)) < 0 ) return 0; /* Не могу создать файл */ else *created = 1; /* Был создан новый файл */ } if (append) lseek (fd, 0l, 2); /* на конец файла */ /* перенаправить стандартный вывод */ dup2(fd, 1); if( err_too ) dup2(fd, 2); /* err_too=1 для >& */ close(fd); return 1; } /* Перенаправить ввод */ int input (name) char *name; { int fd; if((fd = open (name, O_RDONLY)) < 0 ) return 0;/* Не могу читать */ /* перенаправить стандартный ввод */ dup2(fd, 0); close(fd); return 1; } А. Богатырев, 1992-95 - 274 - Си в UNIX /* запуск команды */ int cmdExec(progr, av, envp, inp, outp, outflg) char *progr; /* имя программы */ char **av; /* список аргументов */ char **envp; /* окружение */ char *inp, *outp; /* файлы ввода-вывода (перенаправления) */ int outflg; /* режимы перенаправления вывода */ { void (*del)(), (*quit)(); int pid; int cr = 0; del = signal(SIGINT, SIG_IGN); quit = signal(SIGQUIT, SIG_IGN); if( ! (pid = fork())){ /* ветвление */ /* порожденный процесс (сын) */ signal(SIGINT, SIG_DFL); /* восстановить реакции */ signal(SIGQUIT,SIG_DFL); /* по умолчанию */ /* getpid() выдает номер (идентификатор) данного процесса */ printf( "Процесс pid=%d запущен\n", pid = getpid()); /* Перенаправить ввод-вывод */ if( inp ) if(!input( inp )){ fprintf(stderr, "Не могу <%s\n", inp ); goto Err; } if( outp ) if(!output (outp, outflg & APPEND, outflg & ERRTOO, &cr)){ fprintf(stderr, "Не могу >%s\n", outp ); goto Err; } /* Заменить программу: при успехе * данная программа завершается, а вместо нее вызывается * функция main(ac, av, envp) программы, хранящейся в файле progr. * ac вычисляет система. */ execvpe(progr, av, envp); Err: /* при неудаче печатаем причину и завершаем порожденный процесс */ perror(firstfound ? firstfound: progr); /* Мы не делаем free(firstfound),firstfound = NULL * потому что данный процесс завершается (и тем ВСЯ его * память освобождается) : */ if( cr && outp ) /* был создан новый файл */ unlink(outp); /* но теперь он нам не нужен */ exit(errno); } /* процесс - отец */ /* Сейчас сигналы игнорируются, wait не может быть оборван * прерыванием с клавиатуры */ dowait(); /* ожидать окончания сына */ /* восстановить реакции на сигналы от клавиатуры */ signal(SIGINT, del); signal(SIGQUIT, quit); return pid; /* вернуть идентификатор сына */ } А. Богатырев, 1992-95 - 275 - Си в UNIX /* Запуск программы с поиском по переменной среды PATH */ int execvpe(progr, av, envp) char *progr, **av, **envp; { char *path, *cp; int try = 1; register eacces = 0; char fullpath[256]; /* полное имя программы */ firstfound = NULL; if((path = getenv("PATH")) == NULL ) path = ".:/bin:/usr/bin:/etc"; /* имя: короткое или путь уже задан ? */ cp = strchr(progr, '/') ? "" : path; do{ /* пробуем разные варианты */ cp = execat(cp, progr, fullpath); retry: fprintf(stderr, "пробуем \"%s\"\n", fullpath ); execve(fullpath, av, envp); /* если программа запустилась, то на этом месте данный * процесс заменился новой программой. Иначе - ошибка. */ switch( errno ){ /* какова причина неудачи ? */ case ENOEXEC: /* это командный файл */ callshell(fullpath, av, envp); return (-1); case ETXTBSY: /* файл записывается */ if( ++try > 5 ) return (-1); sleep(try); goto retry; case EACCES: /* не имеете права */ if(firstfound == NULL) firstfound = strdup(fullpath); eacces++; break; case ENOMEM: /* программа не лезет в память */ case E2BIG: return (-1); } }while( cp ); if( eacces ) errno = EACCES; return (-1); } /* Склейка очередной компоненты path и имени программы name */ static char *execat(path, name, buf) register char *path, *name; char *buf; /* где будет результат */ { register char *s = buf; while(*path && *path != LIM ) *s++ = *path++; /* имя каталога */ if( s != buf ) *s++ = '/'; while( *name ) *s++ = *name++; /* имя программы */ *s = '\0'; return ( *path ? ++path /* пропустив LIM */ : NULL ); } А. Богатырев, 1992-95 - 276 - Си в UNIX /* Запуск командного файла при помощи вызова интерпретатора */ void callshell(progr, av, envp) char *progr, **av, **envp; { register i; char *sh; char *newav[MAXARGS+2]; int fd; char first = 0; if((fd = open(progr, O_RDONLY)) < 0 ) sh = "/bin/sh"; else{ read(fd, &first, 1); close(fd); sh = (first == '#') ? "/bin/csh" : "/bin/sh"; } newav[0] = "Shellscript"; newav[1] = progr; for(i=1; av[i]; i++) newav[i+1] = av[i]; newav[i+1] = NULL; printf( "Вызываем %s\n", sh ); execve(sh, newav, envp); } /* Ожидать окончания всех процессов, выдать причины смерти. */ void dowait(){ int ws; int pid; while((pid = wait( &ws)) > 0 ){ if( WIFEXITED(ws)){ printf( "Процесс %d умер с кодом %d\n", pid, WEXITSTATUS(ws)); }else if( WIFSIGNALED(ws)){ printf( "Процесс %d убит сигналом %d\n", pid, WTERMSIG(ws)); if(WCOREDUMP(ws)) printf( "Образовался core\n" ); /* core - образ памяти процесса для отладчика adb */ }else if( WIFSTOPPED(ws)){ printf( "Процесс %d остановлен сигналом %d\n", pid, WSTOPSIG(ws)); } } } А. Богатырев, 1992-95 - 277 - Си в UNIX /* Расширение шаблонов имен. Это упрощенная версия, которая * расширяет имена только в текущем каталоге. */ void glob(dir, args, indx, str /* что расширять */, quote ) char *args[], *dir; int *indx; char *str; char quote; /* кавычки, в которые заключена строка str */ { static char globchars[] = "*?["; char *p; char **start = &args[ *indx ]; short nglobbed = 0; register struct dirent *dirbuf; DIR *fd; extern DIR *opendir(); /* Затычка для отмены глоббинга: */ if( *str == '\\' ){ str++; goto noGlob; } /* Обработка переменных $NAME */ if( *str == '$' && quote != '\'' ){ char *s = getenv(str+1); if( s ) str = s; } /* Анализ: требуется ли глоббинг */ if( quote ) goto noGlob; for( p=str; *p; p++ ) /* Есть ли символы шаблона? */ if( strchr(globchars, *p)) goto doGlobbing; noGlob: args[ (*indx)++ ] = strdup(str); return; doGlobbing: if((fd = opendir (dir)) == NULL){ fprintf(stderr, "Can't read %s\n", dir); return; } while ((dirbuf = readdir (fd)) != NULL ) { if (dirbuf->d_ino == 0) continue; if (strcmp (dirbuf->d_name, ".") == 0 || strcmp (dirbuf->d_name, "..") == 0) continue; if( match( dirbuf->d_name, str)){ args[ (*indx)++ ] = strdup(dirbuf->d_name); nglobbed++; } } closedir(fd); if( !nglobbed){ printf( "%s: no match\n", str); goto noGlob; }else{ /* отсортировать */ qsort(start, nglobbed, sizeof (char *), cmps); } } А. Богатырев, 1992-95 - 278 - Си в UNIX /* Разбор командной строки */ int parse(s) register char *s; { int i; register char *p; char tmp[80]; /* очередной аргумент */ char c; /* очистка старых аргументов */ for(i=0; arg[i]; i++) free(arg[i]), arg[i] = NULL; if( fin ) free(fin ), fin = NULL; if( fout ) free(fout), fout = NULL; rout = 0; /* разбор строки */ for( i=0 ;; ){ char quote = '\0'; /* пропуск пробелов - разделителей слов */ while((c = *s) && isspace(c)) s++; if( !c ) break; /* очередное слово */ p = tmp; if(*s == '\'' || *s == '"' ){ /* аргумент в кавычках */ quote = *s++; /* символ кавычки */ while((c = *s) != '\0' && c != quote){ if( c == '\\' ){ /* заэкранировано */ c = *++s; if( !c ) break; } *p++ = c; ++s; } if(c == '\0') fprintf(stderr, "Нет закрывающей кавычки %c\n", quote); else s++; /* проигнорировать кавычку на конце */ А. Богатырев, 1992-95 - 279 - Си в UNIX } else while((c = *s) && !isspace(c)){ if(c == '\\') /* заэкранировано */ if( !(c = *++s)) break /* while */; *p++ = c; s++; } *p = '\0'; /* Проверить, не есть ли это перенаправление * ввода/вывода. В отличие от sh и csh * здесь надо писать >ФАЙЛ <ФАЙЛ * >< вплотную к имени файла. */ p = tmp; /* очередное слово */ if( *p == '>'){ /* перенаправлен вывод */ p++; if( fout ) free(fout), rout = 0; /* уже было */ if( *p == '>' ){ rout |= APPEND; p++; } if( *p == '&' ){ rout |= ERRTOO; p++; } if( !*p ){ fprintf(stderr, "Нет имени для >\n"); fout = NULL; rout = 0; } else fout = strdup(p); } else if( *p == '<' ){ /* перенаправлен ввод */ p++; if( fin ) free(fin); /* уже было */ if( !*p ){ fprintf(stderr, "Нет имени для <\n"); fin = NULL; } else fin = strdup(p); } else /* добавить имена к аргументам */ glob( ".", arg, &i, p, quote ); } arg[i] = NULL; return i; } /* Установить имя пользователя */ void setuser(){ int uid = getuid(); /* номер пользователя, запустившего Шелл */ char *user = "mr. Nobody"; /* имя пользователя */ char *home = "/tmp"; /* его домашний каталог */ struct passwd *pp = getpwuid( uid ); if( pp != NULL ){ if(pp->pw_name && *pp->pw_name ) user = pp->pw_name; if( *pp->pw_dir ) home = pp->pw_dir; } setenv("USER", user); setenv("HOME", home); } void setcwd(){ /* Установить имя текущего каталога */ char cwd[512]; getwd(cwd); setenv( "CWD", cwd ); } А. Богатырев, 1992-95 - 280 - Си в UNIX void main(ac, av, ev) char *av[], *ev[]; { int argc; /* количество аргументов */ char *prompt; /* приглашение */ setuser(); setcwd(); signal(SIGINT, SIG_IGN); setbuf(stdout, NULL); /* отменить буферизацию */ for(;;){ prompt = getenv( "prompt" ); /* setenv prompt -->\ */ printf( prompt ? prompt : "@ ");/* приглашение */ if( gets(cmd) == NULL /* at EOF */ ) exit(0); argc = parse(cmd); if( !argc) continue; if( !strcmp(arg[0], "exit" )) exit(0); if( !strcmp(arg[0], "cd" )){ char *d = (argc==1) ? getenv("HOME"):arg[1]; if(chdir(d) < 0) printf( "Не могу войти в %s\n", d ); else setcwd(); continue; } if( !strcmp(arg[0], "echo" )){ register i; FILE *fp; if( fout ){ if((fp = fopen(fout, rout & APPEND ? "a":"w")) == NULL) continue; } else fp = stdout; for(i=1; i < argc; i++ ) fprintf( fp, "%s%s", arg[i], i == argc-1 ? "\n":" "); if( fp != stdout ) fclose(fp); continue; } if( !strcmp(arg[0], "setenv" )){ if( argc == 1 ) printenv(); else if( argc == 2 ) setenv( arg[1], "" ); else setenv( arg[1], arg[2]); continue; } cmdExec(arg[0], (char **) arg, environ, fin, fout, rout); } } А. Богатырев, 1992-95 - 281 - Си в UNIX /* -----------------------------------------------------------*/ /* Отсортировать и напечатать окружение */ void printenv(){ char *e[40]; register i = 0; char *p, **q = e; do{ p = e[i] = environ[i]; i++; } while( p ); #ifdef SORT qsort( e, --i /* сколько */, sizeof(char *), cmps); #endif while( *q ) printf( "%s\n", *q++ ); } /* Сравнение имени переменной окружения с name */ static char *envcmp(name, evstr) char *name, *evstr; { char *p; int code; if((p = strchr(evstr, '=')) == NULL ) return NULL; /* error ! */ *p = '\0'; /* временно */ code = strcmp(name, evstr); *p = '='; /* восстановили */ return code==0 ? p+1 : NULL; } /* Установить переменную окружения */ void setenv( name, value ) char *name, *value; { static malloced = 0; /* 1, если environ перемещен */ char *s, **p, **newenv; int len, change_at = (-1), i; /* Есть ли переменная name в environ-е ? */ for(p = environ; *p; p++ ) if(s = envcmp(name, *p)){ /* уже есть */ if((len = strlen(s)) >= strlen(value)){ /* достаточно места */ strcpy(s, value); return; } /* Если это новый environ ... */ if( malloced ){ free( *p ); *p = str3spl(name, "=", value); return; } /* иначе создаем копию environ-а */ change_at = p - environ; /* индекс */ break; } А. Богатырев, 1992-95 - 282 - Си в UNIX /* Создаем копию environ-а. Если change_at == (-1), то * резервируем новую ячейку для еще не определенной переменной */ for(p=environ, len=0; *p; p++, len++ ); /* вычислили количество переменных */ if( change_at < 0 ) len++; if((newenv = (char **) malloc( sizeof(char *) * (len+1))) == (char **) NULL) return; for(i=0; i < len+1; i++ ) newenv[i] = NULL; /* зачистка */ /* Копируем старый environ в новый */ if( !malloced ) /* исходный environ в стеке (дан системой) */ for(i=0; environ[i]; i++ ) newenv[i] = strdup(environ[i]); else for(i=0; environ[i]; i++ ) newenv[i] = environ[i]; /* Во втором случае строки уже были спасены, копируем ссылки */ /* Изменяем, если надо: */ if( change_at >= 0 ){ free( newenv[change_at] ); newenv[change_at] = str3spl(name, "=", value); } else { /* добавить в конец новую переменную */ newenv[len-1] = str3spl(name, "=", value); } /* подменить environ */ if( malloced ) free( environ ); environ = newenv; malloced++; qsort( environ, len, sizeof(char *), cmps); } /* Допишите команды: unsetenv имя_переменной - удаляет переменную среды; exit N - завершает интерпретатор с кодом возврата N (это целое число); */ А. Богатырев, 1992-95 - 283 - Си в UNIX

7. Текстовая обработка.


Под "текстовой обработкой" (в противовес "вычислительным задачам") здесь понима- ется огромный класс задач обработки информации нечислового характера, например редак- тирование текста, форматирование документов, поиск и сортировка, базы данных, лекси- ческий и синтаксический анализ, печать на принтере, преобразование формата таблиц, и.т.п. 7.1. Напишите программу, "угадывающую" слово из заранее заданного списка по первым нескольким буквам. Выдайте сообщение "неоднозначно", если есть несколько похожих слов. Усложните программу так, чтобы список слов считывался в программу при ее

list


7.2. Напишите программу, которая удваивает пробелы в тексте с одиночными пробелами. 7.3. Напишите программу, которая копирует ввод на вывод, заменяя каждую последова-

один


ее решения сходна с решением следующей задачи. 7.4. Напишите программу подсчета слов в файле. Слово определите как последователь- ность символов, не включающую символы пробела, табуляции или новой строки. "Канони- ческий" вариант решения, приведенный у Кернигана и Ритчи, таков:

ctype


stdio


const int YES=1, NO=0; main(){

inWord


words


c


c


inWord


inWord


}

words


} Обратите внимание на конструкцию const. Это объявление имен как констант. Эта конст- рукция близка к #define YES 1 но позволяет компилятору

типизированная


- создавать более экономный код; - запрещает изменять это значение. Рассмотрим пример main(){ /* cc 00.c -o 00 -lm */ double sqrt(double); const double sq12 = sqrt(12.0); #define SQRT2 sqrt(2.0) double x; x = sq12 * sq12 * SQRT2 * SQRT2; /* @1 */ sq12 = 3.4641; /* @2 */ printf("%g %g\n", sq12, x); } Использование #define превратит строку @1 в x = sq12 * sq12 * sqrt(2.0) * sqrt(2.0); то есть создаст код с двумя вызовами функции sqrt. Конструкция же const заносит вычисленное выражение в ячейку памяти и далее просто использует ее значение. При этом А. Богатырев, 1992-95 - 284 - Си в UNIX компилятор не позволяет впоследствии изменять это значение, поэтому строка @2 оши- бочна. Теперь предложим еще одну программу подсчета слов, где слово определяется макро- сом isWord, перечисляющим буквы допустимые в слове. Программа основана на переключа- тельной таблице функций (этот подход применим во многих случаях): #include <ctype.h> #include <stdio.h> int wordLength, inWord, words; /* = 0 */ char aWord[128], *wrd; void space (c){} void letter (c){ wordLength++; *wrd++ = c; } void begWord(c){ wordLength=0; inWord=1; wrd=aWord; words++; letter(c); } void endWord(c){ inWord=0; *wrd = '\0'; printf("Слово '%s' длины %d\n", aWord, wordLength); }

sw


/* !isWord */ { space, endWord }, /* isWord */ { begWord, letter } /* !inWord inWord */ }; #define isWord(c) (isalnum(c) || c=='-' || c=='_') main(){ register c; while((c = getchar()) != EOF)

sw


printf("%d слов\n", words); } 7.5. Напишите программу, выдающую гистограмму длин строк файла (т.е. таблицу: строк длины 0 столько-то, длины 1 - столько-то, и.т.п., причем таблицу можно изобразить графически).

in


out


7.7. Напишите программу, которая будет печатать слова из файла ввода, причем по одному на строку. 7.8. Напишите программу, печатающую гистограмму длин слов из файла ввода. 7.9. Напишите программу, читающую слова из файла и размещающую их в виде двунаправ- ленного списка слов, отсортированного по алфавиту. Указания: используйте динамическую память (malloc) и указатели; напишите функцию включения нового слова в список на нуж- ное место. В конце работы распечатайте список дважды: в прямом и в обратном порядке. Усложнение: не хранить в списке дубликаты; вместо этого вместе со словом хранить счетчик количества его вхождений в текст. 7.10. Напишите программу, которая печатает слова из своего файла ввода, расположен- ные в порядке убывания частоты их появления. Перед каждым словом напечатайте число частоты его появления. 7.11. Напишите программу, читающую файл построчно и печатающую слова в каждой строке в обратном порядке. А. Богатырев, 1992-95 - 285 - Си в UNIX 7.12. Напишите программу копирования ввода на вывод таким образом, чтобы из каждой группы последовательно одинаковых строк выводилась только одна строка. Это аналог программы uniq в системе UNIX. Ответ: #include <stdio.h> /* char *gets(); */ char buf1[4096], buf2[4096]; char *this = buf1, *prev = buf2; main(){ long nline =0L; char *tmp; while( gets(this)){ if(nline){ /* сравнить новую и предыдущую строки */ if( strcmp(this, prev)) /* различны ? */ puts(prev); } /* обмен буферов: */ tmp=prev; prev=this; this=tmp; nline++; /* номер строки */ }/* endwhile */ if( nline ) puts(prev); /* последняя строка всегда выдается */ } 7.13. Составьте программу, которая будет удалять в конце (и в начале) каждой строки файла пробелы и табуляции, а также удалять строки, целиком состоящие из пробелов и табуляций. 7.14. Для экономии места в файле, редакторы текстов при записи отредактированного файла сжимают подряд идущие пробелы в табуляцию. Часто это неудобно для программ обработки текстов (поскольку требует особой обработки табуляций - это ОДИН символ, который на экране и в тексте занимает НЕСКОЛЬКО позиций!), поэтому при чтении файла мы должны расширять табуляции в нужное количество пробелов, например так: /* заменять табуляции на пробелы */ void untab(s) register char *s; { char newstr[256]; /* новая строка */ char *src = s; int n; /* счетчик */ register dstx; /* координата x в новой строке */ for(dstx = 0; *s != '\0'; s++) if( *s == '\t'){ for(n = 8 - dstx % 8 ; n > 0 ; n--) newstr[dstx++] = ' '; }else newstr[dstx++] = *s; newstr[dstx] = '\0'; strcpy(src, newstr); /* строку на старое место */ } 7.15. Напишите обратную функцию, сжимающую подряд идущие пробелы в табуляции. А. Богатырев, 1992-95 - 286 - Си в UNIX void tabify(){ int chr; int icol, ocol; /* input/output columns */ for(icol = ocol = 0; ; ){ if((chr = getchar()) == EOF) break; switch(chr){ case ' ': icol++; break; case '\n': case '\r': ocol = icol = 0; putchar(chr); break; case '\t': icol += 8; icol &= ~07; /* icol -= icol % 8; */ break; default: while(((ocol + 8) & ~07) <= icol){ #ifdef NOTDEF if(ocol + 1 == icol) break; /* взять ' ' вместо '\t' */ #endif putchar('\t'); ocol += 8; ocol &= ~07; } while(ocol < icol){ putchar(' '); ocol++; } putchar(chr); icol++; ocol++; break; } } } 7.16. Составьте программу, укорачивающую строки исходного файла до заданной величины и помещающую результат в указанный файл. Учтите, что табуляция разворачивается в нес- колько пробелов! 7.17. Разработайте программу, укорачивающую строки входного файла до 60 символов. Однако теперь запрещается обрубать слова. А. Богатырев, 1992-95 - 287 - Си в UNIX 7.18. Разработайте программу, заполняющую промежутки между словами строки дополни- тельными пробелами таким образом, чтобы длина строки была равна 60 символам. 7.19. Напишите программу, переносящую слишком длинные строки. Слова разбивать нельзя (неумешающееся слово следует перенести целиком). Ширину строки считать равной 60. 7.20. Составьте программу, центрирующую строки файла относительно середины экрана, т.е. добавляющую в начало строки такое количество пробелов, чтобы середина строки печаталась в 40-ой позиции (считаем, что обычный экран имеет ширину 80 символов).

n


n


строки). #include <stdio.h> /* ... текст функции untab(); ... */ void process(char name[], int n, int spacesOnly){ char line[256]; int length, shift, nline = 0; char newname[128]; FILE *fpin, *fpout; if((fpin = fopen(name, "r")) == NULL){ fprintf(stderr, "Не могу читать %s\n", name); return; } sprintf(newname, "_%s", name); /* например */ if((fpout = fopen(newname, "w")) == NULL){ fprintf(stderr, "Не могу создать %s\n", newname); fclose(fpin); return; } while(fgets(line, sizeof line, fpin)){ ++nline; if((length = strlen(line)) && line[length-1] == '\n') line[--length] = '\0'; /* обрубить '\n' */ untab(line); /* развернуть табуляции */ for(shift=0; line[shift] != '\0' && shift < n ; ++shift) if(spacesOnly && line[shift] != ' ') break; if(*line && shift != n ) /* Предупреждение */ fprintf(stderr, "Начало строки #%d слишком коротко\n", nline); fprintf(fpout, "%s\n", line+shift); /* нельзя было fputs(line+n, fpout); * т.к. эта позиция может быть ЗА концом строки */ } fclose(fpin); fclose(fpout); } void main(int argc, char **argv){ if( argc != 3 ) exit(1); process(argv[2], atoi(argv[1]) /* 8 */, 1); exit(0); } 7.22. Напишите программу, разбивающую файл на два по вертикали: в первый файл попа- дает левая половина исходного файла, во второй - правая. Ширину колонки задавайте из аргументов main(). Если же аргумент не указан - 40 позиций. 7.23. Напишите программу сортировки строк в алфавитном порядке. Учтите, что функция strcmp() сравнивает строки в порядке кодировки, принятой на данной конкретной машине. Русские буквы, как правило, идут не в алфавитном порядке! Следует написать функцию А. Богатырев, 1992-95 - 288 - Си в UNIX для алфавитного сравнения отдельных символов и, пользуясь ею, переписать функцию strcmp(). 7.24. Отсортируйте массив строк по лексикографическому убыванию, игнорируя различия между строчными и прописными буквами. 7.25. Составьте программу дихотомического поиска в отсортированном массиве строк (методом деления пополам). /* Поиск в таблице методом половинного деления: dihotomia */ #include <stdio.h> struct elem { char *name; /* ключ поиска */ int value; } table[] = { /* имена строго по алфавиту */ { "andrew", 17 }, { "bill", 23 }, { "george", 55 }, { "jack", 54 }, { "jaw", 43 }, { "john", 33 }, { "mike", 99 }, { "paul", 21 }, { "sue", 66 }, /* SIZE - 2 */ { NULL, -1 }, /* SIZE - 1 */ /* NULL введен только для распечатки таблицы */ }; #define SIZE (sizeof(table) / sizeof(struct elem)) /* Дихотомический поиск по таблице */ struct elem *find(s, table, size) char *s; /* что найти ? */ struct elem table[]; /* в чем ? */ int size; /* среди первых size элементов */ { register top, bottom, middle; register code; top = 0; /* начало */ bottom = size - 1; /* конец: индекс строки "sue" */ while( top <= bottom ){ middle = (top + bottom) / 2; /* середина */ /* сравнить строки */ code = strcmp( s, table[middle].name ) ; if( code > 0 ){ top = middle + 1; }else if( code < 0 ){ bottom = middle - 1; }else return &table[ middle ]; } return (struct elem *) NULL; /* не нашел */ } А. Богатырев, 1992-95 - 289 - Си в UNIX /* распечатка таблицы */ void printtable(tbl) register struct elem *tbl; { for( ; tbl->name != NULL ; tbl++ ){ printf( "%-15s %d\n", tbl->name, tbl->value ); } } int main(){ char buf[80]; struct elem *ptr; printtable(table); for(;;){ printf( "-> " ); if( gets( buf ) == NULL) break; /* EOF */ if( ! strcmp( buf, "q" )) exit(0); /* quit: выход */ ptr = find( buf, table, SIZE-1 ); if( ptr ) printf( "%d\n", ptr->value ); else { printf( "--- Не найдено ---\n" ); printtable(table); } } return 0; } 7.26. Напишем функцию, которая преобразует строку так, что при ее печати буквы в ней

подчеркнуты


создается этим примером, является общепринятым в UNIX и распознается некоторыми прог- раммами: например, программа просмотра файлов less (more) выделяет такие буквы на экране специальными шрифтами или инверсией фона. #define LEN 9 /* потом напишите 256 */ char input[] = "(xxx+yyy)/123.75=?"; char output[LEN]; void main( void ){ int len=LEN, i; void bi_conv(); char c;

_


if(len > LEN){ printf("Увеличь LEN до %d\n", len); len = LEN; /* доступный максимум */ } for(i=0; i < len && (c = output[i]); ++i) putchar(c); putchar('\n'); } /* Заметьте, что include-файлы не обязательно * должны включаться в самом начале программы! */ #include <stdio.h> #include <ctype.h> #define PUT(c) { count++; \ if(put < *len){ *p++ = (c); ++put;}} #define GET() (*s ? *s++ : EOF)

_


А. Богатырев, 1992-95 - 290 - Си в UNIX /*IN*/ char *s, /*OUT*/ char *p, /*INOUT*/ int *len ){ int count, put, c; for(count=put=0; (c=GET()) != EOF; ){ /* жирный: C\bC */ /* подчеркнутый: _\bC */ if(isalpha(c)){ PUT('_'); PUT('\b'); } else if(isdigit(c)){ PUT( c ); PUT('\b'); } PUT(c); } PUT('\0'); /* закрыть строку */ *len = count; #undef PUT #undef GET }

файла


input


это определить #define PUT(c) if(c)putchar(c) #define GET() getchar() Напишите подобную функцию, удваивающую буквы в ссттррооккее. 7.27. Напишите программу, удаляющую из файла выделения. Для этого надо просто уда-

C


stdio


#define NOPUT (-1) /* не символ ASCII */ /* Названия шрифтов - в перечислимом типе */ typedef enum { NORMAL=1, ITALICS, BOLD, RED=BOLD } font;

ontty


f


textfont


f


/* Установить выделение на экране терминала */

f


ttyfont


ttyfont


printf("\033[0m"); /* set NORMAL font */

ttyfont


case NORMAL: /* уже сделано выше */ break; case BOLD: printf("\033[1m"); break; case ITALICS: /* use reverse video */ printf("\033[7m"); break; } }

c


c


c


setfont(NORMAL); /* Ожидать новой C\b посл-ти */ } void

c


/* Стандартный вывод - это терминал ? */

ontty


setfont(NORMAL);

c


А. Богатырев, 1992-95 - 291 - Си в UNIX

c


c


c


cprev


else /* наложение A\bB */ setfont(RED);

cprev


cprev


}

cprev


SetTtyFont(NORMAL); } 7.28. Напишите программу печати на принтере листинга Си-программ. Ключевые слова языка выделяйте двойной надпечаткой. Для выдачи на терминал напишите программу, под- черкивающую ключевые слова (подчеркивание - в следующей строке). Упрощение: выде- ляйте не ключевые слова, а большие буквы. Указание: для двойной печати используйте управляющий символ '\r' - возврат к началу той же строки; затем строка печатается повторно, при этом символы, которые не должны печататься жирно, следует заменить на пробелы (или на табуляцию, если этот символ сам есть '\t'). 7.29. Напишите программу, печатающую тексты Си-программ на принтере. Выделяйте клю-

строка


вом. Шрифты для EPSON-FX совместимых принтеров (например EP-2424) переключаются такими управляющими последовательностями (ESC означает символ '\033'): ВКЛЮЧЕНИЕ ВЫКЛЮЧЕНИЕ

bold


emphasized


italics


underline


повышенное качество печати ESC x 1 ESC x 0

near letter quality


superscript


subscript


сжатый шрифт (17 букв/дюйм) '\017' '\022'

condensed


двойная ширина букв ESC W 1 ESC W 0

expanded


пропорциональная печать ESC p 1 ESC p 0

proportional spacing


Можно включить одновременно несколько из перечисленных выше режимов. В каждой из следующих двух групп надо выбрать одно из трех:

pitch


pica (10 букв/дюйм) ESC P elite (12 букв/дюйм) ESC M micron (15 букв/дюйм) ESC g

font


draft


text


courier


Всюду выше 0 означает либо '0' либо '\0'; 1 означает либо '1' либо '\1'. Пример: printf( "This is \033Gboldface\033H word\n"); А. Богатырев, 1992-95 - 292 - Си в UNIX 7.30. Составьте программу вывода набора файлов на печать, начинающую каждый очеред- ной файл с новой страницы и печатающую перед каждым файлом заголовок и номер текущей

form feed


7.31. Напишите программу печати текста в две колонки. Используйте буфер для форми- рования листа: файл читается построчно (слишком длинные строки обрубать), сначала заполняется левая половина листа (буфера), затем правая. Когда лист полностью запол- нен или файл кончился - выдать лист построчно, расписать буфер пробелами (очистить лист) и повторить заполнение очередного листа. Указание: размеры листа должны переда- ваться как аргументы main(), для буфера используйте двумерный массив букв, память для него заказывайте динамически. Усложнение: не обрубайте, а переносите слишком длинные строки (строка может потребовать даже переноса с листа на лист).

pr


#include <stdio.h> #include <string.h> #define YES 1 #define NO 0 #define FORMFEED '\f' #define LINEFEED '\n' extern char *malloc(unsigned); extern char *strchr(char *, char); void untab(register char *s); void resetsheet( void ); void addsheet( char *s, FILE *fpout ); void flushsheet( FILE *fpout ); void printline( int y, char *s, char *attr, FILE *fpout ); void doattr( register char *abuf, register char *vbuf ); void printcopy( FILE *fpin, FILE *fpout ); void main(void); char *strdup (const char *s){ char *p = malloc(strlen(s)+1); strcpy(p,s); return p; /* return strcpy((char *) malloc(strlen(s)+1), s); */ } /* ... текст функции untab() ... */ int Sline; /* строка на листе */ int Shalf; /* половина листа */ int npage; /* номер страницы */ int startpage = 1; /* печать начиная с 1ой страницы */ int fline; /* номер строки файла */ int topline = 0; /* смещение до начала листа */ int halfwidth; /* ширина полулиста */ int twocolumns = YES; /* в две колонки ? */ int lshift, rshift = 1; /* поля слева и справа */ typedef unsigned short ushort; int COLS = 128; /* ширина листа (букв) */ int LINES = 66; /* длина листа (строк) */ ushort *mem; /* буфер листа */ #define AT(x,y) mem[ (x) + (y) * COLS ] /* Выделить буфер под лист и зачистить его */ void resetsheet ( void ){ register x; if( mem == NULL ){ /* выделить память */ А. Богатырев, 1992-95 - 293 - Си в UNIX if ((mem = (ushort *) malloc (COLS * LINES * sizeof(ushort))) == NULL ){ fprintf(stderr, "Out of memory.\n"); exit(1); } } /* очистить */ for( x= COLS * LINES - 1 ; x >= 0 ; x-- ) mem[x] = ' ' & 0xFF; halfwidth = (twocolumns ? COLS/2 : COLS ) - (lshift + rshift ); Sline = topline; Shalf = 0; }

_


if( twocolumns == YES && Shalf == 0 ){ \ /* закрыть данную половину листа */ \ Shalf = 1; /* перейти к новой половине */ \ Sline = topline; \ } else \ flushsheet(fpout) /* напечатать лист */ /* Записать строку в лист */ void addsheet ( char *s, FILE *fpout ) { register x, y; register i; char *rest = NULL; int wrap = NO; /* YES когда идет перенос слишком длинной строки */ /* в какое место поместить строку? */ x = (Shalf == 0 ? 0 : COLS/2) + lshift; y = Sline; i = 0; /* позиция в строке s */ while (*s) { if( *s == '\f' ){ /* вынужденный form feed */ rest = strdup( s+1 ); /* остаток строки */ NEXT_HALF; if( *rest ) addsheet(rest, fpout); free( rest ); return; } if( i >= halfwidth ){ /* перенести длинную строку */ wrap = YES; rest = strdup(s); break; } /* Обработка выделений текста */ if( s[1] == '\b' ){ while( s[1] == '\b' ){ AT(x, y) = (s[0] << 8) | (s[2] & 0xFF); /* overstrike */ s += 2; } s++; x++; i++; } else { AT (x, y) = *s++ & 0xFF; А. Богатырев, 1992-95 - 294 - Си в UNIX x++; i++; } } /* Увеличить строку/половину_листа */ Sline++; if (Sline == LINES) { /* полулист заполнен */ NEXT_HALF; } if( wrap && rest ) { /* дописать остаток строки */ addsheet(rest, fpout); free(rest); } } int again; /* нужна ли повторная надпечатка? */ /* Напечатать заполненный лист */ void flushsheet ( FILE *fpout ){ register x, y, xlast; char *s, *p; static char outbuf[BUFSIZ], attr[BUFSIZ]; /* attr - буфер под атрибуты выделений */ ushort c; if( npage >= startpage ) for (y = 0; y < LINES; y++) { /* обрезать концевые пробелы */ for (xlast = (-1), x = COLS - 1; x >= 0; x--) if (AT (x, y) != ' ') { xlast = x; break; } again = NO; s = outbuf; p = attr; for (x = 0; x <= xlast; x++){ c = AT(x, y); *s++ = c & 0xFF; /* имеет атрибуты ? */ c >>= 8; c &= 0xFF; *p++ = c ? c : ' '; if( c ) again = YES; } *s = '\0'; *p = '\0'; printline(y, outbuf, attr, fpout); } npage++; /* next page */ resetsheet(); /* зачистить новый лист */ } /* Напечатать одну строку листа */ void printline ( int y, char *s, char *attr, FILE *fpout ){ register x; if( again ){ doattr(attr, s); fprintf(fpout, "%s\r", attr ); } fprintf(fpout, "%s", s); /* перевод листа или строки */ fputc( y == LINES-1 ? FORMFEED : LINEFEED, fpout ); } /* Проверить - нет ли атрибутов выделений */ void doattr ( register char *abuf, register char *vbuf ){ for(; *abuf; abuf++, vbuf++ ) if( !strchr(" _-!|\177", *abuf)) *abuf = *vbuf; } А. Богатырев, 1992-95 - 295 - Си в UNIX /* Копирование файла на принтер */ void printcopy ( FILE *fpin, FILE *fpout ) { char inbuf[BUFSIZ]; npage = 1; /* первая страница имеет номер 1 */ fline = 0; /* текущая строка файла - 0 */ resetsheet(); /* зачистить буфер листа */ while( fgets(inbuf, sizeof inbuf - 1, fpin ) != NULL ){ register l = strlen( inbuf ); if( l && inbuf[l-1] == '\n' ) inbuf[--l] = '\0' ; fline++; untab ( inbuf ); addsheet( inbuf, fpout ); } if( !(Sline == topline && Shalf == 0)) /* если страница не была только что зачищена ... */ flushsheet(fpout); fprintf(stderr, "%d строк, %d листов.\n", fline, npage-1); }

файл


void main (){ printcopy(stdin, stdout); } Файл-принтер имеет в UNIX имя /dev/lp или подобное ему, а в MS DOS - имя prn. 7.32. Напишите программу, которая построчно считывает небольшой файл в память и печатает строки в обратном порядке. Указание: используйте динамическую память - функции malloc() и strcpy(). Объясним, почему желательно пользоваться динамической памятью. Пусть мы знаем, что строки имеют максимальную длину 80 символов и максимальное количество строк равно 50. Мы могли бы хранить текст в двумерном массиве:

text


занимающем 50*80 = 4000 байт памяти. Пусть теперь оказалось, что строки файла в действительности имеют длину по 10 букв. Мы используем 50 * (10 + 1) = 550 байт не используем 4000 - 50 * (10 + 1) = 3450 байт (+1 нужен для символа '\0' на конце строки). Пусть мы теперь пишем

text


и при чтении очередной строки сохраняем ее так:

buffer


buffer


text


/* +1 для хранения \0, который не учтен strlen-ом */

text


} то есть заказываем ровно столько памяти, сколько надо для хранения строки и ни байтом больше. Здесь мы (если sizeof(char *)==4) используем А. Богатырев, 1992-95 - 296 - Си в UNIX 50 * 4 + 50 * (10 + 1 + 4) = 950 байт массив указателей + заказанная malloc память (+4 - служебная информация malloc), но зато у нас не остается неиспользуемой памяти. Преимуществом выделения памяти в виде массива является то, что эта память выделится ГАРАНТИРОВАННО, тогда как malloc()-у может не хватить памяти (если мы ее прежде очень много захватывали и не освобождали free()). Если malloc не может выделить участок памяти требуемого размера, он возвращает значение NULL:

text


stderr


Распечатка строк:

i


text


text


}

ptr


ptr


использована. Данные в освобожденной памяти ПОРТЯТСЯ после free(). Ошибочно (и опасно) освобождать память, которая НЕ БЫЛА отведена malloc()-ом!

списка


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

указатели


руются. В двумерном же байтовом массиве нам пришлось бы для тех же перестановок копировать целые массивы байт - строки этой текстовой матрицы. 7.33. Напишите программу, печатающую строки файла в обратном порядке. Не считывать файл целиком в память! Следует использовать метод "обратного чтения" либо метод "быстрого доступа" к строкам файла, описанный в главе "Работа с файлами". ____________________ |- На самом деле все освобожденные куски включаются в список свободной памяти, и склеиваются вместе, если два освобожденных куска оказались рядом. При новых вызовах malloc сначала просматривается список свободной памяти - нет ли там области достаточ- ного размера? Этот алгоритм описан у Кернигана и Ритчи. А. Богатырев, 1992-95 - 297 - Си в UNIX /* Инвертирование порядка строк в файле. * Используется та идея, что файл-результат имеет тот же * размер, что и исходный */ #include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #define BUFS 4096 /* максимальная длина строки */ void main(int argc, char **argv ) { FILE *fp; struct stat st; long len; char buffer[ BUFS+1 ]; FILE *fpnew; /* инверсный файл */ int lgt; if( argc != 2 ){ printf("Error: must be filename\n"); exit(1); } if( (fp= fopen( argv[1], "r" )) == NULL ){ printf( "Can not open %s\n", argv[1] ); exit(2); } stat( argv[1], &st ); /* fstat(fileno(fp), &st); */ len = st.st_size; /* длина файла в байтах */ if( (fpnew = fopen( "inv.out", "w" ))== NULL ){ printf("Can not create file\n"); exit(3); } while( fgets( buffer, sizeof buffer, fp ) != NULL ){ lgt = strlen( buffer ); fseek(fpnew, len - lgt , 0); /* Помните, что смещение у lseek и fseek - * это число типа long, а не int. * Поэтому лучше всегда писать * lseek(fd, (long) off, whence); */ len -= lgt; fprintf( fpnew, "%s", buffer ); /* или лучше fputs(buffer, fpnew); */ } fclose( fp ); fclose( fpnew ); } 7.34. Напишите программу, которая читает файл, состоящий из "блоков" текста, разде- ленных пустыми строками. Размер "блока" ограничен. Программа готовит файл для печати на принтер так, чтобы ни один блок не разбивался на части: А. Богатырев, 1992-95 - 298 - Си в UNIX ----------- ----------- |###### A | |###### A | лист1 |#### A | превращать |#### A | |##### A | в |##### A | | | | | |###### B | | | ----------- ----------- |#### B | |###### B | лист2 | | |#### B | ... | | то есть если блок не умещается на остатке листа, он должен быть перенесен на следую- щий лист. Блоки следует разделять одной пустой строкой (но первая строка листа не должна быть пустой!). Если блок длиннее страницы - не переносите его. /* Решение задачи о переносе блоков текста, * если они не умещаются на остатке листа */ #include <stdio.h> #include <ctype.h> extern void *malloc(unsigned); extern int atoi(char *); FILE *fpin = stdin, *fpout = stdout; /* Спасти строку в динамически выделенной памяти */ char *strdup (const char *s) { char *ptr = (char *) malloc (strlen (s) + 1); if( ptr ) strcpy (ptr, s); return ptr; } int page_length = 66; /* длина страницы */ int current_line; /* текущая строка на странице (с нуля) */ int numbered = 0; /* нумеровать строки листа ? */ #define MAXLINES 256 /* макс. длина блока */ int stored = 0; /* запомнено строк */ char *lines[MAXLINES]; /* запомненные строки */ /* Запомнить строку блока в буфер строк */ void remember (char *s) { if (stored >= MAXLINES) { fprintf (stderr, "Слишком длинный блок.\n"); return; } else if((lines[stored++] = strdup (s)) == NULL ){ fprintf (stderr, "Мало памяти (Out of memory).\n"); exit(13); } } /* Переход на следующую страницу */ void newpage () { current_line = 0; putc('\f', fpout); } А. Богатырев, 1992-95 - 299 - Си в UNIX /* Перевод строки или листа */ void newline (void) { if (current_line == page_length - 1) newpage (); /* начать новый лист */ else { current_line++; if( numbered ) fprintf(fpout, "%02d\n", current_line); else putc ('\n', fpout); } } /* Переход на следующую страницу вставкой пустых строк */ void nextpage () { while (current_line != 0) newline (); } /* Выдать спасенный блок */ void throwout () { register i; for (i = 0; i < stored; i++) { if( numbered ) fprintf(fpout, "%02d %s", current_line, lines[i]); else fputs (lines[i], fpout); newline (); free (lines[i]); } stored = 0; } /* Выдать блок, перенося на следующий лист если надо */ void flush () { int rest_of_page = page_length - current_line; /* осталось пустых строк на странице */ if ((stored > page_length && rest_of_page < page_length / 4) || rest_of_page < stored) nextpage (); throwout (); if (current_line) /* не первая строка листа */ newline (); /* разделитель блоков */ } /* Обработать входной файл */ void process () { char buffer[512]; int l; while (fgets (buffer, sizeof buffer, fpin) != NULL) { if ((l = strlen (buffer)) && buffer[l - 1] == '\n') buffer[ --l] = '\0'; if (l) remember (buffer); /* а по пустой строке - выдать блок */ else if (stored) flush (); } if (stored) flush (); nextpage(); } А. Богатырев, 1992-95 - 300 - Си в UNIX void main (int argc, char *argv[]) { argc--; argv++; while (*argv) { if (**argv == '-') { char *key = *argv + 1, *arg; switch (*key) { case 'l': if (! key[1]) { if( argv[1] ){ arg = argv[1]; argv++; argc--; } else arg = ""; } else arg = key+1; if( isdigit(*arg) ){ page_length = atoi(arg); fprintf (stderr, "Длина страницы: %d строк\n", page_length); } else fprintf(stderr, "-l ЧИСЛО\n"); break; case 'n': numbered++; break; default: fprintf (stderr, "Неизвестный ключ %s\n", key); break; } } argv++; argc--; } process (); exit(0); } 7.35. Составьте программу вывода строк файла в инверсном отображении, причем порядок символов в строках также следует инвертировать. Например, abcdef ... oklmn 987654321 ..... превращать в ..... 123456789 nmlko ... fedcba Программа должна быть составлена двумя способами: при помощи обратного чтения файла и рекурсивным вызовом самой функции инвертирования. Указание: при обратном чтении надо читать файл большими кусками (блоками). 7.36. Напишите программу, читающую файл построчно и размещающую строки в отсортиро- ванное двоичное дерево. По концу файла - распечатайте это дерево. Указание: исполь- зуйте динамическую память и рекурсию. А. Богатырев, 1992-95 - 301 - Си в UNIX /* Двоичная сортировка строк при помощи дерева */ #include <stdio.h> char buf[240]; /* буфер ввода */ int lines; /* номер строки файла */ typedef struct node{ struct _data{ /* ДАННЫЕ */ char *key; /* ключ - строка */ int line; /* номер строки */ } data; /* СЛУЖЕБНАЯ ИНФОРМАЦИЯ */ struct node *l, /* левое поддерево */ *r; /* правое поддерево */ } Node; Node *root = NULL; /* корень дерева (ссылка на верхний узел) */ /* Отведение памяти и инициализация нового узла */ Node *newNode(s) char *s; /* строка */ { Node *tmp; extern char *malloc(); /* выделитель памяти */ tmp = (Node *) malloc(sizeof(Node)); if( tmp == NULL ){ fprintf( stderr, "Нет памяти.\n"); exit(1); } tmp -> l = tmp -> r = NULL; /* нет поддеревьев */ tmp -> data.line = lines; /* номер строки файла */ tmp -> data.key = malloc( strlen(s) + 1 ); /* +1 - под байт '\0' в конце строки */ strcpy(tmp -> data.key, s); /* копируем ключ в узел */ return tmp; } int i; /* Вынесено в статическую память, чтобы при каждом * рекурсивном вызове не создавалась новая auto-переменная, * а использовалась одна и та же статическая */ А. Богатырев, 1992-95 - 302 - Си в UNIX /* Рекурсивная печать дерева */ void printtree(root, tree, level, c) Node *root; /* корень дерева */ Node *tree; /* дерево */ int level; /* уровень */ char c; /* имя поддерева */ { if( root == NULL ){ printf("Дерево пусто.\n"); return; } if( tree == NULL ) return; /* если есть - распечатать левое поддерево */ printtree (root, tree -> l, level + 1, '/'); /* 'L' */ /* распечатать ключ узла */ for( i=0; i < level; i++ ) printf(" "); printf("%c%3d--\"%s\"\n", c, tree-> data.line, tree -> data.key); /* если есть - распечатать правое поддерево */ printtree(root, tree -> r, level + 1, '\\'); /* 'R' */ } void prTree(tree) Node *tree; { printtree(tree, tree, 0, '*'); } /* Добавить узел с ключом key в дерево tree */ void addnode(tree, key) Node **tree; /* в какое дерево добавлять: адрес переменной, * содержащей ссылку на корневой узел */ char *key; /* ключ узла */ { #define TREE (*tree) if( TREE == NULL ){ /* дерево пока пусто */ TREE = newNode( key ); return; } /* иначе есть хоть один узел */ if ( strcmp (key, TREE -> data.key) < 0 ) { /* добавить в левое поддерево */ if ( TREE -> l == NULL ){ /* нет левого дерева */ TREE -> l = newNode(key); return; } else addnode( & TREE ->l , key); } А. Богатырев, 1992-95 - 303 - Си в UNIX else{ /* добавить в правое дерево */ if ( TREE -> r == NULL ){ /* нет правого поддерева */ TREE -> r = newNode(key); return; } else addnode ( & TREE ->r, key) ; } } /* Процедура удаления из дерева по ключу. */ typedef struct node *NodePtr; static NodePtr delNode; /* удаляемая вершина */ void delete(key, tree) char *key; /* ключ удаляемого элемента */ NodePtr *tree; /* из какого дерева удалять */ { extern void doDelete(); if(*tree == NULL){ printf( "%s не найдено\n", key ); return; } /* поиск ключа */ else if(strcmp(key, (*tree)->data.key) < 0) delete( key, &(*tree)->l ); else if(strcmp(key, (*tree)->data.key) > 0) delete( key, &(*tree)->r ); else{ /* ключ найден */ delNode = *tree; /* указатель на удаляемый узел */ if(delNode->r == NULL) *tree = delNode->l; else if(delNode->l == NULL) *tree = delNode->r; else doDelete( & delNode->l ); free(delNode); } } static void doDelete(rt) NodePtr *rt; { if( (*rt)->r != NULL ) /* спуск по правой ветви */ doDelete( &(*rt)->r ); else{ /* перенос данных в другой узел */ delNode->data = (*rt)->data; delNode = *rt; /* для free() */ *rt = (*rt)->l; } } А. Богатырев, 1992-95 - 304 - Си в UNIX void main(){ extern char *gets(); char *s; while (gets(buf) != NULL){ /* пока не конец файла */ lines++; addnode( & root, buf ); } prTree(root); /* удалим строку */ freopen("/dev/tty", "r", stdin); do{ printf( "что удалить ? " ); if((s = gets(buf)) == NULL) break; delete(buf, &root); prTree( root ); } while( s && root ); printf("Bye-bye.\n"); exit(0); }

либо


затем распечатывает их. Для хранения введенных данных используйте объединение. #include <stdio.h> #include <ctype.h> #define INT 'i' #define STR 's' struct data { char tag; /* тэг, пометка. Код типа данных. */ union { int i; char *s; } value; } a[10]; int counter = 0; /* счетчик */ void main(){ char word[128]; int i; char *malloc(unsigned); /* Чтение: */ for(counter=0; counter < 10; counter++){ if( gets(word) == NULL ) break; if( isdigit((unsigned char) *word)){ a[counter].value.i = atoi(word); a[counter].tag = INT; } else { a[counter].value.s = malloc(strlen(word)+1); strcpy(a[counter].value.s, word); a[counter].tag = STR; } } /* Распечатка: */ for(i=0; i < counter; i++) switch(a[i].tag){ case INT: printf("число %d\n", a[i].value.i); break; case STR: printf("слово %s\n", a[i].value.s); free(a[i].value.s); break; } А. Богатырев, 1992-95 - 305 - Си в UNIX } 7.38. Рассмотрим задачу написания функции, которая обрабатывает переменное число аргументов, например функцию-генератор меню. В такую функцию надо подавать строки меню и адреса функций, вызываемых при выборе каждой из строк. Собственно проблема, которую мы тут обсуждаем - как передавать переменное число аргументов в подобные функции? Мы приведем три программы использующие три различных подхода. Предпочтение не отдано ни одному из них - каждый из них может оказаться эффективнее других в опре- деленных ситуациях. Думайте сами! 7.38.1. Массив /* Передача аргументов в функцию как МАССИВА. * Следует явно указать число аргументов в массиве. */ #include <stdio.h> /* printf(), NULL */ #include <string.h> /* strdup() */ #include <stdlib.h> /* malloc() */ #define A_INT 1 #define A_STR 2 #define A_NULL 0 typedef struct arg { int type; union jack { char *s; int d; } data; struct arg *next; } Arg; void doit(Arg args[], int n){ int i; for(i=0; i < n; i++) switch(args[i].type){ case A_INT: printf("%d", args[i].data.d); break; case A_STR: printf("%s", args[i].data.s); break; default: fprintf(stderr, "Unknown type!\n"); break; } } А. Богатырев, 1992-95 - 306 - Си в UNIX /* При инициализации union надо использовать тип * первого из перечисленных значений. */ Arg sample[] = { { A_INT, (char *) 123 }, { A_STR, (char *) " hello, " }, { A_INT, (char *) 456 }, { A_STR, (char *) " world\n" } }; int main(int ac, char *av[]){ doit(sample, sizeof sample / sizeof sample[0]); return 0; } 7.38.2. Список /* Передача аргументов в функцию как СПИСКА. * Достоинство: список можно модифицировать * во время выполнения программы: добавлять и * удалять элементы. Недостаток тот же: список надо * построить динамически во время выполнения, * заранее этого сделать нельзя. * Недостатком данной программы является также то, * что список не уничтожается после использования. * В C++ эта проблема решается при помощи использования * автоматически вызываемых деструкторов. */ #include <stdio.h> /* printf(), NULL */ #include <string.h> /* strdup() */ #include <stdlib.h> /* malloc() */ #define A_INT 1 #define A_STR 2 #define A_NULL 0 typedef struct arg { int type; union jack { char *s; int d; } data; struct arg *next; } Arg; А. Богатырев, 1992-95 - 307 - Си в UNIX void doit(Arg *arglist){ for( ; arglist; arglist=arglist->next) switch(arglist->type){ case A_INT: printf("%d", arglist->data.d); break; case A_STR: printf("%s", arglist->data.s); break; default: fprintf(stderr, "Unknown type!\n"); break; } } Arg *new_int(int n, Arg *next){ Arg *ptr = (Arg *) malloc(sizeof(Arg)); ptr->type = A_INT; ptr->data.d = n; ptr->next = next; return ptr; } Arg *new_str(char *s, Arg *next){ Arg *ptr = (Arg *) malloc(sizeof(Arg)); ptr->type = A_STR; ptr->data.s = strdup(s); ptr->next = next; return ptr; } int main(int ac, char *av[]){ doit( new_int(123, new_str(" hello, ", new_int(456, new_str(" world\n", NULL)))) ); return 0; } 7.38.3. Функция с переменным числом параметров /* Передача аргументов в функцию как СПИСКА АРГУМЕНТОВ * ФУНКЦИИ с признаком конца списка. */ #include <stdio.h> /* printf(), NULL */ #include <stdarg.h> /* va_... */ #define A_INT 1 #define A_STR 2 #define A_NULL 0 А. Богатырев, 1992-95 - 308 - Си в UNIX void doit(...){ /* переменное число аргументов */ va_list args; /* второй параметр - аргумент, предшествующий ... * Если такого нет - ставим запятую и пустое место! */ va_start(args, ); for(;;){ switch(va_arg(args, int)){ case A_INT: printf("%d", va_arg(args, int)); break; case A_STR: printf("%s", va_arg(args, char *)); break; case A_NULL: goto breakloop; default: fprintf(stderr, "Unknown type!\n"); break; } } breakloop: va_end(args); } int main(int ac, char *av[]){ doit( A_INT, 123, A_STR, " hello, ", A_INT, 456, A_STR, " world\n", A_NULL ); return 0; } 7.39. Напишите несколько функций для работы с упрощенной базой данных. Запись в базе данных содержит ключ - целое, и строку фиксированной длины:

data


b


b


}; Напишите: - добавление записи - уничтожение по ключу - поиск по ключу (и печать строки) - обновление по ключу. Файл организован как несортированный массив записей без дубликатов (т.е. ключи не могут повторяться). Поиск производить линейно. Используйте функции fread, fwrite,

n


fp


Перепишите эту программу, объявив ключ как строку, например А. Богатырев, 1992-95 - 309 - Си в UNIX

b


Если строка-ключ короче KEYLEN символов, она должна оканчиваться '\0', иначе -

d


в каталогах файловой системы). Усовершенствуйте алгоритм доступа, используя хеширо-

hash


отдельный файл. Этот файл ключей состоит из структур

record


b


b


b


}; то есть организован аналогично нашей первой базе данных. Сначала вы ищете нужный

b


b


b


список


номер


ных в списке (по значению), добавления данных в список в алфавитном порядке, (они просто приписываются к концу файла, но в нужных местах переставляются ссылки), распе- чатки списка в порядке ссылок, удалению элементов из списка (из самого файла они не удаляются!). Ссылка (номер) первой записи (головы списка) хранится в первых двух байтах файла, рассматриваемых как short. Введите оптимизацию: напишите функцию для сортировки файла (превращению переме- шанного списка в линейный) и вычеркивания из него удаленных записей. При этом файл будет перезаписан. Если файл отсортирован, то поиск в нем можно производить более эффективно, чем прослеживание цепочки ссылок: просто линейным просмотром. Третий байт файла используйте как признак: 1 - файл был отсортирован, 0 - после сортировки в него было что-то добавлено и линейный порядок нарушен.

строка


щенному регулярному выражению в стиле Шелл. Метасимволы шаблона: * - любое число любых символов (0 и более); ? - один любой символ. Усложнение:

буквы


буквы


h


Указание: для проверки "остатка" строки используйте рекурсивный вызов этой же функ- ции. Используя эту функцию, напишите программу, которая выделяет из файла СЛОВА,

Ии


строку надо сначала разбить на слова, а потом проверить каждое слово. А. Богатырев, 1992-95 - 310 - Си в UNIX #include <stdio.h> #include <string.h> #include <locale.h> #define U(c) ((c) & 0377) /* подавление расширения знака */ #define QUOT '\\' /* экранирующий символ */ #ifndef MATCH_ERR # define MATCH_ERR printf("Нет ]\n") #endif /* s - сопоставляемая строка * p - шаблон. Символ \ отменяет спецзначение метасимвола. */ int match (register char *s, register char *p) { register int scc; /* текущий символ строки */ int c, cc, lc; /* lc - предыдущий символ в [...] списке */ int ok, notflag; for (;;) { scc = U(*s++); /* очередной символ строки */ switch (c = U (*p++)) { /* очередной символ шаблона */ case QUOT: /* a*\*b */ c = U (*p++); if( c == 0 ) return(0); /* ошибка: pattern\ */ else goto def; case '[': /* любой символ из списка */ ok = notflag = 0; lc = 077777; /* достаточно большое число */ if(*p == '!'){ notflag=1; p++; } while (cc = U (*p++)) { if (cc == ']') { /* конец перечисления */ if (ok) break; /* сопоставилось */ return (0); /* не сопоставилось */ } if (cc == '-') { /* интервал символов */ if (notflag){ /* не из диапазона - OK */ if (!syinsy (lc, scc, U (*p++))) ok++; /* из диапазона - неудача */ else return (0); } else { /* символ из диапазона - OK */ if (syinsy (lc, scc, U (*p++))) ok++; } } else { if (cc == QUOT){ /* [\[\]] */ cc = U(*p++); if(!cc) return(0);/* ошибка */ } if (notflag){ if (scc && scc != (lc = cc)) ok++; /* не входит в список */ else return (0); } else { А. Богатырев, 1992-95 - 311 - Си в UNIX if (scc == (lc = cc)) /* входит в список */ ok++; } } } if (cc == 0){ /* конец строки */ MATCH_ERR; return (0); /* ошибка */ } continue; case '*': /* любое число любых символов */ if (!*p) return (1); for (s--; *s; s++) if (match (s, p)) return (1); return (0); case 0: return (scc == 0); default: def: if (c != scc) return (0); continue; case '?': /* один любой символ */ if (scc == 0) return (0); continue; } } } /* Проверить, что smy лежит между smax и smin */ int syinsy (unsigned smin, unsigned smy, unsigned smax) { char left [2]; char right [2]; char middle [2]; left [0] = smin; left [1] = '\0'; right [0] = smax; right [1] = '\0'; middle[0] = smy; middle[1] = '\0'; return (strcoll(left, middle) <= 0 && strcoll(middle, right) <= 0); } Обратите внимание на то, что в UNIX расширением шаблонов имен файлов, вроде *.c, занимается не операционная система (как в MS DOS), а программа-интерпретатор команд пользователя (shell: /bin/sh, /bin/csh, /bin/ksh). Это позволяет обрабатывать (в принципе) разные стили шаблонов имен.

usr


содержащий исходные тексты функций compile и step для регулярного выражения в стиле программ ed, lex, grep:

C


или заэкранированный спецсимвол \. \[ \* \$ \^ \\ означают сами себя; А. Богатырев, 1992-95 - 312 - Си в UNIX . означает один любой символ кроме \n;

abc


a


abc


минус в конце означает сам символ -;

abc


внутри [] скобка ] на первом месте означает сама себя;

a


кроме


a


крышка не на первом месте означает сама себя; [\*.] спецсимволы внутри [] не несут специального значения, а представляют сами себя;

C


.* любое число любых символов;

выражение


любое число (0 и более) повторений выражения, например [0-9]* означает число

влево


подвыражение;

выражение


n


выражение


n


выражение


n


выражение


конец


выражение


начало


\n символ перевода строки; \(.....\) сегмент. Сопоставившаяся с ним подстрока будет запомнена;

N


с 1). Напишите функцию matchReg, использующую этот стиль регулярных выражений. Сохраняйте шаблон, при вызове matchReg сравнивайте старый шаблон с новым. Перекомпиляцию следует производить только если шаблон изменился:

stdio


ctype


#define INIT register char *sp = instring; #define GETC() (*sp++) #define PEEKC() (*sp) #define UNGETC(c) (--sp) #define RETURN(ptr) return #define ERROR(code) \ {fprintf(stderr,"%s:ERR%d\n",instring,code);exit(177);}

regexp


#define EOL '\0' /* end of line */ #define ESIZE 512 int matchReg(char *str, char *pattern){ static char oldPattern[256]; static char compiledExpr[ESIZE]; if( strcmp(pattern, oldPattern)){ /* различны */ /* compile regular expression */ compile(pattern, compiledExpr, &compiledExpr[ESIZE], EOL); А. Богатырев, 1992-95 - 313 - Си в UNIX strcpy(oldPattern, pattern); /* запомнить */ } return step(str, compiledExpr); /* сопоставить */ } /* Пример вызова: reg '^int' 'int$' char | less */

reg


void main(int ac, char **av){ char inputline[BUFSIZ]; register i; while(gets(inputline)){ for(i=1; i < ac; i++) if(matchReg(inputline, av[i])){

loc1


/*printf("%s\n", inputline);*/ /* Напечатать строку, * выделяя сопоставившуюся часть жирно */ for(p=inputline; p != loc1; p++) putchar(*p); for( ; p != loc2; p++) if(isspace((unsigned char) *p)) putchar(*p); else printf("%c\b%c", *p, *p); for( ; *p; p++) putchar(*p); putchar('\n'); break; } } }

regexp


всех строках файла. Если строка не удовлетворяет регулярному выражению - она остается неизменной. Примеры вызова: $ regsub '\([0-9]\{1,\}\)' '(\1)'

file


Вторая команда должна заменять все вхождения f(a,b) на f(b,a). Выражение, обозначен-

N


N


\, его надо удваивать: \\. А. Богатырев, 1992-95 - 314 - Си в UNIX /* Контекстная замена */ #include <stdio.h> #include <ctype.h> #define INIT register char *sp = instring; #define GETC() (*sp++) #define PEEKC() (*sp) #define UNGETC(c) (--sp) #define RETURN(ptr) return #define ERROR(code) regerr(code) void regerr(); # include <regexp.h> #define EOL '\0' /* end of line */ #define ESIZE 512 short all = 0; /* ключ -a означает, что в строке надо заменить ВСЕ вхождения образца (global, all): * regsub -a int INT * "aa int bbb int cccc" -> "aa INT bbb INT cccc" * * step() находит САМУЮ ДЛИННУЮ подстроку, удовлетворяющую выражению, * поэтому regsub 'f(\(.*\),\(.*\))' 'f(\2,\1)' * заменит "aa f(1,2) bb f(3,4) cc" -> "aa f(4,1,2) bb f(3) cc' * |___________|_| |_|___________| */ char compiled[ESIZE], line[512]; А. Богатырев, 1992-95 - 315 - Си в UNIX void main(int ac, char *av[]){ register char *s, *p; register n; extern int nbra; extern char *braslist[], *braelist[], *loc1, *loc2; if( ac > 1 && !strcmp(av[1], "-a")){ ac--; av++; all++; } if(ac != 3){ fprintf(stderr, "Usage: %s [-a] pattern subst\n", av[0]); exit(1); } compile(av[1], compiled, compiled + sizeof compiled, EOL); while( gets(line) != NULL ){ if( !step(s = line, compiled)){ printf("%s\n", line); continue; } do{ /* Печатаем начало строки */ for( ; s != loc1; s++) putchar(*s); /* Делаем замену */ for(s=av[2]; *s; s++) if(*s == '\\'){ if(isdigit(s[1])){ /* сегмент */ int num = *++s - '1'; if(num < 0 || num >= nbra){ fprintf(stderr, "Bad block number %d\n", num+1); exit(2); } for(p=braslist[num]; p != braelist[num]; ++p) putchar(*p); } else if(s[1] == '&'){ ++s; /* вся сопоставленная строка */ for(p=loc1; p != loc2; ++p) putchar(*p); } else putchar(*++s); } else putchar(*s); } while(all && step(s = loc2, compiled)); /* Остаток строки */ for(s=loc2; *s; s++) putchar(*s); putchar('\n'); } /* endwhile */ } А. Богатырев, 1992-95 - 316 - Си в UNIX void regerr(int code){ char *msg; switch(code){ case 11: msg = "Range endpoint too large."; break; case 16: msg = "Bad number."; break; case 25: msg = "\\digit out of range."; break; case 36: msg = "Illegal or missing delimiter."; break; case 41: msg = "No remembered search string."; break; case 42: msg = "\\(~\\) imbalance."; break; case 43: msg = "Too many \\(."; break; case 44: msg = "More than 2 numbers given in \\{~\\\"}."; break; case 45: msg = "} expected after \\."; break; case 46: msg = "First number exceeds second in \\{~\\}."; break; case 49: msg = "[ ] imbalance."; break; case 50: msg = "Regular expression overflow."; break; default: msg = "Unknown error"; break; } fputs(msg, stderr); fputc('\n', stderr); exit(code); } void prfields(){ int i; for(i=0; i < nbra; i++) prfield(i); } void prfield(int n){ char *fbeg = braslist[n], *fend = braelist[n]; printf("\\%d='", n+1); for(; fbeg != fend; fbeg++) putchar(*fbeg); printf("'\n"); } 7.44. Составьте функцию поиска подстроки в строке. Используя ее, напишите программу поиска подстроки в текстовом файле. Программа должна выводить строки (либо номера строк) файла, в которых встретилась данная подстрока. Подстрока задается в качестве аргумента функции main(). /* Алгоритм быстрого поиска подстроки. * Дж. Мур, Р. Бойер, 1976 Texas * Смотри: Communications of the ACM 20, 10 (Oct., 1977), 762-772 * * Этот алгоритм выгоден при многократном поиске образца в * большом количестве строк, причем если они равной длины - * можно сэкономить еще и на операции strlen(str). * Алгоритм характерен тем, что при неудаче производит сдвиг не на * один, а сразу на несколько символов вправо. * В лучшем случае алгоритм делает slen/plen сравнений. */ char *pattern; /* образец (что искать) */ static int plen; /* длина образца */ static int d[256]; /* таблица сдвигов; в алфавите ASCII - * 256 букв. */ /* расстояние от конца образца до позиции i в нем */ #define DISTANCE(i) ((plen-1) - (i)) А. Богатырев, 1992-95 - 317 - Си в UNIX /* Поиск: * выдать индекс вхождения pattern в str, * либо -1, если не входит */ int indexBM( str ) char *str; /* в чем искать */ { int slen = strlen(str); /* длина строки */ register int pindx; /* индекс сравниваемой буквы в образце */ register int cmppos; /* индекс сравниваемой буквы в строке */ register int endpos; /* позиция в строке, к которой "приставляется" * последняя буква образца */ /* пока образец помещается в остаток строки */ for( endpos = plen-1; endpos < slen ; ){ /* Для отладки: pr(str, pattern, endpos - (plen-1), 0); /**/ /* просмотр образца от конца к началу */ for( cmppos = endpos, pindx = (plen - 1); pindx >= 0 ; cmppos--, pindx-- ) if( str[cmppos] != pattern[pindx] ){ /* Сдвиг, который ставит самый правый в образце * символ str[endpos] как раз под endpos-тую * позицию строки. Если же такой символ в образце не * содержится (или содержится только на конце), * то начало образца устанавливается в endpos+1 ую * позицию */ endpos += d[ str[endpos] & 0377 ]; break; /* & 0377 подавляет расширение знака. Еще */ } /* можно сделать все char -> unsigned char */ if( pindx < 0 ) return ( endpos - (plen-1)); /* Нашел: весь образец вложился */ } return( -1 ); /* Не найдено */ } А. Богатырев, 1992-95 - 318 - Си в UNIX /* Разметка таблицы сдвигов */ void compilePatternBM( ptrn ) char *ptrn; { register int c; pattern = ptrn; plen = strlen(ptrn); /* c - номер буквы алфавита */ for(c = 0; c < 256; c++) d[c] = plen; /* сдвиг на длину всего образца */ /* c - позиция в образце */ for(c = 0; c < plen - 1; c++) d[ pattern[c] & 0377 ] = DISTANCE(c); /* Сдвиг равен расстоянию от самого правого * (кроме последней буквы образца) * вхождения буквы в образец до конца образца. * Заметим, что если буква входит в образец несколько раз, * то цикл учитывает последнее (самое правое) вхождение. */ } /* Печать найденных строк */ void pr(s, p, n, nl) char *s, *p; { register i; printf("%4d\t%s\n", nl, s ); printf(" \t"); for(i = 0; i < n; i++ ) putchar( s[i] == '\t' ? '\t' : ' ' ); printf( "%s\n", p ); } /* Аналог программы fgrep */ #include <stdio.h> char str[ 1024 ]; /* буфер для прочитанной строки */ void main(ac, av) char **av; { int nline = 0; /* номер строки файла */ int ind; int retcode = 1; if(ac != 2){ fprintf(stderr, "Usage: %s 'pattern'\n", av[0] ); exit(33); } compilePatternBM( av[1] ); while( gets(str) != NULL ){ nline++; if((ind = indexBM(str)) >= 0 ){ retcode = 0; /* O'KAY */ pr(str, pattern, ind, nline); } } exit(retcode); } А. Богатырев, 1992-95 - 319 - Си в UNIX /* Пример работы алгоритма: peter piper picked a peck of pickled peppers. peck peter piper picked a peck of pickled peppers. peck peter piper picked a peck of pickled peppers. peck peter piper picked a peck of pickled peppers. peck peter piper picked a peck of pickled peppers. peck peter piper picked a peck of pickled peppers. peck peter piper picked a peck of pickled peppers. peck peter piper picked a peck of pickled peppers. peck */ 7.45. Напишите аналогичную программу, выдающую все строки, удовлетворяющие упрощен- ному регулярному выражению, задаваемому как аргумент для main(). Используйте функцию match, написанную нами ранее. Вы написали аналог программы grep из UNIX (но с другим типом регулярного выражения, нежели в оригинале).

s1


a


a


a


нимается буквально). 7.47. Напишите программу, читающую файл и заменяющую строки вида

1 и более пробелов и табуляций


на пары строк |.pp

текст


(здесь | обозначает левый край файла, a <> - метасимволы). Это - простейший препро- цессор, готовящий текст в формате nroff (это форматтер текстов в UNIX). Усложнения: - строки, начинающиеся с точки или с апострофа, заменять на

текст


- строки, начинающиеся с цифры, заменять на

число


текст


- символ \ заменять на последовательность \e. - удалять пробелы перед символами .,;:!?) и вставлять после них пробел (знак пре- пинания должен быть приклеен к концу слова, иначе он может быть перенесен на

начинающуюся


- склеивать перенесенные слова, поскольку nroff делает переносы сам:

начало


конец


А. Богатырев, 1992-95 - 320 - Си в UNIX Вызывайте этот препроцессор разметки текста так:

файлы


7.48. Составьте программу преобразования прописных букв из файла ввода в строчные, используя при этом функцию, в которой необходимо организовать анализ символа (дейст- вительно ли это буква). Строчные буквы выдавать без изменения. Указание: используйте

ctype


Ответ:

ctype


stdio


main(){

c


c


c


c


} либо ...

c


либо даже

c


В последнем случае под isupper и islower должны пониматься только буквы (увы, не во всех реализациях это так!). 7.49. Обратите внимание, что если мы выделяем класс символов при помощи сравнения, например:

ch


ch


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

ch


также делается при использовании char в качестве аргумента функции). При этом, если

ch


отрицательное


c


c


ch


< 0. Следует подавлять расширение знака:

ch


(0377 - маска из 8 бит, она же 0xFF, весь байт), либо объявить

ch


что означает, что при приведении к int знаковый бит не расширяется. 7.50. Рассмотрим еще один пример: А. Богатырев, 1992-95 - 321 - Си в UNIX main(){

ch


/* 0377 - код последнего символа алфавита ASCII */

ch


printf( "%03o %s\n",

ch


ch


} Какие неприятности ждут нас здесь?

ch


тельное


то есть у нас всегда печатается "no". Это мы можем исправить, написав

ch


ch


ch


ch


производятся по модулю 0400 (0377 - это максимальное значение, которое можно

ch


0 < 0377 и условие цикла верно! Цикл продолжается; т.е. происходит зациклива-

ch


не 0 (или unsigned int, лишь бы длины переменной хватало, чтобы вместить число больше 0377). 7.51. Составьте программу, преобразующую текст, состоящий только из строчных букв в текст, состоящий из прописных и строчных букв. Первая буква и буква после каждой точки - прописные, остальные - строчные. слово один. слово два. --> Слово один. Слово два. Эта программа может оказаться полезной для преобразования текста, набранного в одном регистре, в текст, содержащий буквы обоих регистров.

spell check


задан список слов; она проверяет - является ли введенное вами слово словом из списка. Если нет - пытается найти наиболее похожее слово из списка, причем если есть нес- колько похожих - выдает все варианты. Отлавливайте случаи:

ножинцы


ккаррандаш


бот


бинт


морда


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

сОВОк


Надо проверять каждую букву слова. Возможно вам будет удобно использовать рекурсию. Подсказка: для некоторых проверок вам может помочь функция match:

слово


входное


входное


*о*м* ?дом дом? *д*м* д?ом *д*о* до?м Приведем вариант решения этой задачи: А. Богатырев, 1992-95 - 322 - Си в UNIX #include <stdio.h> #include <ctype.h> #include <locale.h> typedef unsigned char uchar; #define ANYCHAR '*' /* символ, сопоставляющийся с одной любой буквой */ static uchar version[120]; /* буфер для генерации вариантов */ static uchar vv; /* буква, сопоставившаяся с ANYCHAR */ /* привести все буквы к одному регистру */ static uchar icase(uchar c){ return isupper(c) ? tolower(c) : c; } /* сравнение строк с игнорированием регистра */ static int eqi(uchar *s1, uchar *s2 ) { while( *s1 && *s2 ){ if( icase( *s1 ) != icase( *s2 )) break; s1++; s2++; } return ( ! *s1 && ! *s2 ) ? 1 : 0 ; /* OK : FAIL */ } /* сравнение строк с игнорированием ANYCHAR */ static strok(register uchar *word, register uchar *pat) { while( *word && *pat ){ if( *word == ANYCHAR){ /* Неважно, что есть *pat, но запомним */ vv= *pat; } else { if( icase(*pat) != icase(*word) ) break; } word++; pat++; } /* если слова кончились одновременно ... */ return ( !*word && !*pat) ? 1 : 0; /* OK : FAIL */ } А. Богатырев, 1992-95 - 323 - Си в UNIX /* ЛИШНЯЯ БУКВА */ static int superfluous( uchar *word /* слово для коррекции */ , uchar *s /* эталон */ ){ register int i,j,k; int reply; register len = strlen(word); for(i=0 ; i < len ; i++){ /* генерим слова , получающиеся удалением одной буквы */ k=0; for(j=0 ; j < i ; j++) version[k++]=word[j]; for(j=i+1 ; j < len ; j++) version[k++]=word[j]; version[k]='\0'; if( eqi( version, s )) return 1; /* OK */ } return 0; /* FAIL */ } /* ПОТЕРЯНА БУКВА */ static int hole; /* место, где вставлена ANYCHAR */ static int lost(uchar *word, uchar *s) { register int i,j,k; register len = strlen(word); hole= (-1); for(i=0 ; i < len+1 ; i++){ k=0; for(j=0 ; j < i ; j++) version[k++]=word[j]; version[k++]=ANYCHAR; for(j=i ; j < len ; j++) version[k++]=word[j]; version[k]='\0'; if( strok( version, s )){ hole=i; return 1; /* OK */ } } return 0; /* FAIL */ } А. Богатырев, 1992-95 - 324 - Си в UNIX /* ИЗМЕНИЛАСЬ ОДНА БУКВА (включает случай ошибки регистра) */ static int changed(uchar *word, uchar *s) { register int i,j,k; register len = strlen(word); hole = (-1); for(i=0 ; i < len ; i++){ k=0; for( j=0 ; j < i ; j++) version[k++]=word[j]; version[k++]=ANYCHAR; for( j=i+1 ; j < len ; j++) version[k++]=word[j]; version[k]='\0'; if( strok( version,s)){ hole=i; return 1; /* OK */ } } return 0; /* FAIL */ } /* УДВОЕННАЯ БУКВА */ static int duplicates(uchar *word, uchar *s, int leng) { register int i,j,k; uchar tmp[80]; if( eqi( word, s )) return 1; /* OK */ for(i=0;i < leng - 1; i++) /* ищем парные буквы */ if( word[i]==word[i+1]){ k=0; for(j=0 ; j < i ; j++) tmp[k++]=word[j]; for(j=i+1 ; j < leng ; j++) tmp[k++]=word[j]; tmp[k]='\0'; if( duplicates( tmp, s, leng-1) == 1) return 1; /* OK */ } return 0; /* FAIL */ } А. Богатырев, 1992-95 - 325 - Си в UNIX /* ПЕРЕСТАВЛЕНЫ СОСЕДНИЕ БУКВЫ */ static int swapped(uchar *word, uchar *s) { register int i,j,k; register len = strlen(word); for(i=0;i < len-1;i++){ k=0; for(j=0 ; j < i ; j++) version[k++]=word[j]; version[k++]=word[i+1]; version[k++]=word[i]; for(j=i+2 ; j < len ; j++) version[k++]=word[j]; version[k]='\0'; if( eqi( version, s)) return 1; /* OK */ } return 0; /* FAIL */ } uchar *words[] = { (uchar *) "bag", (uchar *) "bags", (uchar *) "cook", (uchar *) "cool", (uchar *) "bug", (uchar *) "buy", (uchar *) "cock", NULL }; #define Bcase(x, operators) case x: { operators; } break; char *cname[5] = { "переставлены буквы", "удвоены буквы ", "потеряна буква ", "ошибочная буква ", "лишняя буква " }; А. Богатырев, 1992-95 - 326 - Си в UNIX static int spellmatch( uchar *word /* IN слово для коррекции */ , uchar *words[] /* IN таблица допустимых слов */ , uchar **indx /* OUT ответ */ ){ int i, code, total = (-1); uchar **ptr; if(!*word) return -1; for(ptr = words; *ptr; ++ptr) if(eqi(word, *ptr)){ if(indx) *indx = *ptr; return 0; } /* Нет в таблице, нужен подбор похожих */ for(ptr = words; *ptr; ++ptr){ uchar *s = *ptr; int max = 5; for(i=0; i < max; i++){ switch( i ){ Bcase(0,code = swapped(word, s) ) Bcase(1,code = duplicates(word, s, strlen(word)) ) Bcase(2,code = lost(word, s) ) Bcase(3,code = changed(word, s) ) Bcase(4,code = superfluous(word, s) ) } if(code){ total++; printf("?\t%s\t%s\n", cname[i], s); if(indx) *indx = s; /* В случае с дубликатами не рассматривать * на наличие лишних букв */ if(i==1) max = 4; } } } return total; } А. Богатырев, 1992-95 - 327 - Си в UNIX void main(){ uchar inpbuf[BUFSIZ]; int n; uchar *reply, **ptr; setlocale(LC_ALL, ""); for(ptr = words; *ptr; ptr++) printf("#\t%s\n", *ptr); do{ printf("> "); fflush(stdout); if(gets((char *)inpbuf) == NULL) break; switch(spellmatch(inpbuf, words, &reply)){ case -1: printf("Нет такого слова\n"); break; case 0: printf("Слово '%s'\n", reply); break; default: printf("Неоднозначно\n"); } } while(1); } 7.53. Пока я сам писал эту программу, я сделал две ошибки, которые должны быть весьма характерны для новичков. Про них надо бы говорить раньше, в главе про строки и в самой первой главе, но тут они пришлись как раз к месту. Вопрос: что печатает сле- дующая программа? #include <stdio.h> char *strings[] = { "Первая строка" "Вторая строка" "Третяя строка", "Четвертая строка", NULL }; void main(){ char **p; for(p=strings;*p;++p) printf("%s\n", *p); } А печатает она вот что: Первая строкаВторая строкаТретяя строка Четвертая строка

склеивает


"начало строки" "и ее конец" если они разделены пробелами в смысле isspace, в том числе и пустыми строками. А в

strings


Вторая ошибка касается того, что можно забыть поставить слово break в операторе switch, и долго после этого гадать о непредсказуемом поведении любого поступающего на вход значения. Дело просто: пробегаются все случаи, управление проваливается из case в следующий case, и так много раз подряд! Это и есть причина того, что в предыдущем А. Богатырев, 1992-95 - 328 - Си в UNIX примере все case оформлены нетривиальным макросом Bcase. 7.54. Составьте программу кодировки и раскодировки файлов по заданному ключу (строке символов). 7.55. Составьте программу, которая запрашивает анкетные данные типа фамилии, имени, отчества, даты рождения и формирует файл. Программа должна отлавливать ошибки ввода несимвольной и нецифровой информации, выхода составляющих даты рождения за допустимые границы с выдачей сообщений об ошибках. Программа должна давать возможность корректи- ровать вводимые данные. Все данные об одном человеке записываются в одну строку файла через пробел. Вот возможный пример части диалога (ответы пользователя выделены жирно): Введите месяц рождения [1-12]: 14 <ENTER> *** Неправильный номер месяца (14). Введите месяц рождения [1-12]: март <ENTER> *** Номер месяца содержит букву 'м'. Введите месяц рождения [1-12]: <ENTER> Вы хотите закончить ввод ? n Введите месяц рождения [1-12]: 11 <ENTER> Ноябрь Введите дату рождения [1-30]: _ В таких программах обычно ответ пользователя вводится как строка: printf("Введите месяц рождения [1-12]: ");

input


затем (если надо) отбрасываются лишние пробелы в начале и в конце строки, затем вве-

input


цифр?), затем строка преобразуется к нужному типу (например, при помощи функции atoi переводится в целое) и проверяется допустимость полученного значения, и.т.д. Вводимую информацию сначала заносите в структуру; затем записывайте содержимое полей структуры в файл в текстовом виде (используйте функцию fprintf, а не fwrite). 7.56. Составьте программу, осуществляющую выборку информации из файла, сформирован- ного в предыдущей задаче, и ее распечатку в табличном виде. Выборка должна осуществ- ляться по значению любого заданного поля (т.е. вы выбираете поле, задаете его значе- ние и получаете те строки, в которых значение указанного поля совпадает с заказанным вами значением). Усложнение: используйте функцию сравнения строки с регулярным выра-

шаблону


чение заданного поля удовлетворяет шаблону). Для чтения файла используйте fscanf, либо fgets и затем sscanf. Второй способ лучше тем, что позволяет проверить по шаб-

любого


12


7.57. Составьте вариант программы подсчета служебных слов языка Си, не учитывающий появление этих слов, заключенных в кавычки. 7.58. Составьте программу удаления из программы на языке Си всех комментариев. Обра- тите внимание на особые случаи со строками в кавычках и символьными константами; так строка char s[] = "/*"; не является началом комментария! Комментарии записывайте в отдельный файл. 7.59. Составьте программу выдачи перекрестных ссылок, т.е. программу, которая выво- дит список всех идентификаторов переменных, используемых в программе, и для каждого из идентификаторов выводит список номеров строк, в которые он входит. А. Богатырев, 1992-95 - 329 - Си в UNIX 7.60. Разработайте простую версию препроцессора для обработки операторов #include. В качестве прототипа такой программы можно рассматривать такую (она понимает дирек-

имяфайла


#include <stdio.h> #include <string.h> #include <errno.h> char KEYWORD[] = "#include "; /* with a trailing space char */ void process(char *name, char *from){ FILE *fp; char buf[4096]; if((fp = fopen(name, "r")) == NULL){ fprintf(stderr, "%s: cannot read \"%s\", %s\n", from, name, strerror(errno)); return; } while(fgets(buf, sizeof buf, fp) != NULL){ if(!strncmp(buf, KEYWORD, sizeof KEYWORD - 1)){ char *s; if((s = strchr(buf, '\n')) != NULL) *s = '\0'; fprintf(stderr, "%s: including %s\n", name, s = buf + sizeof KEYWORD - 1); process(s, name); } else fputs(buf, stdout); } fclose(fp); } int main(int ac, char *av[]){ int i; for(i=1; i < ac; i++) process(av[i], "MAIN"); return 0; } 7.61. Разработайте простую версию препроцессора для обработки операторов #define. Сначала реализуйте макросы без аргументов. Напишите обработчик макросов вида

имя


тело макроса - можно несколько строк #endm 7.62. Напишите программу, обрабатывающую определения #ifdef, #else, #endif. Учтите, что эти директивы могут быть вложенными: #ifdef A # ifdef B ... /* defined(A) && defined(B) */ # endif /*B*/ ... /* defined(A) */ #else /*not A*/ ... /* !defined(A) */ # ifdef C ... /* !defined(A) && defined(C) */ # endif /*C*/ А. Богатырев, 1992-95 - 330 - Си в UNIX #endif /*A*/ 7.63. Составьте программу моделирования простейшего калькулятора, который считывает в каждой строчке по одному числу (возможно со знаком) или по одной операции сложения или умножения, осуществляет операцию и выдает результат. 7.64. Составьте программу-калькулятор, которая производит операции сложения, вычита- ния, умножения, деления; операнды и знак арифметической операции являются строковыми аргументами функции main. 7.65. Составьте программу, вычисляющую значение командной строки, представляющей собой обратную польскую запись арифметического выражения. Например, 20 10 5 + * вычисляется как 20 * (10 + 5) . 7.66. Составьте функции работы со стеком: - добавление в стек - удаление вершины стека (с возвратом удаленного значения) Используйте два варианта: стек-массив и стек-список. 7.67. Составьте программу, которая использует функции работы со стеком для перевода арифметических выражений языка Си в обратную польскую запись. /*#!/bin/cc $* -lm * Калькулятор. Иллюстрация алгоритма превращения выражений * в польскую запись по методу приоритетов. */ #include <stdio.h> #include <stdlib.h> /* extern double atof(); */ #include <math.h> /* extern double sin(), ... */ #include <ctype.h> /* isdigit(), isalpha(), ... */ #include <setjmp.h> /* jmp_buf */ jmp_buf AGAIN; /* контрольная точка */ err(n){ longjmp(AGAIN,n);} /* прыгнуть в контрольную точку */ А. Богатырев, 1992-95 - 331 - Си в UNIX /* ВЫЧИСЛИТЕЛЬ --------------------------------------- */ /* Если вместо помещения операндов в стек stk[] просто * печатать операнды, а вместо выполнения операций над * стеком просто печатать операции, мы получим "польскую" * запись выражения: * a+b -> a b + * (a+b)*c -> a b + c * * a + b*c -> a b c * + */ /* стек вычислений */ #define MAXDEPTH 20 /* глубина стеков */ int sp; /* указатель стека (stack pointer) */ double stk[MAXDEPTH]; double dpush(d) double d; /* занести число в стек */ { if( sp == MAXDEPTH ){ printf("Стек операндов полон\n");err(1);} else return( stk[sp++] = d ); } double dpop(){ /* взять вершину стека */ if( !sp ){ printf("Стек операндов пуст\n"); err(2); } else return stk[--sp]; } static double r,p; /* вспомогательные регистры */ void add() { dpush( dpop() + dpop()); } void mult() { dpush( dpop() * dpop()); } void sub() { r = dpop(); dpush( dpop() - r); } void divide() { r = dpop(); if(r == 0.0){ printf("Деление на 0\n"); err(3); } dpush( dpop() / r ); } void pwr() { r = dpop(); dpush( pow( dpop(), r )); } void dup() { dpush( dpush( dpop())); } void xchg(){ r = dpop(); p = dpop(); dpush(r); dpush(p); } void neg() { dpush( - dpop()); } void dsin(){ dpush( sin( dpop())); } void dcos(){ dpush( cos( dpop())); } void dexp(){ dpush( exp( dpop())); } void dlog(){ dpush( log( dpop())); } void dsqrt(){ dpush( sqrt( dpop())); } void dsqr(){ dup(); mult(); } /* M_PI и M_E определены в <math.h> */ void pi() { dpush( M_PI /* число пи */ ); } void e() { dpush( M_E /* число e */ ); } void prn() { printf("%g\n", dpush( dpop())); } void printstk(){ if( !sp ){ printf("Стек операндов пуст\n"); err(4);} while(sp) printf("%g ", dpop()); putchar('\n'); } А. Богатырев, 1992-95 - 332 - Си в UNIX /* КОМПИЛЯТОР ---------------------------------------- */ /* номера лексем */ #define END (-3) /* = */ #define NUMBER (-2) /* число */ #define BOTTOM 0 /* псевдолексема "дно стека" */ #define OPENBRACKET 1 /* ( */ #define FUNC 2 /* f( */ #define CLOSEBRACKET 3 /* ) */ #define COMMA 4 /* , */ #define PLUS 5 /* + */ #define MINUS 6 /* - */ #define MULT 7 /* * */ #define DIV 8 /* / */ #define POWER 9 /* ** */ /* Приоритеты */ #define NOTDEF 333 /* не определен */ #define INFINITY 3000 /* бесконечность */ /* Стек транслятора */ typedef struct _opstack { int cop; /* код операции */ void (*f)(); /* "отложенное" действие */ } opstack; int osp; /* operations stack pointer */ opstack ost[MAXDEPTH]; void push(n, func) void (*func)(); { if(osp == MAXDEPTH){ printf("Стек операций полон\n");err(5);} ost[osp].cop = n; ost[osp++].f = func; } int pop(){ if( !osp ){ printf("Стек операций пуст\n"); err(6); } return ost[--osp].cop; } int top(){ if( !osp ){ printf("Стек операций пуст\n"); err(7); } return ost[osp-1].cop; } void (*topf())(){ return ost[osp-1].f; } #define drop() (void)pop() void nop(){ printf( "???\n" ); } /* no operation */

_


А. Богатырев, 1992-95 - 333 - Си в UNIX /* Таблица приоритетов */ struct synt{ int inp_prt; /* входной приоритет */ int stk_prt; /* стековый приоритет */ void (*op)(); /* действие над стеком вычислений */ } ops[] = { /* BOTTOM */ {NOTDEF, -1, nop }, /* OPENBRACKET */ {INFINITY, 0, obr_err}, /* FUNC */ {INFINITY, 0, obr_err}, /* CLOSEBRACKET */ {1, NOTDEF, nop }, /* NOPUSH */ /* COMMA */ {1, NOTDEF, nop }, /* NOPUSH */ /* PLUS */ {1, 1, add }, /* MINUS */ {1, 1, sub }, /* MULT */ {2, 2, mult }, /* DIV */ {2, 2, divide }, /* POWER */ {3, 3, pwr } }; #define stkprt(i) ops[i].stk_prt #define inpprt(i) ops[i].inp_prt #define perform(i) (*ops[i].op)() /* значения, заполняемые лексическим анализатором */ double value; void (*fvalue)(); int tprev; /* предыдущая лексема */ А. Богатырев, 1992-95 - 334 - Си в UNIX /* Транслятор в польскую запись + интерпретатор */ void reset(){ sp = osp = 0; push(BOTTOM, NULL); tprev = END;} void calc(){ int t; do{ if( setjmp(AGAIN)) printf( "Стеки после ошибки сброшены\n" ); reset(); while((t = token()) != EOF && t != END){ if(t == NUMBER){ if(tprev == NUMBER){ printf("%g:Два числа подряд\n",value); err(9); } /* любое число просто заносится в стек */ tprev = t; dpush(value); continue; } /* иначе - оператор */ tprev = t; /* Выталкивание и выполнение операций со стека */ while(inpprt(t) <= stkprt( top()) ) perform( pop()); /* Сокращение или подмена скобок */ if(t == CLOSEBRACKET){ if( top() == OPENBRACKET || top() == FUNC ){ void (*ff)() = topf(); drop(); /* схлопнуть скобки */ /* обработка функции */ if(ff) (*ff)(); }else{ printf( "Не хватает (\n"); err(10); } } /* Занесение операций в стек (кроме NOPUSH-операций) */ if(t != CLOSEBRACKET && t != COMMA) push(t, t == FUNC ? fvalue : NULL ); } if( t != EOF ){ /* Довыполнить оставшиеся операции */ while( top() != BOTTOM ) perform( pop()); printstk(); /* печать стека вычислений (ответ) */ } } while (t != EOF); } /* Лексический анализатор ---------------------------- */ extern void getn(), getid(), getbrack(); int token(){ /* прочесть лексему */ int c; while((c = getchar())!= EOF && (isspace(c) || c == '\n')); if(c == EOF) return EOF; ungetc(c, stdin); if(isdigit(c)){ getn(); return NUMBER; } if(isalpha(c)){ getid(); getbrack(); return FUNC; } return getop(); } А. Богатырев, 1992-95 - 335 - Си в UNIX /* Прочесть число (с точкой) */ void getn(){ int c, i; char s[80]; s[0] = getchar(); for(i=1; isdigit(c = getchar()); i++ ) s[i] = c; if(c == '.'){ /* дробная часть */ s[i] = c; for(i++; isdigit(c = getchar()); i++) s[i] = c; } s[i] = '\0'; ungetc(c, stdin); value = atof(s); } /* Прочесть операцию */ int getop(){ int c; switch( c = getchar()){ case EOF: return EOF; case '=': return END; case '+': return PLUS; case '-': return MINUS; case '/': return DIV; case '*': c = getchar(); if(c == '*') return POWER; else{ ungetc(c, stdin); return MULT; } case '(': return OPENBRACKET; case ')': return CLOSEBRACKET; case ',': return COMMA; default: printf( "Ошибочная операция %c\n", c); return token(); } } struct funcs{ /* Таблица имен функций */ char *fname; void (*fcall)(); } tbl[] = { { "sin", dsin }, { "cos", dcos }, { "exp", dexp }, { "sqrt", dsqrt }, { "sqr", dsqr }, { "pi", pi }, { "sum", add }, { "ln", dlog }, { "e", e }, { NULL, NULL } }; char *lastf; /* имя найденной функции */ /* Прочесть имя функции */ void getid(){ struct funcs *ptr = tbl; char name[80]; int c, i; *name = getchar(); for(i=1; isalpha(c = getchar()); i++) name[i] = c; name[i] = '\0'; ungetc(c, stdin); /* поиск в таблице */ for( ; ptr->fname; ptr++ ) if( !strcmp(ptr->fname, name)){ fvalue = ptr->fcall; lastf = ptr->fname; return; } printf( "Функция \"%s\" неизвестна\n", name ); err(11); } А. Богатырев, 1992-95 - 336 - Си в UNIX /* прочесть открывающую скобку после имени функции */ void getbrack(){ int c; while((c = getchar()) != EOF && c != '(' ) if( !isspace(c) && c != '\n' ){ printf("Между именем функции %s и ( символ %c\n", lastf, c); ungetc(c, stdin); err(12); } } void main(){ calc();} /* Примеры: ( sin( pi() / 4 + 0.1 ) + sum(2, 4 + 1)) * (5 - 4/2) = ответ: 23.3225 (14 + 2 ** 3 * 7 + 2 * cos(0)) / ( 7 - 4 ) = ответ: 24 */ 7.68. Приведем еще один арифметический вычислитель, использующий классический рекур- сивный подход: /* Калькулятор на основе рекурсивного грамматического разбора. * По мотивам арифметической части программы csh (СиШелл). * csh написан Биллом Джоем (Bill Joy). : var1 = (x = 1+3) * (y=x + x++) 36 : s = s + 1 ошибка : y 9 : s = (1 + 1 << 2) == 1 + (1<<2) 0 : var1 + 3 + -77 -38 : a1 = 3; a2 = (a4=a3 = 2; a1++)+a4+2 8 : sum(a=2;b=3, a++, a*3-b) 12 */ #include <stdio.h> #include <ctype.h> #include <setjmp.h> typedef enum { NUM, ID, OP, OPEN, CLOSE, UNKNOWN, COMMA, SMC } TokenType; char *toknames[] = { "number", "identifier", "operation", "open_paren", "close_paren", "unknown", "comma", "semicolon" }; typedef struct _Token { char *token; /* лексема (слово) */ struct _Token *next; /* ссылка на следующую */ TokenType type; /* тип лексемы */ } Token; extern void *malloc(unsigned); extern char *strchr(char *, char); char *strdup(const char *s){ char *p = (char *)malloc(strlen(s)+1); if(p) strcpy(p,s); return p; } А. Богатырев, 1992-95 - 337 - Си в UNIX /* Лексический разбор ------------------------------------------*/ /* Очистить цепочку токенов */ void freelex(Token **p){ Token *thisTok = *p; while( thisTok ){ Token *nextTok = thisTok->next; free((char *) thisTok->token); free((char *) thisTok); thisTok = nextTok; } *p = NULL; } /* Добавить токен в хвост списка */ void addtoken(Token **hd, Token **tl, char s[], TokenType t){ Token *newTok = (Token *) malloc(sizeof(Token)); newTok->next = (Token *) NULL; newTok->token = strdup(s); newTok->type = t; if(*hd == NULL) *hd = *tl = newTok; else{ (*tl)->next = newTok; *tl = newTok; } } /* Разобрать строку в список лексем (токенов) */ #define opsym(c) ((c) && strchr("+-=!~^|&*/%<>", (c)))

_


_


void lex(Token **hd, Token **tl, register char *s){ char *p, csave; TokenType type; while(*s){ while( isspace(*s)) ++s; p = s; if( !*s ) break; if(isdigit (*s)){ type = NUM; while(isdigit (*s))s++; } else if(is_alpha(*s)){ type = ID; while(is_alnum(*s))s++; } else if(*s == '('){ type = OPEN; s++; } else if(*s == ')'){ type = CLOSE; s++; } else if(*s == ','){ type = COMMA; s++; } else if(*s == ';'){ type = SMC; s++; } else if(opsym(*s)){ type = OP; while(opsym(*s)) s++; } else { type = UNKNOWN; s++; } csave = *s; *s = '\0'; addtoken(hd, tl, p, type); *s = csave; } } /* Распечатка списка лексем */ void printlex(char *msg, Token *t){ if(msg && *msg) printf("%s: ", msg); for(; t != NULL; t = t->next) printf("%s`%s' ", toknames[(int)t->type], t->token); putchar('\n'); } А. Богатырев, 1992-95 - 338 - Си в UNIX /* Система переменных ----------------------------------------- */ #define NEXT(v) *v = (*v)->next #define TOKEN(v) (*v)->token #define TYPE(v) (*v)->type #define eq(str1, str2) (!strcmp(str1, str2)) jmp_buf breakpoint; #define ERR(msg,val) { printf("%s\n", msg);longjmp(breakpoint, val+1);} typedef struct { char *name; /* Имя переменной */ int value; /* Значение переменной */ int isset; /* Получила ли значение ? */ } Var; #define MAXV 40 Var vars[MAXV]; /* Получить значение переменной */ int getVar(char *name){ Var *ptr; for(ptr=vars; ptr->name; ptr++) if(eq(name, ptr->name)){ if(ptr->isset) return ptr->value; printf("%s: ", name); ERR("variable is unbound yet", 0); } printf("%s: ", name); ERR("undefined variable", 0); } /* Создать новую переменную */ Var *internVar(char *name){ Var *ptr; for(ptr=vars; ptr->name; ptr++) if(eq(name, ptr->name)) return ptr; ptr->name = strdup(name); ptr->isset = 0; ptr->value = 0; return ptr; } /* Установить значение переменной */ void setVar(Var *ptr, int val){ ptr->isset = 1; ptr->value = val; } /* Распечатать значения переменных */ void printVars(){ Var *ptr; for(ptr=vars; ptr->name; ++ptr) printf("\t%s %s %d\n", ptr->isset ? "BOUND ":"UNBOUND", ptr->name, ptr->value); } А. Богатырев, 1992-95 - 339 - Си в UNIX /* Синтаксический разбор и одновременное вычисление ----------- */ /* Вычисление встроенных функций */ int apply(char *name, int args[], int nargs){ if(eq(name, "power2")){ if(nargs != 1) ERR("power2: wrong argument count", 0); return (1 << args[0]); } else if(eq(name, "min")){ if(nargs != 2) ERR("min: wrong argument count", 0); return (args[0] < args[1] ? args[0] : args[1]); } else if(eq(name, "max")){ if(nargs != 2) ERR("max: wrong argument count", 0); return (args[0] < args[1] ? args[1] : args[0]); } else if(eq(name, "sum")){ register i, sum; for(i=0, sum=0; i < nargs; sum += args[i++]); return sum; } else if(eq(name, "rand")){ switch(nargs){ case 0: return rand(); case 1: return rand() % args[0]; case 2: return args[0] + rand() % (args[1] - args[0] + 1); default: ERR("rand: wrong argument count", 0); } } ERR("Unknown function", args[0]); } /* Вычислить выражение из списка лексем. */ /* Синтаксис задан праворекурсивной грамматикой */ int expr(Token *t){ int val = 0; if(val = setjmp(breakpoint)) return val - 1; val = expression(&t); if(t){ printlex(NULL, t); ERR("Extra tokens", val); } return val; } /* <EXPRESSION> = <EXPASS> | <EXPASS> ";" <EXPRESSION> */ int expression(Token **v){ int arg = expass(v); if(*v && TYPE(v) == SMC ){ NEXT(v); return expression(v); } else return arg; } /* <EXPASS> = <ПЕРЕМЕННАЯ> "=" <EXPASS> | <EXP0> */ int expass(Token **v){ int arg; if(*v && (*v)->next && (*v)->next->type == OP && eq((*v)->next->token, "=")){ Var *ptr; /* присваивание (assignment) */ if( TYPE(v) != ID ) /* слева нужна переменная */ ERR("Lvalue needed", 0); ptr = internVar(TOKEN(v)); NEXT(v); NEXT(v); setVar(ptr, arg = expass(v)); return arg; } return exp0(v); } А. Богатырев, 1992-95 - 340 - Си в UNIX /* <EXP0> = <EXP1> | <EXP1> "||" <EXP0> */ int exp0(Token **v){ int arg = exp1(v); if(*v && TYPE(v) == OP && eq(TOKEN(v), "||")){ NEXT(v); return(exp0(v) || arg ); /* помещаем arg ВТОРЫМ, чтобы второй операнд вычислялся * ВСЕГДА (иначе не будет исчерпан список токенов и * возникнет ошибка в expr(); Это не совсем по правилам Си. */ } else return arg; } /* <EXP1> = <EXP2> | <EXP2> "&&" <EXP1> */ int exp1(Token **v){ int arg = exp2(v); if(*v && TYPE(v) == OP && eq(TOKEN(v), "&&")){ NEXT(v); return(exp1(v) && arg); } else return arg; } /* <EXP2> = <EXP2A> | <EXP2A> "|" <EXP2> */ int exp2(Token **v){ int arg = exp2a(v); if(*v && TYPE(v) == OP && eq(TOKEN(v), "|")){ NEXT(v); return( arg | exp2(v)); } else return arg; } /* <EXP2A> = <EXP2B> | <EXP2B> "^" <EXP2A> */ int exp2a(Token **v){ int arg = exp2b(v); if(*v && TYPE(v) == OP && eq(TOKEN(v), "^")){ NEXT(v); return( arg ^ exp2a(v)); } else return arg; } /* <EXP2B> = <EXP2C> | <EXP2C> "&" <EXP2B> */ int exp2b(Token **v){ int arg = exp2c(v); if(*v && TYPE(v) == OP && eq(TOKEN(v), "&")){ NEXT(v); return( arg & exp2b(v)); } else return arg; } /* <EXP2C> = <EXP3> | <EXP3> "==" <EXP3> | <EXP3> "!=" <EXP3> */ int exp2c(Token **v){ int arg = exp3(v); if(*v && TYPE(v) == OP && eq(TOKEN(v), "==")){ NEXT(v); return( arg == exp3(v)); } else if(*v && TYPE(v) == OP && eq(TOKEN(v), "!=")){ NEXT(v); return( arg != exp3(v)); } else return arg; } А. Богатырев, 1992-95 - 341 - Си в UNIX /* <EXP3> = <EXP3A> | <EXP3A> ">" <EXP3> | <EXP3A> "<" <EXP3> | <EXP3A> ">=" <EXP3> | <EXP3A> "<=" <EXP3> */ int exp3(Token **v){ int arg = exp3a(v); if(*v && TYPE(v) == OP && eq(TOKEN(v), ">")){ NEXT(v); return( arg && exp3(v)); }else if(*v && TYPE(v) == OP && eq(TOKEN(v), "<")){ NEXT(v); return( arg && exp3(v)); }else if(*v && TYPE(v) == OP && eq(TOKEN(v), ">=")){ NEXT(v); return( arg && exp3(v)); }else if(*v && TYPE(v) == OP && eq(TOKEN(v), "<=")){ NEXT(v); return( arg && exp3(v)); } else return arg; } /* <EXP3A> = <EXP4> | <EXP4> "<<" <EXP3A> | <EXP4> ">>" <EXP3A> */ int exp3a(Token **v){ int arg = exp4(v); if(*v && TYPE(v) == OP && eq(TOKEN(v), "<<")){ NEXT(v); return( arg << exp3a(v)); }else if(*v && TYPE(v) == OP && eq(TOKEN(v), ">>")){ NEXT(v); return( arg && exp3a(v)); } else return arg; } /* <EXP4> = <EXP5> | <EXP5> "+" <EXP4> | <EXP5> "-" <EXP4> */ int exp4(Token **v){ int arg = exp5(v); if(*v && TYPE(v) == OP && eq(TOKEN(v), "+")){ NEXT(v); return( arg + exp4(v)); }else if(*v && TYPE(v) == OP && eq(TOKEN(v), "-")){ NEXT(v); return( arg - exp4(v)); } else return arg; } /* <EXP5> = <EXP6> | <EXP6> "*" <EXP5> | <EXP6> "/" <EXP5> | <EXP6> "%" <EXP5> */ int exp5(Token **v){ int arg = exp6(v), arg1; if(*v && TYPE(v) == OP && eq(TOKEN(v), "*")){ NEXT(v); return( arg * exp5(v)); }else if(*v && TYPE(v) == OP && eq(TOKEN(v), "/")){ NEXT(v); if((arg1 = exp5(v)) == 0) ERR("Zero divide", arg); return( arg / arg1); }else if(*v && TYPE(v) == OP && eq(TOKEN(v), "%")){ NEXT(v); if((arg1 = exp5(v)) == 0) ERR("Zero module", arg); return( arg % arg1); } else return arg; } А. Богатырев, 1992-95 - 342 - Си в UNIX /* <EXP6> = "!"<EXP6> | "~"<EXP6> | "-"<EXP6> | "(" <EXPRESSION> ")" | <ИМЯФУНКЦИИ> "(" [ <EXPRESSION> [ "," <EXPRESSION> ]... ] ")" | <ЧИСЛО> | <CH_ПЕРЕМЕННАЯ> */ int exp6(Token **v){ int arg; if( !*v) ERR("Lost token", 0); if(TYPE(v) == OP && eq(TOKEN(v), "!")){ NEXT(v); return !exp6(v); } if(TYPE(v) == OP && eq(TOKEN(v), "~")){ NEXT(v); return ~exp6(v); } if(TYPE(v) == OP && eq(TOKEN(v), "-")){ NEXT(v); return -exp6(v); /* унарный минус */ } if(TYPE(v) == OPEN){ NEXT(v); arg = expression(v); if( !*v || TYPE(v) != CLOSE) ERR("Lost ')'", arg); NEXT(v); return arg; } if(TYPE(v) == NUM){ /* изображение числа */ arg = atoi(TOKEN(v)); NEXT(v); return arg; } if(TYPE(v) == ID){ char *name = (*v)->token; int args[20], nargs = 0; NEXT(v); if(! (*v && TYPE(v) == OPEN)){ /* Переменная */ return expvar(v, name); } /* Функция */ args[0] = 0; do{ NEXT(v); if( *v && TYPE(v) == CLOSE ) break; /* f() */ args[nargs++] = expression(v); } while( *v && TYPE(v) == COMMA); if(! (*v && TYPE(v) == CLOSE)) ERR("Error in '()'", args[0]); NEXT(v); return apply(name, args, nargs); } printlex(TOKEN(v), *v); ERR("Unknown token type", 0); } /* <CH_ПЕРЕМЕННАЯ> = <ПЕРЕМЕННАЯ> | <ПЕРЕМЕННАЯ> "++" | <ПЕРЕМЕННАЯ> "--" Наши операции ++ и -- соответствуют ++x и --x из Си */ int expvar(Token **v, char *name){ int arg = getVar(name); Var *ptr = internVar(name); if(*v && TYPE(v) == OP){ if(eq(TOKEN(v), "++")){ NEXT(v); setVar(ptr, ++arg); return arg; } if(eq(TOKEN(v), "--")){ NEXT(v); setVar(ptr, --arg); return arg; } } return arg; } А. Богатырев, 1992-95 - 343 - Си в UNIX /* Головная функция ------------------------------------------- */ char input[256]; Token *head, *tail; void main(){ do{ printf(": "); fflush(stdout); if( !gets(input)) break; if(!*input){ printVars(); continue; } if(eq(input, "!!")) ; /* ничего не делать, т.е. повторить */ else{ if(head) freelex(&head); lex(&head, &tail, input); } printf("Result: %d\n", expr(head)); } while(1); putchar('\n'); }

n


ются двоеточиями. Предусмотрите задание символа-разделителя из аргументов программы.

etc


Для выделения очередного поля можно использовать следующую процедуру: main(){ char c, *next, *strchr(); int nfield; char *s = "11111:222222222:333333:444444"; for(nfield=0;;nfield++){ if(next = strchr(s, ':')){ c= *next; *next= '\0'; } printf( "Поле #%d: '%s'\n", nfield, s); /* можно сделать с полем s что-то еще */ if(next){ *next= c; s= next+1; continue; } else { break; /* последнее поле */ } } } 7.70. Разработайте архитектуру и систему команд учебной машины и напишите интерпре- татор учебного ассемблера, отрабатывающего по крайней мере такие команды: mov пересылка (:=) add сложение sub вычитание cmp сравнение и выработка признака jmp переход jeq переход, если == jlt переход, если < jle переход, если <= neg изменение знака not инвертирование признака 7.71. Напишите программу, преобразующую определения функций Си в "старом" стиле в "новый" стиль стандарта ANSI ("прототипы" функций).

x


x


s


v


{ ... } преобразуется в

x


{ ... }

y


А. Богатырев, 1992-95 - 344 - Си в UNIX Еще пример: char *ff() { ... } заменяется на char *ff(void){ ... } В данной задаче вам возможно придется использовать программу lex.

всех


описатель int нельзя опускать. Так q(x, s) char *s; { ... } // не прототип, допустимо. // x - int по умолчанию. q(x, char *s); // недопустимо. q(int x, char *s); // верно. Собственно под "прототипом" понимают предварительное описание функции в новом стиле - где вместо тела {...} сразу после заголовка стоит точка с запятой.

x


...

x


В прототипе имена аргументов можно опускать: long f(long, long); /* прототип */ char *strchr(char *, char); Это предварительное описание помещают где-нибудь в начале программы, до первого вызова функции. В современном Си прототипы заменяют описания вида extern long f(); о которых мы говорили раньше. Прототипы предоставляют программисту механизм для автоматического контроля формата вызова функции. Так, если функция имеет прототип double f( double ); и вызывается как

x


то компилятор автоматически превратит это в

x


(поскольку существует приведение типа от int к double); если же написано f( "привет" ); то компилятор сообщит об ошибке (так как нет преобразования типа (char *) в double). Прототип принуждает компилятор проверять: a) соответствие ТИПОВ фактических параметров (при вызове) типам формальных парамет- ров (в прототипе); b) соответствие КОЛИЧЕСТВА фактических и формальных параметров; c) тип возвращаемого функцией значения. Прототипы обычно помещают в include-файлы. Так в ANSI стандарте Си предусмотрен файл, подключаемый

stdlib


А. Богатырев, 1992-95 - 345 - Си в UNIX в котором определены прототипы функций из стандартной библиотеки языка Си. Черезвы- чайно полезно писать эту директиву include, чтобы компилятор проверял, верно ли вы вызываете стандартные функции. Заметим, что если вы определили прототипы каких-то функций, но в своей программе

не все


указание


компилятору; ни в какие машинные команды они не транслируются. То же самое касается описаний внешних переменных и функций в виде

x


extern char *func(); Если вы не используете переменную или функцию с таким именем, то эти строки не имеют никакого эффекта (как бы вообще отсутствуют). 7.72. Обратная задача: напишите преобразователь из нового стиля в старый.

x


переводить в

x


7.73. Довольно легко использовать прототипы таким образом, что они потеряют всякий смысл. Для этого надо написать программу, состоящую из нескольких файлов, и в каждом файле использовать свои прототипы для одной и той же функции. Так бывает, когда вы поменяли функцию и прототип в одном файле, быть может во втором, но забыли сделать это в остальных. -------- файл a.c -------- void g(void); void h(void); int x = 0, y = 13; void f(int arg){ printf("f(%d)\n", arg); x = arg; x++; } int main(int ac, char *av[]){ h(); f(1); g(); printf("x=%d y=%d\n", x, y); return 0; } А. Богатырев, 1992-95 - 346 - Си в UNIX -------- файл b.c -------- extern int x, y; int f(int); void g(){ y = f(5); } -------- файл c.c -------- void f(); void h(){ f(); } Выдача программы: abs@wizard$ cc a.c b.c c.c -o aaa a.c: b.c: c.c: abs@wizard$ aaa f(-277792360) f(1) f(5) x=6 y=5 abs@wizard$ Обратите внимание, что во всех трех файлах f() имеет разные прототипы! Поэтому прог- рамма печатает нечто, что довольно-таки бессмысленно! Решение таково: стараться вынести прототипы в include-файл, чтобы все файлы программы включали одни и те же прототипы. Стараться, чтобы этот include-файл вклю- чался также в файл с самим определением функции. В таком случае изменение только заголовка функции или только прототипа вызовет ругань компилятора о несоответствии. Вот как должен выглядеть наш проект: ------------- файл header.h ------------- extern int x, y; void f(int arg); int main(int ac, char *av[]); void g(void); void h(void); А. Богатырев, 1992-95 - 347 - Си в UNIX -------- файл a.c -------- #include "header.h" int x = 0, y = 13; void f(int arg){ printf("f(%d)\n", arg); x = arg; x++; } int main(int ac, char *av[]){ h(); f(1); g(); printf("x=%d y=%d\n", x, y); return 0; } -------- файл b.c -------- #include "header.h" void g(){ y = f(5); } -------- файл c.c -------- #include "header.h" void h(){ f(); } Попытка компиляции: abs@wizard$ cc a.c b.c c.c -o aaa a.c: b.c: "b.c", line 4: operand cannot have void type: op "=" "b.c", line 4: assignment type mismatch: int "=" void cc: acomp failed for b.c c.c: "c.c", line 4: prototype mismatch: 0 args passed, 1 expected cc: acomp failed for c.c А. Богатырев, 1992-95 - 348 - Си в UNIX

8. Экранные библиотеки и работа с видеопамятью.


два


экран


клавиатуры


Современные терминалы в определенном смысле являются устройствами прямого дос- тупа: - информация может быть выведена в любое место экрана, а не только последовательно строка за строкой. - некоторые терминалы позволяют прочесть содержимое произвольной области экрана в вашу программу. Традиционные терминалы являются самостоятельными устройствами, общающимися с компью-

систему команд терминала


быть различен для терминалов разных моделей. Поэтому библиотека работы с традиционным терминалом должна решать следующие проблемы: - настройка на систему команд данного устройства, чтобы одна и та же программа работала на разных типах терминалов. - эмуляция недостающих в системе команд; максимальное использование предоставлен- ных терминалом возможностей. - мимнимизация передачи данных через линию связи (для ускорения работы). - было бы полезно, чтобы библиотека предоставляла пользователю некоторые логичес- кие абстракции, вроде ОКОН - прямоугольных областей на экране, ведущих себя подобно маленьким терминалам. В UNIX эти задачи решает стандартная библиотека curses (а только первую задачу - более простая библиотека termcap). Для настройки на систему команд конкретного дисп- лея эти библиотеки считывают описание системы команд, хранящееся в файле

etc


способы работы с экраном (через видеопамять, см. ниже). В задачах данного раздела вам придется пользоваться библиотекой curses. При ком-

curses


в следующем примере:

progr


usr


usr


библиотек должны быть записаны в команде САМЫМИ ПОСЛЕДНИМИ. Заметим, что стандартная библиотека языка Си (содержащая системные вызовы, библиотеку stdio (функции printf, scanf, fread, fseek, ...), разные часто употребляемые функции (strlen, strcat, sleep,

lib


c


В начале своей программы вы должны написать директиву

curses


usr


мых библиотекой curses, некоторые предопределенные константы и.т.п. (это надо, чтобы ваша программа пользовалась именно этими стандартными соглашениями). Посмотрите в этот файл! Когда вы пользуетесь curses-ом, вы НЕ должны пользоваться функциями стандартной библиотеки stdio для непосредственного вывода на экран; так вы не должны пользоваться ____________________

протоколом


вера и клиентов; двух машин в сети (кстати, термин для обозначения машины в сети - "host" или "site")) о формате (правилах оформления) и смысле данных в передаваемых друг другу сообщениях. Аналогия из жизни - человеческие речь и язык. Речь всех лю- дей состоит из одних и тех же звуков и может быть записана одними и теми же буквами (а данные - байтами). Но если два человека говорят на разных языках - т.е. по- разному конструируют фразы и интерпретируют звуки - они не поймут друг друга! А. Богатырев, 1992-95 - 349 - Си в UNIX функциями printf, putchar. Это происходит потому, что curses хранит в памяти про- цесса копию содержимого экрана, и если вы выводите что-либо на экран терминала обходя функции библиотеки curses, то реальное содержимое экрана и позиция курсора на нем перестают соответствовать хранимым в памяти, и библиотека curses начнет выводить неп- равильное изображение. ПРОГРАММА | |

CURSES


| printw,addch,move | | V V

STDIO


Таким образом, curses является дополнительным "слоем" между вашей программой и стан- дартным выводом и игнорировать этот слой не следует. Напомним, что изображение, создаваемое при помощи библиотеки curses, сначала формируется в памяти программы без выполнения каких-либо операций с экраном дисплея (т.е. все функции wmove, waddch, waddstr, wprintw изменяют только ОБРАЗЫ окон в

ПОСЛЕ


вете функцию refresh() ("обновить"), все изменения происшедшие в окнах будут отобра-

одновременное


экрана позволяет провести ряд оптимизаций). Если вы забудете сделать refresh - экран останется неизменным. Обычно эту функцию вызывают перед тем, как запросить у пользо-

ввод


тинку. Хранение содержимого окон в памяти программы позволяет ей считывать содержи-

обычных


содержимое какой-либо области экрана. Общение с терминалом через линию связи (или вообще через последовательный прото- кол) является довольно медленным. На персональных компьютерах существует другой спо-

видеопамять


ную область памяти компьютера, содержимое которой аппаратно отображается на экране консоли. Работа с экраном превращается для программиста в работу с этим массивом байт (запись/чтение). Программы, пользующиеся этим способом, просты и работают очень быстро (ибо доступ к памяти черезвычайно быстр, и сделанные в ней изменения "проявля- ются" на экране почти мгновенно). Недостаток таких программ - привязанность к конк- ретному типу машины. Эти программы немобильны и не могут работать ни на обычных тер- миналах (подключаемых к линии связи), ни на машинах с другой структурой видеопамяти. Выбор между "традиционной" работой с экраном и прямым доступом (фактически - между мобильностью и скоростью) - вопрос принципиальный, тем не менее принятие решения зависит только от вас. Видеопамять IBM PC в текстовом режиме 80x25 16 цветов имеет следующую структуру: struct symbol{ /* IBM PC family */ char chr; /* код символа */ char attr; /* атрибуты символа (цвет) */ } mem[ 25 ] [ 80 ]; /* 25 строк по 80 символов */ Структура байта атрибутов: ------------------------------------------- | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | # бита ------------------|------------------------ |blink| R | G | B | intensity | r | g | b | цвет ------------------|------------------------

background


R - red (красный) G - green (зеленый) B - blue (синий) blink - мерцание букв (не фона!) intensity - повышенная яркость А. Богатырев, 1992-95 - 350 - Си в UNIX Координатная система на экране: верхний левый угол экрана имеет координаты (0,0), ось

сверху вниз


Цвет символа получается смешиванием 3х цветов: красного, зеленого и синего (электронно-лучевая трубка дисплея имеет 3 электронные пушки, отвечающие этим цве- там). Кроме того, допустимы более яркие цвета. 4 бита задают комбинацию 3х основных цветов и повышенной яркости. Образуется 2**4=16 цветов:

номер цвета


BLACK 0 0 0 0 0 черный BLUE 0 0 0 1 1 синий GREEN 0 0 1 0 2 зеленый CYAN 0 0 1 1 3 циановый (серо-голубой) RED 0 1 0 0 4 красный MAGENTA 0 1 0 1 5 малиновый BROWN 0 1 1 0 6 коричневый LIGHTGRAY 0 1 1 1 7 светло-серый (темно-белый) DARKGRAY 1 0 0 0 8 темно-серый LIGHTBLUE 1 0 0 1 9 светло-синий LIGHTGREEN 1 0 1 0 10 светло-зеленый LIGHTCYAN 1 0 1 1 11 светло-циановый LIGHTRED 1 1 0 0 12 ярко-красный LIGHTMAGENTA 1 1 0 1 13 ярко-малиновый YELLOW 1 1 1 0 14 желтый WHITE 1 1 1 1 15 (ярко)-белый Физический адрес видеопамяти IBM PC в цветном алфавитно-цифровом режиме (80x25, 16 цветов) равен 0xB800:0x0000. В MS DOS указатель на эту память можно получить при

make far pointer


XENIX|- указатель получается при помощи системного вызова ioctl, причем система пре- доставит вам виртуальный адрес, ибо привелегия работы с физическими адресами в UNIX принадлежит только системе. Работу с экраном в XENIX вы можете увидеть в примере "осыпающиеся буквы". 8.1. /*#! /bin/cc fall.c -o fall -lx * "Осыпающиеся буквы". * Использование видеопамяти IBM PC в ОС XENIX. * Данная программа иллюстрирует доступ к экрану * персонального компьютера как к массиву байт; * все изменения в массиве немедленно отображаются на экране. * Функция nap() находится в библиотеке -lx * Показана также работа с портами IBM PC при помощи ioctl(). */ #include <stdio.h> #include <fcntl.h> /* O_RDWR */ #include <signal.h> #include <ctype.h> #include <sys/types.h> #include <sys/at_ansi.h> #include <sys/kd.h> /* for System V/4 and Interactive UNIX only */ /*#include <sys/machdep.h> for XENIX and SCO UNIX only */ #include <sys/sysmacros.h> ____________________ |- XENIX - (произносится "зиникс") версия UNIX для IBM PC, первоначально разрабо- танная фирмой Microsoft и поставляемая фирмой Santa Cruz Operation (SCO). А. Богатырев, 1992-95 - 351 - Си в UNIX #ifdef M_I386 # define far /* на 32-битной машине far не требуется */ #endif char far *screen; /* видеопамять как массив байт */ /* far - "длинный" (32-битный) адрес(segment,offset) */ int segm; /* сегмент с видеопамятью */ #define COLS 80 /* число колонок на экране */ #define LINES 25 /* число строк */ #define DELAY 20 /* задержка (миллисекунд) */ int ega; /* дескриптор для доступа к драйверу EGA */ /* структура для обмена с портами */ static struct port_io_struct PORT[ 4 /* не более 4 за раз */] = { /* операция номер порта данные */ /* .dir .port .data */ /* Переустановить flip/flop: * заставить порт 0x3C0 ожидать пары адрес/значение * при последовательной записи байтов в этот порт. */ { IN_ON_PORT, 0x3DA, -1 }, /* IN-чтение */ /* Теперь 3c0 ожидает пары адрес/значение */ { OUT_ON_PORT, 0x3C0, -1 /* адрес */ }, { OUT_ON_PORT, 0x3C0, -1 /* значение*/ }, /* OUT-запись */ /* переинициализировать дисплей, установив бит #5 порта 3c0 */ { OUT_ON_PORT, 0x3C0, 0x20 } }; void closescr(nsig){ /* конец работы */ setbgcolor(0); /* установить черный фон экрана */ exit(0); } /* получение доступа к видеопамяти адаптера VGA/EGA/CGA */ void openscr () { static struct videodev { char *dev; int mapmode; } vd[] = { { "/dev/vga", MAPVGA }, { "/dev/ega", MAPEGA }, { "/dev/cga", MAPCGA }, { NULL, -1 } }, *v; /* устройство для доступа к видеоадаптеру */ for(v=vd; v->dev;v++ ) if((ega = open (v->dev, O_RDWR)) >= 0 ) goto ok; fprintf( stderr, "Can't open video adapter\n" ); exit(1); А. Богатырев, 1992-95 - 352 - Си в UNIX ok: /* fprintf(stderr, "Adapter:%s\n", v->dev); */ /* получить адрес видеопамяти и доступ к ней */ #ifdef M_I386 screen = (char *) ioctl (ega, v->mapmode, 0); #else segm = ioctl (ega, v->mapmode, 0); screen = sotofar (segm, 0); /* (segment,offset) to far pointer */ #endif signal( SIGINT, closescr ); } /* макросы для доступа к байтам "символ" и "атрибуты" * в координатах (x,y) экрана. */ #define GET(x,y) screen[ ((x) + (y) * COLS ) * 2 ] #define PUT(x,y, c) screen[ ((x) + (y) * COLS ) * 2 ] = (c) #define GETATTR(x,y) screen[ ((x) + (y) * COLS ) * 2 + 1 ] #define PUTATTR(x,y, a) screen[ ((x) + (y) * COLS ) * 2 + 1 ] = (a) /* символ изображается как черный пробел ? */ #define white(c,a) ((isspace(c) || c==0) && (attr & 0160)==0) /* установить цвет фона экрана */ void setbgcolor( color ){ PORT[1].data = 0; /* регистр номер 0 палитры содержит цвет фона */ /* всего в палитре 16 регистров (0x00...0xFF) */ PORT[2].data = color ; /* новое значение цвета, составленное как битовая маска * RGBrgb (r- красный, g- зеленый, b- синий, RGB- дополнительные * тусклые цвета) */ /* выполнить обмены с портами */ if( ioctl( ega, EGAIO, PORT ) < 0 ){ fprintf( stderr, "Can't out port\n" ); perror( "out" ); } } А. Богатырев, 1992-95 - 353 - Си в UNIX void main(ac, av) char **av;{ void fall(); openscr(); if( ac == 1 ){ setbgcolor(020); /* темно-зеленый фон экрана */ fall(); /* осыпание букв */ } else { if(*av[1] == 'g') /* Установить режим адаптера graphics 640x350 16-colors */ ioctl( ega, SW_CG640x350, NULL); /* Если вы хотите получить адрес видеопамяти в графическом режиме, * вы должны СНАЧАЛА включить этот режим, * ЗАТЕМ сделать screen=ioctl(ega, v->mapmode, NULL); * и ЕЩЕ РАЗ сделать включение графического режима. */ /* Установить режим адаптера text 80x25 16-colors */ else ioctl( ega, SW_ENHC80x25, NULL); } closescr(0); } /* осыпать буквы вниз */ void fall(){ register i, j; int rest; int nextcol; int n; int changed = 1; /* не 0, если еще не все буквы опали */ char mask [ COLS ]; while( changed ){ changed = 0; for( i = 0 ; i < COLS ; i++ ) mask[ i ] = 0; for( i = 0 ; i < COLS ; i++ ){ rest = COLS - i; /* осталось осыпать колонок */ nextcol = rand() % rest; j = 0; /* индекс в mask */ n = 0; /* счетчик */ for(;;){ if( mask[j] == 0 ){ if( n == nextcol ) break; n++; } j++; } changed += fallColumn( j ); mask[j] = 1; } } } А. Богатырев, 1992-95 - 354 - Си в UNIX /* осыпать буквы в одном столбце */ int fallColumn( x ){ register int y; char ch, attr; int firstspace = (-1); int firstnospace = (-1); Again: /* find the falled array */ for( y=LINES-1; y >= 0 ; y-- ){ ch = GET( x, y ); attr = GETATTR( x,y ); if( white(ch, attr)){ firstspace = y; goto FindNoSpace; } } AllNoSpaces: return 0; /* ничего не изменилось */ FindNoSpace: /* найти не пробел */ for( ; y >= 0 ; y-- ){ ch = GET( x, y ); attr = GETATTR( x, y ); if( !white(ch, attr)){ firstnospace = y; goto Fall; } } AllSpaces: /* в данном столбце все упало */ return 0; Fall: /* "уронить" букву */ for( y = firstnospace ; y < firstspace ; y++ ){ /* переместить символ на экране на одну позицию вниз */ ch = GET( x, y ); attr = GETATTR( x, y ); PUT( x, y, 0 ); PUTATTR( x, y, 0 ); PUT( x, y+1 , ch ); PUTATTR( x, y+1, attr ); nap( DELAY ); /* подождать DELAY миллисекунд */ } return 1; /* что-то изменилось */ } 8.2. Для работы может оказаться более удобным иметь указатель на видеопамять как на массив структур. Приведем пример для системы MS DOS: А. Богатырев, 1992-95 - 355 - Си в UNIX #include <dos.h> /* там определено MK_FP */ char far *screen = MK_FP(0xB800 /*сегмент*/, 0x0000 /*смещение*/); struct symb{ char chr; char attr; } far *scr, far *ptr; #define COLS 80 /* число колонок */ #define LINES 25 /* число строк */ #define SCR(x,y) scr[(x) + COLS * (y)] /* x из 0..79, y из 0..24 */ void main(){ int x, y; char c; scr = (struct symb far *) screen; /* или сразу * scr = (struct symb far *) MK_FP(0xB800,0x0000); */ /* переписать строки экрана справа налево */ for(x=0; x < COLS/2; x++ ) for( y=0; y < LINES; y++ ){ c = SCR(x,y).chr; SCR(x,y).chr = SCR(COLS-1-x, y).chr; SCR(COLS-1-x, y).chr = c; } /* сделать цвет экрана: желтым по синему */ for(x=0; x < COLS; x++) for(y=0; y < LINES; y++) SCR(x,y).attr = (0xE | (0x1 << 4)); /* желтый + синий фон */ /* прочесть любую кнопку с клавиатуры (пауза) */ (void) getch(); } И, наконец, еще удобнее работа с видеопамятью как с двумерным массивом структур: #include <dos.h> /* MS DOS */ #define COLS 80 #define LINES 25 struct symb { char chr; char attr;

_


void main(void){ register x, y; for(y=0; y < LINES; y++) for(x=0; x < COLS; ++x){ scr[y][x].chr = '?'; scr[y][x].attr = (y << 4) | (x & 0xF); } getch(); } Учтите, что при работе с экраном через видеопамять, курсор не перемещается! Если в А. Богатырев, 1992-95 - 356 - Си в UNIX

автоматически


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

явным образом


записи в видеопамять (например, обращаясь к портам видеоконтроллера). Обратите внимание, что спецификатор модели памяти far должен указываться перед КАЖДЫМ указателем (именно для иллюстрации этого в первом примере описан неиспользуе-

ptr


8.3. Составьте программу сохранения содержимого экрана IBM PC (видеопамяти) в текс- товом режиме в файл и обратно (в системе XENIX). 8.4. Пользуясь прямым доступом в видеопамять, напишите функции для спасения прямоу- гольной области экрана в массив и обратно. Вот функция для спасения в массив: typedef struct { short xlen, ylen; char *save; } Pict; extern void *malloc(unsigned); Pict *gettext (int x, int y, int xlen, int ylen){ Pict *n = (Pict *) malloc(sizeof *n); register char *s; register i, j; n->xlen = xlen; n->ylen = ylen; s = n->save = (char *) malloc( 2 * xlen * ylen ); for(i=y; i < y+ylen; i++) for(j=x; j < x+xlen; j++){ *s++ = SCR(j,i).chr ; *s++ = SCR(j,i).attr; } return n; }

xlen


buf


лять. void puttext (Pict *n, int x, int y){ register char *s = n->save; register i, j; for(i=y; i < y + n->ylen; i++) for(j=x; j < x + n->xlen; j++){ SCR(j,i).chr = *s++; SCR(j,i).attr = *s++; } } /* очистка памяти текстового буфера */ void deltext(Pict *n){ free(n->save); free(n); } Приведем еще одну полезную функцию, которая может вам пригодиться - это аналог printf при прямой работе с видеопамятью. #include <stdarg.h> /* текущий цвет: белый по синему */ static char currentColor = 0x1F; int videoprintf (int x, int y, char *fmt, ...){ char buf[512], *s; va_list var; А. Богатырев, 1992-95 - 357 - Си в UNIX /* clipping (отсечение по границам экрана) */ if( y < 0 || y >= LINES ) return x; va_start(var, fmt); vsprintf(buf, fmt, var); va_end(var); for(s=buf; *s; s++, x++){ /* отсечение */ if(x < 0 ) continue; if(x >= COLS) break; SCR(x,y).chr = *s; SCR(x,y).attr = currentColor; } return x; } void setcolor (int col){ currentColor = col; } 8.5. Пользуясь написанными функциями, реализуйте функции для "выскакивающих" окон

pop


save


save


// ... рисуем цветными пробелами прямоугольник с

x


// внизу-справа... // ...рисуем некие таблицы, меню, текст в этой зоне... // стираем нарисованное окно, восстановив то изображение, // поверх которого оно "всплыло".

save


save


Для начала напишите "выскакивающее" окно с сообщением; окно должно исчезать по нажа- тию любой клавиши.

x


text


чения функции.

text


8.6. Сделайте так, чтобы "выскакивающие" окна имели тень. Для этого надо сохранить в некоторый буфер атрибуты символов (сами символы не надо!), находящихся на местах $: ########## ##########$ ##########$ $$$$$$$$$$ а затем прописать этим символам на экране атрибут 0x07 (белый по черному). При стира- нии окна (puttext-ом) следует восстановить спасенные атрибуты этих символов (стереть

xlen


8.7. Напишите функцию, рисующую на экране прямоугольную рамку. Используйте ее для рисования рамки окна. А. Богатырев, 1992-95 - 358 - Си в UNIX 8.8. Напишите "выскакивающее" окно, которое проявляется на экране как бы расширяясь из точки: ############## ###### ############## ### ###### ############## ###### ############## ##############

x


x


x0


void zoom(int x0, int y0, int W, int H){ int x, y, w, h, hprev; /* промежуточное окно */ for(hprev=0, w=1; w < W; w++){ h = H * w; h /= W; /* W/H == w/h */ if(h == hprev) continue; hprev = h; x = x0 + (W - w)/2; /* чтобы центры окон */ y = y0 + (H - h)/2; /* совпадали */ box(x, y, w, h); delay(10); /* задержка 10 миллисек. */ } box(x0, y0, W, H); } 8.9. Составьте библиотеку функций, аналогичных библиотеке curses, для ЭВМ IBM PC в ОС XENIX. Используйте прямой доступ в видеопамять. 8.10. Напишите рекурсивное решение задачи "ханойские башни" (перекладывание дисков: есть три стержня, на один из них надеты диски убывающего к вершине диаметра. Требу- ется переложить их на третий стержень, никогда не кладя диск большего диаметра поверх диска меньшего диаметра). Усложнение - используйте пакет curses для изображения перекладывания дисков на экране терминала. Указание: идея рекурсивного алгоритма:

n


n


from


n


}

n


n


from


to


by


n


8.11. Напишите программу, ищущую выход из лабиринта ("червяк в лабиринте"). Лаби-

maze


имеет рекурсивную природу и выглядит примерно так:

setjmp


_


maze(){ /* Это головная функция */

jmp


x


x


А. Богатырев, 1992-95 - 359 - Си в UNIX } }

x


x


x


x


x


x


x


x


x


}

x


x


x


y


x


x


y


}

лабиринт


червяка, ползающего по лабиринту в своих исканиях. 8.12. Используя библиотеку termcap напишите функции для: - очистки экрана. - позиционирования курсора. - включения/выключения режима выделения текста инверсией. 8.13. Используя написанные функции, реализуйте программу выбора в меню. Выбранную строку выделяйте инверсией фона. /*#!/bin/cc termio.c -O -o termio -ltermcap * Смотри man termio, termcap и screen. * Работа с терминалом в стиле System-V. * Работа с системой команд терминала через /etc/termcap * Работа со временем. * Работа с будильником. */ #include <stdio.h> /* standard input/output */ #include <sys/types.h> /* system typedefs */ #include <termio.h> /* terminal input/output */ #include <signal.h> /* signals */ #include <fcntl.h> /* file control */ #include <time.h> /* time structure */ void setsigs(), drawItem(), drawTitle(), prSelects(), printTime(); А. Богатырев, 1992-95 - 360 - Си в UNIX /* Работа с описанием терминала TERMCAP ---------------------------------*/ extern char *getenv (); /* получить переменную окружения */ extern char *tgetstr (); /* получить строчный описатель /termcap/ */ extern char *tgoto (); /* подставить %-параметры /termcap/ */ static char Tbuf[2048], /* буфер для описания терминала, обычно 1024 */ /* Tbuf[] можно сделать локальной автоматической переменной * в функции tinit(), чтобы не занимать место */ Strings[256], /* буфер для расшифрованных описателей */ *p; /* вспомогательная перем. */ char *tname; /* название типа терминала */ int COLS, /* число колонок экрана */ LINES; /* число строк экрана */ char *CM; /* описатель: cursor motion */ char *CL; /* описатель: clear screen */ char *CE; /* описатель: clear end of line */ char *SO, *SE; /* описатели: standout Start и End */ char *BOLD, *NORM; /* описатели: boldface and NoStandout */ int BSflag; /* можно использовать back space '\b' */ void tinit () { /* Функция настройки на систему команд дисплея */ p = Strings; /* Прочесть описание терминала в Tbuf */ switch (tgetent (Tbuf, tname = getenv ("TERM"))) { case -1: printf ("Нет файла TERMCAP (/etc/termcap).\n"); exit (1); case 0: printf ("Терминал %s не описан.\n", tname); exit (2); case 1: break; /* OK */ } COLS = tgetnum ("co"); /* Прочесть числовые описатели. */ LINES = tgetnum ("li"); CM = tgetstr ("cm", &p); /* Прочесть строчные описатели. */ CL = tgetstr ("cl", &p); /* Описатель дешифруется и заносится */ CE = tgetstr ("ce", &p); /* в массив по адресу p. Затем */ SO = tgetstr ("so", &p); /* указатель p продвигается на */ SE = tgetstr ("se", &p); /* свободное место, а адрес расшиф- */ BOLD = tgetstr ("md", &p); /* рованной строки выдается из ф-ции */ NORM = tgetstr ("me", &p); BSflag = tgetflag( "bs" ); /* Узнать значение флажка: 1 - есть, 0 - нет */ } А. Богатырев, 1992-95 - 361 - Си в UNIX /* Макрос, внесенный в функцию. Дело в том, что tputs в качестве третьего аргумента требует имя функции, которую она вызывает в цикле: (*f)(c); Если подать на вход макрос, вроде putchar, а не адрес входа в функцию, мы и не достигнем желанного эффекта, и получим ругань от компилятора. */ void put (c) char c; { putchar (c); } /* очистить экран */ void clearScreen () { if (CL == NULL) /* Функция tputs() дорасшифровывает описатель */ return; /* (обрабатывая задержки) и выдает его */ tputs (CL, 1, put); /* посимвольно ф-цией put(c) 1 раз */ /* Можно выдать команду не 1 раз, а несколько: например если это */ /* команда сдвига курсора на 1 позицию влево '\b' */ } /* очистить конец строки, курсор остается на месте */ void clearEOL () { /* clear to the end of line */ if (CE == NULL) return; tputs (CE, 1, put); } /* позиционировать курсор */ void gotoXY (x, y) { /* y - по вертикали СВЕРХУ-ВНИЗ. */ if (x < 0 || y < 0 || x >= COLS || y >= LINES) { printf ("Точка (%d,%d) вне экрана\n", x, y); return; } /* CM - описатель, содержащий 2 параметра. Подстановку параметров * делает функция tgoto() */ tputs (tgoto (CM, x, y), 1, put); } /* включить выделение */ void standout () { if (SO) tputs (SO, 1, put); } /* выключить выделение */ void standend () { if (SE) tputs (SE, 1, put); /* else normal(); */ } /* включить жирный шрифт */ void bold () { if (BOLD) tputs (BOLD, 1, put); } А. Богатырев, 1992-95 - 362 - Си в UNIX /* выключить любой необычный шрифт */ void normal () { if (NORM) tputs (NORM, 1, put); else standend(); } /* Управление драйвером терминала --------------------------------- */ #define ESC '\033' #define ctrl(c) ((c) & 037 ) int curMode = 0; int inited = 0; struct termio old, new; int fdtty; void ttinit () { /* открыть терминал в режиме "чтение без ожидания" */ fdtty = open ("/dev/tty", O_RDWR | O_NDELAY); /* узнать текущие режимы драйвера */ ioctl (fdtty, TCGETA, &old); new = old; /* input flags */ /* отменить преобразование кода '\r' в '\n' на вводе */ new.c_iflag &= ~ICRNL; if ((old.c_cflag & CSIZE) == CS8) /* 8-битный код */ new.c_iflag &= ~ISTRIP; /* отменить & 0177 на вводе */ /* output flags */ /* отменить TAB3 - замену табуляций '\t' на пробелы */ /* отменить ONLCR - замену '\n' на пару '\r\n' на выводе */ new.c_oflag &= ~(TAB3 | ONLCR); /* local flags */ /* выключить режим ICANON, включить CBREAK */ /* выключить эхоотображение набираемых символов */ new.c_lflag &= ~(ICANON | ECHO); /* control chars */ /* при вводе с клавиш ждать не более ... */ new.c_cc[VMIN] = 1; /* 1 символа и */ new.c_cc[VTIME] = 0; /* 0 секунд */ /* Это соответствует режиму CBREAK */ /* Символы, нажатие которых заставляет драйвер терминала послать сигнал * либо отредактировать набранную строку. Значение 0 означает, * что соответствующего символа не будет */ new.c_cc[VINTR] = ctrl ('C'); /* символ, генерящий SIGINT */ new.c_cc[VQUIT] = '\0'; /* символ, генерящий SIGQUIT */ new.c_cc[VERASE] = '\0'; /* забой (отмена последнего символа)*/ new.c_cc[VKILL] = '\0'; /* символ отмены строки */ /* По умолчанию эти кнопки равны: DEL, CTRL/\, BACKSPACE, CTRL/U */ setsigs (); inited = 1; /* уже инициализировано */ } А. Богатырев, 1992-95 - 363 - Си в UNIX void openVisual () { /* open visual mode (включить "экранный" режим) */ if (!inited) ttinit (); if (curMode == 1) return; /* установить моды драйвера из структуры new */ ioctl (fdtty, TCSETAW, &new); curMode = 1; /* экранный режим */ } void closeVisual () { /* canon mode (включить канонический режим) */ if (!inited) ttinit (); if (curMode == 0) return; ioctl (fdtty, TCSETAW, &old); curMode = 0; /* канонический режим */ } /* завершить процесс */ void die (nsig) { normal(); closeVisual (); /* При завершении программы (в том числе по * сигналу) мы должны восстановить прежние режимы драйвера, * чтобы терминал оказался в корректном состоянии. */ gotoXY (0, LINES - 1); putchar ('\n'); if (nsig) printf ("Пришел сигнал #%d\n", nsig); exit (nsig); } void setsigs () { register ns; /* Перехватывать все сигналы; завершаться по ним. */ /* UNIX имеет 15 стандартных сигналов. */ for (ns = 1; ns <= 15; ns++) signal (ns, die); } А. Богатырев, 1992-95 - 364 - Си в UNIX /* Работа с меню -------------------------------------------- */ struct menu { char *m_text; /* выдаваемая строка */ int m_label; /* помечена ли она ? */ } menuText[] = { /* названия песен Beatles */ { "Across the Universe", 0 } , { "All I've got to do", 0 } , { "All my loving", 0 } , { "All together now", 0 } , { "All You need is love",0 } , { "And I love her", 0 } , { "And your bird can sing", 0 } , { "Another girl", 0 } , { "Any time at all", 0 } , { "Ask me why", 0 } , { NULL, 0 } }; #define Y_TOP 6 int nitems; /* количество строк в меню */ int nselected = 0; /* количество выбранных строк */ char title[] = "ПРОБЕЛ - вниз, ЗАБОЙ - вверх, ESC - выход, \ ENTER - выбрать, TAB - отменить"; # define TIMELINE 1 void main (ac, av) char **av; { char **line; register i; int c; int n; /* текущая строка */ extern char readkey (); /* forward */ extern char *ttyname (); /* имя терминала */ char *mytty; extern char *getlogin (); /* имя пользователя */ char *userName = getlogin (); srand (getpid () + getuid ()); /* инициализировать * датчик случайных чисел */ /* считаем строки меню */ for (nitems = 0; menuText[nitems].m_text != NULL; nitems++); /* инициализируем терминал */ tinit (); ttinit(); mytty = ttyname(fdtty); openVisual (); А. Богатырев, 1992-95 - 365 - Си в UNIX again: clearScreen (); if (mytty != NULL && userName != NULL) { gotoXY (0, TIMELINE); bold (); printf ("%s", userName); normal (); printf (" at %s (%s)", mytty, tname); } drawTitle ("", Y_TOP - 4); drawTitle (title, Y_TOP - 3); drawTitle ("", Y_TOP - 2); /* рисуем меню */ for (i = 0; i < nitems; i++) { drawItem (i, 20, Y_TOP + i, 0); } /* цикл перемещений по меню */ for (n=0; ; ) { printTime (); /* выдаем текущее время */ drawItem (n, 20, Y_TOP + n, 1); c = getcharacter (); drawItem (n, 20, Y_TOP + n, 0); switch (c) { case ' ': go_down: n++; if (n == nitems) n = 0; break; case '\b': case 0177: n--; if (n < 0) n = nitems - 1; break; case ESC: goto out; case '\t': /* Unselect item */ if (menuText[n].m_label != 0) { menuText[n].m_label = 0; drawItem (n, 20, Y_TOP + n, 0); nselected--; prSelects (); } goto go_down; А. Богатырев, 1992-95 - 366 - Си в UNIX case '\r': /* Select item */ case '\n': bold (); drawTitle (menuText[n].m_text, LINES - 2); /* last but two line */ normal (); if (menuText[n].m_label == 0) { menuText[n].m_label = 1; drawItem (n, 20, Y_TOP + n, 0); nselected++; prSelects (); } goto go_down; default: goto go_down; } } out: clearScreen (); gotoXY (COLS / 3, LINES / 2); bold (); printf ("Нажми любую кнопку."); normal (); /* замусорить экран */ while (!(c = readkey ())) { /* случайные точки */ gotoXY (rand () % (COLS - 1), rand () % LINES); putchar ("@.*"[rand () % 3]); /* выдать символ */ fflush (stdout); } standout (); printf ("Нажата кнопка с кодом 0%o\n", c & 0377); standend (); if (c == ESC) { sleep (2); /* подождать 2 секунды */ goto again; } die (0); /* успешно завершиться, * восстановив режимы драйвера */ } А. Богатырев, 1992-95 - 367 - Си в UNIX /* Нарисовать строку меню номер i * в координатах (x,y) с или без выделения */ void drawItem (i, x, y, out) { gotoXY (x, y); if (out) { standout (); bold (); } printf ("%c %s ", menuText[i].m_label ? '-' : ' ', /* помечено или нет */ menuText[i].m_text /* сама строка */ ); if (out) { standend (); normal (); } } /* нарисовать центрированную строку в инверсном изображении */ void drawTitle (title, y) char *title; { register int n; int length = strlen (title); /* длина строки */ gotoXY (0, y); /* clearEOL(); */ standout (); for (n = 0; n < (COLS - length) / 2; n++) putchar (' '); printf ("%s", title); n += length; /* дорисовать инверсией до конца экрана */ for (; n < COLS - 1; n++) putchar (' '); standend (); } /* выдать общее число выбранных строк */ void prSelects () { char buffer[30]; if (nselected == 0) { gotoXY (0, LINES - 1); clearEOL (); } else { sprintf (buffer, "Выбрано: %d/%d", nselected, nitems); drawTitle (buffer, LINES - 1); } } А. Богатырев, 1992-95 - 368 - Си в UNIX /* Работа с будильником -------------------------- */ #define PAUSE 4 int alarmed; /* флаг будильника */ /* реакция на сигнал "будильник" */ void onalarm (nsig) { alarmed = 1; } /* Прочесть символ с клавиатуры, но не позже чем через PAUSE секунд. * иначе вернуть код 'пробел'. */ int getcharacter () { int c; fflush(stdout); /* заказать реакцию на будильник */ signal (SIGALRM, onalarm); alarmed = 0; /* сбросить флаг */ /* заказать сигнал "будильник" через PAUSE секунд */ alarm (PAUSE); /* ждать нажатия кнопки. * Этот оператор завершится либо при нажатии кнопки, * либо при получении сигнала. */ c = getchar (); /* проверяем флаг */ if (!alarmed) { /* был нажат символ */ alarm (0); /* отменить заказ будильника */ return c; } /* был получен сигнал "будильник" */ return ' '; /* продвинуть выбранную строку вниз */ } /* ---- NDELAY read ----------------------------- */ /* Вернуть 0 если на клавиатуре ничего не нажато, * иначе вернуть нажатую кнопку */ char readkey () { char c; int nread; nread = read (fdtty, &c, 1); /* обычный read() дожидался бы нажатия кнопки. * O_NDELAY позволяет не ждать, но вернуть "прочитано 0 символов". */ return (nread == 0) ? 0 : c; } А. Богатырев, 1992-95 - 369 - Си в UNIX /* -------- Работа со временем ------------------------ */ void printTime () { time_t t; /* текущее время */ struct tm *tm; extern struct tm *localtime (); char tmbuf[30]; static char *week[7] = { "Вс", "Пн", "Вт", "Ср", "Чт", "Пт", "Сб" }; static char *month[12] = { "Янв", "Фев", "Мар", "Апр", "Май", "Июн", "Июл", "Авг", "Сен", "Окт", "Ноя", "Дек" }; time (&t); /* узнать текущее время */ tm = localtime (&t); /* разложить его на компоненты */ sprintf (tmbuf, "%2s %02d:%02d:%02d %02d-%3s-%d", week[tm -> tm_wday], /* день недели (0..6) */ tm -> tm_hour, /* часы (0..23) */ tm -> tm_min , /* минуты (0..59) */ tm -> tm_sec , /* секунды (0..59) */ tm -> tm_mday, /* число месяца (1..31) */ month[tm -> tm_mon], /* месяц (0..11) */ tm -> tm_year + 1900 /* год */ ); gotoXY (COLS / 2, TIMELINE); clearEOL (); gotoXY (COLS - strlen (tmbuf) - 1, TIMELINE); bold (); printf ("%s", tmbuf); normal (); } 8.14. Напишите программу, выдающую файл на экран порциями по 20 строк и ожидающую нажатия клавиши. Усложнения: a) добавить клавишу для возврата к началу файла. b) используя библиотеку termcap, очищать экран перед выдачей очередной порции текста. c) напишите эту программу, используя библиотеку curses. d) используя curses, напишите программу параллельного просмотра 2-х файлов в 2-х неперекрывающихся окнах. e) то же в перекрывающихся окнах. 8.15. Напишите функции включения и выключения режима эхо-отображения набираемых на клавиатуре символов (ECHO). 8.16. То же про "режим немедленного ввода" (CBREAK). В обычном режиме строка, наб- ранная на клавиатуре, сначала попадает в некоторый буфер в драйвере терминала|-. ____________________ |- Такие буфера носят название "character lists" - clist. Существуют "сырой" (raw) clist, в который попадают ВСЕ символы, вводимые с клавиатуры; и "канонический" clist, в котором хранится отредактированная строка - обработаны забой, отмена строки. Сами специальные символы (редактирования и генерации сигналов) в каноническую очередь не попадают (в режиме ICANON). А. Богатырев, 1992-95 - 370 - Си в UNIX "Сырая" "Каноническая" клавиатура--->ОчередьВвода--*-->ОчередьВвода-->read | файл-устройство

dev


| экран<---ОчередьВывода---<--*--<-----------<---write Этот буфер используется для предчтения - вы можете набирать текст на клавиатуре еще

до


и при поступлении запроса будет выдан из буфера. Также, в каноническом режиме ICANON, буфер ввода используется для редактирования введенной строки: забой отменяет послед- ний набранный символ, CTRL/U отменяет всю набранную строку; а также он используется для выполнения некоторых преобразований символов на вводе и выводе|=. Введенная строка попадает в программу (которая запросила данные с клавиатуры при помощи read, gets, putchar) только после того, как вы нажмете кнопку <ENTER> с кодом '\n'. До этого вводимые символы накапливаются в буфере, но в программу не передаются - программа тем временем "спит" в вызове read. Как только будет нажат символ '\n', он сам поступит в буфер, а программа будет разбужена и сможет наконец прочесть из буфера ввода набранный текст. Для меню, редакторов и других "экранных" программ этот режим неудобен: пришлось

немедленно


в вашу программу (без ожидания нажатия '\n'). В данном случае буфер драйвера исполь- зуется только для предчтения, но не для редактирования вводимого текста. Редактиро- вание возлагается на вас - предусмотрите его в своей программе сами! Заметьте, что код кнопки <ENTER> ("конец ввода") - '\n' - не только "проталки- вает" текст в программу, но и сам попадает в буфер драйвера, а затем в вашу прог- рамму. Не забывайте его как-то обрабатывать. В MS DOS функция чтения кнопки в режиме ~ECHO+CBREAK называется getch(). В UNIX аналогично ей будет работать обычный getchar(), если перед его использованием устано- вить нужные режимы драйвера tty вызовом ioctl. По окончании программы режим драйвера надо восстановить (за вас это никто не сделает). Также следует восстанавливать режим драйвера при аварийном завершении программы (по любому сигналу|-|-). Очереди ввода и вывода используются также для синхронизации скорости работы программы (скажем, скорости наполнения буфера вывода символами, поступающими из прог- раммы через вызовы write) и скорости работы устройства (с которой драйвер выбирает символы с другого конца очереди и выдает их на экран); а также для преобразований символов на вводе и выводе. Пример управления всеми режимами есть в приложении.

Функциональные


колько символов. Например на терминалах, работающих в системе команд стандарта ANSI, кнопки со стрелками посылают такие последовательности:

стрелка вверх


стрелка вниз


стрелка вправо


стрелка влево


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

escape


единственный


большим 0xFF. Склейка последовательностей символов, поступающих от функциональных клавиш, в такой внутренний код - также задача экранной библиотеки (учет системы

вводе


____________________ |= Режимы преобразований, символы редактирования, и.т.п. управляются системным вы- зовом ioctl. Большой пример на эту тему есть в приложении. |-|- Если ваша программа завершилась аварийно и моды терминала остались в "странном" состоянии, то привести терминал в чувство можно командой stty sane А. Богатырев, 1992-95 - 371 - Си в UNIX

одиночный


клавиатуры - его посылает клавиша Esc. Поэтому если мы строим распознаватель клавиш, который при поступлении кода 033 начинает ожидать составную последовательность - мы должны выставлять таймаут, например alarm(1); и если по его истечении больше никаких символов не поступило - выдавать код 033 как код клавиши Esc. Напишите распознаватель кодов, поступающих с клавиатуры. Коды обычных букв выда- вать как есть (0..0377), коды функциональных клавиш выдавать как числа >= 0400. Учтите, что разные типы дисплеев посылают разные последовательности от одних и тех же функциональных клавиш: предусмотрите настройку на систему команд ДАННОГО дисплея при помощи библиотеки termcap. Распознаватель удобно строить при помощи сравнения посту- пающих символов с ветвями дерева (спускаясь по нужной ветви дерева при поступлении очередного символа. Как только достигли листа дерева - возвращаем код, приписанный этому листу): ---> '\033' ---> '[' ---> 'A' --> выдать 0400 | \--> 'B' --> 0401 | \--> 'C' --> 0402 | \--> 'D' --> 0403 \--> 'X' -----------> 0404 ... Нужное дерево стройте при настройке на систему команд данного дисплея. Библиотека curses уже имеет такой встроенный распознаватель. Чтобы составные последовательности склеивались в специальные коды, вы должны установить режим keypad:

c


...

window


...

c


Без этого wgetch() считывает все символы поодиночке. Символические названия кодов

curses


и.т.п. Если вы работаете с единственным окном размером с весь экран, то в качестве

window


curses


# ======================================== Makefile для getch getch: getch.o cc getch.o -o getch -ltermlib getch.o: getch.c getch.h cc -g -DUSG -c getch.c А. Богатырев, 1992-95 - 372 - Си в UNIX /* Разбор составных последовательностей клавиш с клавиатуры. */ /* ================================================== getch.h */ #define FALSE 0 #define TRUE 1 #define BOOLEAN unsigned char #define INPUT_CHANNEL 0 #define OUTPUT_CHANNEL 1 #define KEY_DOWN 0400 #define KEY_UP 0401 #define KEY_LEFT 0402 #define KEY_RIGHT 0403 #define KEY_PGDN 0404 #define KEY_PGUP 0405 #define KEY_HOME 0406 #define KEY_END 0407 #define KEY_BACKSPACE 0410 #define KEY_BACKTAB 0411 #define KEY_DC 0412 #define KEY_IC 0413 #define KEY_DL 0414 #define KEY_IL 0415 #define KEY_F(n) (0416+n) #define ESC ' 33' extern char *tgetstr(); void _put(char c); void _puts(char *s); void keyboard_access_denied(void); char *strdup(const char *s); void keyinit(void); int getc_raw(void); void keyreset(void); int getch(void); int lgetch(BOOLEAN); int ggetch(BOOLEAN); int kgetch(void); void _sigalrm(int n); void init_keytry(void); void add_to_try(char *str, short code); void keypad_on(void); void keypad_off(void); int dotest(void); void tinit(void); void main(void); А. Богатырев, 1992-95 - 373 - Си в UNIX /* ===================================================== getch.c * The source version of getch.c file was * written by Pavel Curtis. * */ #include <stdio.h> #include <signal.h> #include <setjmp.h> #include <termios.h> #include <ctype.h> #include <string.h> #include <locale.h> #include "getch.h" #define keypad_local S[0] #define keypad_xmit S[1] #define key_backspace S[2] #define key_backtab S[3] #define key_left S[4] #define key_right S[5] #define key_up S[6] #define key_down S[7] #define key_ic S[8] #define key_dc S[9] #define key_il S[10] #define key_dl S[11] #define key_f1 S[12] #define key_f2 S[13] #define key_f3 S[14] #define key_f4 S[15] #define key_f5 S[16] #define key_f6 S[17] #define key_f7 S[18] #define key_f8 S[19] #define key_f9 S[20] #define key_f10 S[21] /* f0 */ #define key_f11 S[22] /* f11 */ #define key_f12 S[23] /* f12 */ #define key_home S[24] #define key_end S[25] #define key_npage S[26] #define key_ppage S[27] #define TOTAL 28 А. Богатырев, 1992-95 - 374 - Си в UNIX /* descriptors for keys */ char *KEYS[TOTAL+1] = { "ke", "ks", "kb", "kB", "kl", "kr", "ku", "kd", "kI", "kD", "kA", "kL", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "f0", "f.", "f-", "kh", "kH", "kN", "kP", NULL }, *S[TOTAL];

_


_


static int _backcnt = 0; static char _backbuf[30]; static struct try { struct try *child; struct try *sibling; char ch; short value; } *_keytry; BOOLEAN keypadok = FALSE; struct termios new_modes;

_


char *strdup(const char *s) { return strcpy((char *) malloc(strlen(s)+1), s); } А. Богатырев, 1992-95 - 375 - Си в UNIX /* Инициализация таблицы строк */ void keyinit(){ char *key, nkey[80], *p; register i; keyreset(); for( i=0; i < TOTAL; i++ ){ p = nkey; printf("tgetstr(%s)...", KEYS[i]); key = tgetstr(KEYS[i], &p); if(S[i]) free(S[i]); if(key == NULL){ S[i] = NULL; /* No such key */ printf("клавиша не определена.\n"); }else{ /* Decrypted string */ S[i] = strdup(key); printf("считано.\n"); } } init_keytry(); if( tcgetattr(INPUT_CHANNEL, &new_modes) < 0 ){ keyboard_access_denied(); } /* input flags */ /* отменить преобразование кода '\r' в '\n' на вводе */ new_modes.c_iflag &= ~ICRNL; if ((new_modes.c_cflag & CSIZE) == CS8) /* 8-битный код */ new_modes.c_iflag &= ~ISTRIP; /* отменить & 0177 на вводе */ /* output flags */ /* отменить TAB3 - замену табуляций '\t' на пробелы */ /* отменить ONLCR - замену '\n' на пару '\r\n' на выводе */ new_modes.c_oflag &= ~(TAB3 | ONLCR); /* local flags */ /* выключить режим ICANON, включить CBREAK */ /* выключить эхоотображение набираемых символов */ new_modes.c_lflag &= ~(ICANON | ECHO); /* control chars */ /* при вводе с клавиш ждать не более ... */ new_modes.c_cc[VMIN] = 1; /* 1 символа и */ new_modes.c_cc[VTIME] = 0; /* 0 секунд */ /* Это соответствует режиму CBREAK */ /* Символы, нажатие которых заставляет драйвер терминала послать сигнал * либо отредактировать набранную строку. Значение 0 означает, * что соответствующего символа не будет */ new_modes.c_cc[VINTR] = '\0'; /* символ, генерящий SIGINT */ new_modes.c_cc[VQUIT] = '\0'; /* символ, генерящий SIGQUIT */ new_modes.c_cc[VERASE] = '\0'; /* забой (отмена последнего символа)*/ new_modes.c_cc[VKILL] = '\0'; /* символ отмены строки */ } А. Богатырев, 1992-95 - 376 - Си в UNIX /* Чтение одного символа непосредственно с клавиатуры */

_


int n; char c; n = read(INPUT_CHANNEL, &c, 1); if (n <= 0) return EOF; return (c & 0xFF); } static BOOLEAN _getback = FALSE; static char _backchar = '\0'; /* Чтение символа - либо из буфера (если не пуст), либо с клавиатуры */ #define nextc() (_backcnt > 0 ? _backbuf[--_backcnt] : \ _getback ? _getback = FALSE, _backchar : \ getc_raw()) #define putback(ch) _backbuf[_backcnt++] = ch void keyreset(){ _backcnt = 0; _backchar = '\0'; _getback = FALSE; } /* Функция чтения составного символа */ int getch(){ int c = lgetch(TRUE); keypad_off(); return c; } /* ВНИМАНИЕ! Если в процессе будет получен сигнал, в то время как процесс находится внутри вызова getch(), то системный вызов read() вернет 0 и errno == EINTR. В этом случае getch() вернет '\0'. Чтобы избежать этой ситуации используется функция lgetch() */ int lgetch(BOOLEAN kpad) { int c; while((c = ggetch(kpad)) <= 0); return c; } int ggetch(BOOLEAN kpad) { int kgetch(); if( kpad ) keypad_on(); else keypad_off(); return keypadok ? kgetch() : nextc(); } А. Богатырев, 1992-95 - 377 - Си в UNIX /* ** int kgetch() ** ** Get an input character, but take care of keypad sequences, returning ** an appropriate code when one matches the input. After each character ** is received, set a one-second alarm call. If no more of the sequence ** is received by the time the alarm goes off, pass through the sequence ** gotten so far. ** */ #define CRNL(c) (((c) == '\r') ? '\n' : (c)) /* борьба с русской клавиатурой */ #if !defined(XENIX) || defined(VENIX) # define unify(c) ( (c)&(( (c)&0100 ) ? ~0240 : 0377 )) #else # define unify(c) (c) #endif А. Богатырев, 1992-95 - 378 - Си в UNIX /* ==================================================================== */ #if !defined(XENIX) && !defined(USG) && !defined(M_UNIX) && !defined(unix) /* Для семейства BSD */ static BOOLEAN alarmed; jmp_buf jbuf; int kgetch() { register struct try *ptr; int ch; char buffer[10]; /* Assume no sequences longer than 10 */ register char *bufp = buffer; void (*oldsig)(); void _sigalrm(); ptr = _keytry; oldsig = signal(SIGALRM, _sigalrm); alarmed = FALSE; if( setjmp( jbuf )) /* чтоб свалиться сюда с read-а */ ch = EOF; do { if( alarmed ) break; ch = nextc(); if (ch != EOF) /* getc() returns EOF on error, too */ *(bufp++) = ch; if (alarmed) break; while (ptr != (struct try *)NULL && (ch == EOF || unify(CRNL(ptr->ch)) != unify(CRNL(ch)) )) ptr = ptr->sibling; if (ptr != (struct try *)NULL) { if (ptr->value != 0) { alarm(0); signal(SIGALRM, oldsig); return(ptr->value); } else { ptr = ptr->child; alarm(1); } } } while (ptr != (struct try *)NULL); alarm(0); signal(SIGALRM, oldsig); if (ch == EOF && bufp == buffer) return ch; А. Богатырев, 1992-95 - 379 - Си в UNIX while (--bufp > buffer) putback(*bufp); return(*bufp & 0377); }

_


{ alarmed = TRUE; longjmp(jbuf, 1); } А. Богатырев, 1992-95 - 380 - Си в UNIX /* ==================================================================== */ #else /* XENIX or USG */ /* Для семейства SYSTEM V */ static BOOLEAN alarmed; int kgetch() { register struct try *ptr; int ch; char buffer[10]; /* Assume no sequences longer than 10 */ register char *bufp = buffer; void (*oldsig)(); void _sigalrm(); ptr = _keytry; oldsig = signal(SIGALRM, _sigalrm); alarmed = FALSE; do { ch = nextc(); if (ch != EOF) /* getc() returns EOF on error, too */ *(bufp++) = ch; if (alarmed) break; while (ptr != (struct try *)NULL && (ch == EOF || unify(CRNL(ptr->ch)) != unify(CRNL(ch)) )) ptr = ptr->sibling; if (ptr != (struct try *)NULL) { if (ptr->value != 0) { alarm(0); signal(SIGALRM, oldsig); return(ptr->value); } else { ptr = ptr->child; alarm(1); } } } while (ptr != (struct try *)NULL); alarm(0); signal(SIGALRM, oldsig); if (ch == EOF && bufp == buffer) return ch; while (--bufp > buffer) putback(*bufp); return(*bufp & 0377); } А. Богатырев, 1992-95 - 381 - Си в UNIX

_


{ alarmed = TRUE; signal(SIGALRM, _sigalrm); } #endif /*XENIX*/ /* ==================================================================== */ /* ** init_keytry() ** Построение дерева разбора последовательностей символов. ** */

_


{ _keytry = (struct try *) NULL; add_to_try(key_backspace, KEY_BACKSPACE); add_to_try("\b", KEY_BACKSPACE); add_to_try("\177", KEY_BACKSPACE); add_to_try(key_backtab, KEY_BACKTAB); add_to_try(key_dc, KEY_DC); add_to_try(key_dl, KEY_DL); add_to_try(key_down, KEY_DOWN); add_to_try(key_f1, KEY_F(1)); add_to_try(key_f2, KEY_F(2)); add_to_try(key_f3, KEY_F(3)); add_to_try(key_f4, KEY_F(4)); add_to_try(key_f5, KEY_F(5)); add_to_try(key_f6, KEY_F(6)); add_to_try(key_f7, KEY_F(7)); add_to_try(key_f8, KEY_F(8)); add_to_try(key_f9, KEY_F(9)); add_to_try(key_f10, KEY_F(10)); add_to_try(key_f11, KEY_F(11)); add_to_try(key_f12, KEY_F(12)); add_to_try(key_home, KEY_HOME); add_to_try(key_ic, KEY_IC); add_to_try(key_il, KEY_IL); add_to_try(key_left, KEY_LEFT); add_to_try(key_npage, KEY_PGDN); add_to_try(key_ppage, KEY_PGUP); add_to_try(key_right, KEY_RIGHT); add_to_try(key_up, KEY_UP); add_to_try(key_end, KEY_END); } А. Богатырев, 1992-95 - 382 - Си в UNIX

_


{ static BOOLEAN out_of_memory = FALSE; struct try *ptr, *savedptr; if (str == NULL || out_of_memory) return; if (_keytry != (struct try *) NULL) { ptr = _keytry; for (;;) { while (ptr->ch != *str && ptr->sibling != (struct try *)NULL) ptr = ptr->sibling; if (ptr->ch == *str) { if (*(++str)) { if (ptr->child != (struct try *)NULL) ptr = ptr->child; else break; } else { ptr->value = code; return; } } else { if ((ptr->sibling = (struct try *) malloc(sizeof *ptr)) == (struct try *)NULL) { out_of_memory = TRUE; return; } savedptr = ptr = ptr->sibling; ptr->child = ptr->sibling = (struct try *)NULL; ptr->ch = *str++; ptr->value = 0; break; } } /* end for (;;) */ } else /* _keytry == NULL :: First sequence to be added */ { savedptr = ptr = _keytry = (struct try *) malloc(sizeof *ptr); if (ptr == (struct try *) NULL) { out_of_memory = TRUE; return; } ptr->child = ptr->sibling = (struct try *) NULL; А. Богатырев, 1992-95 - 383 - Си в UNIX ptr->ch = *(str++); ptr->value = 0; } /* at this point, we are adding to the try. ptr->child == NULL */ while (*str) { ptr->child = (struct try *) malloc(sizeof *ptr); ptr = ptr->child; if (ptr == (struct try *)NULL) { out_of_memory = TRUE; ptr = savedptr; while (ptr != (struct try *)NULL) { savedptr = ptr->child; free(ptr); ptr = savedptr; } return; } ptr->child = ptr->sibling = (struct try *)NULL; ptr->ch = *(str++); ptr->value = 0; } ptr->value = code; return; } /* Включение альтернативного режима клавиатуры */

_


if( keypadok ) return; keypadok = TRUE; if( keypad_xmit ) _puts( keypad_xmit ); } /* Включение стандартного режима клавиатуры */

_


if( !keypadok ) return; keypadok = FALSE; if( keypad_local ) _puts( keypad_local ); } А. Богатырев, 1992-95 - 384 - Си в UNIX /* Тестовая функция */ int dotest() { struct termios saved_modes; int c; char *s; char keyname[20]; if( tcgetattr(INPUT_CHANNEL, &saved_modes) < 0 ){ err: keyboard_access_denied(); } if( tcsetattr(INPUT_CHANNEL, TCSADRAIN, &new_modes) < 0 ) goto err; keyreset(); for(;;){ c = getch(); switch(c){ case KEY_DOWN: s = "K_DOWN" ; break; case KEY_UP: s = "K_UP" ; break; case KEY_LEFT: s = "K_LEFT" ; break; case KEY_RIGHT: s = "K_RIGHT" ; break; case KEY_PGDN: s = "K_PGDN" ; break; case KEY_PGUP: s = "K_PGUP" ; break; case KEY_HOME: s = "K_HOME" ; break; case KEY_END: s = "K_END" ; break; case KEY_BACKSPACE: s = "K_BS" ; break; case '\t': s = "K_TAB" ; break; case KEY_BACKTAB: s = "K_BTAB" ; break; case KEY_DC: s = "K_DEL" ; break; case KEY_IC: s = "K_INS" ; break; case KEY_DL: s = "K_DL" ; break; case KEY_IL: s = "K_IL" ; break; case KEY_F(1): s = "K_F1" ; break; case KEY_F(2): s = "K_F2" ; break; case KEY_F(3): s = "K_F3" ; break; case KEY_F(4): s = "K_F4" ; break; case KEY_F(5): s = "K_F5" ; break; case KEY_F(6): s = "K_F6" ; break; case KEY_F(7): s = "K_F7" ; break; case KEY_F(8): s = "K_F8" ; break; case KEY_F(9): s = "K_F9" ; break; case KEY_F(10): s = "K_F10" ; break; case KEY_F(11): s = "K_F11" ; break; case KEY_F(12): s = "K_F12" ; break; case ESC: s = "ESC" ; break; case EOF: s = "K_EOF" ; break; case '\r': s = "K_RETURN"; break; case '\n': s = "K_ENTER" ; break; default: s = keyname; if( c >= 0400 ){ sprintf(keyname, "K_F%d", c - KEY_F(0)); } else if( iscntrl(c)){ sprintf(keyname, "CTRL(%c)", c + 'A' - 1); } else { sprintf(keyname, "%c", c ); А. Богатырев, 1992-95 - 385 - Си в UNIX } } printf("Клавиша: %s\n\r", s); if(c == ESC) break; } tcsetattr(INPUT_CHANNEL, TCSADRAIN, &saved_modes); } /* Функция настройки на систему команд дисплея */ void tinit (void) { /* static */ char Tbuf[2048]; /* Tbuf должен сохраняться все время, пока могут вызываться функции tgetstr(). * Для этого он либо должен быть static, либо вызов функции keyinit() * должен находиться внутри tinit(), что и сделано. */ char *tname; extern char *getenv(); if((tname = getenv("TERM")) == NULL){ printf("TERM не определено: неизвестный тип терминала.\n"); exit(2); } printf("Терминал: %s\n", tname); /* Прочесть описание терминала в Tbuf */ switch (tgetent(Tbuf, tname)) { case -1: printf ("Нет файла TERMCAP (/etc/termcap).\n"); exit (1); case 0: printf ("Терминал '%s' не описан.\n", tname); exit (2); case 1: break; /* OK */ } if(strlen(Tbuf) >= 1024) printf("Описание терминала слишком длинное - возможны потери в конце описания\n"); keyinit(); /* инициализировать строки, пока Tbuf[] доступен */ } void main(void){ setlocale(LC_ALL, ""); tinit(); /* keyinit(); */ dotest(); exit(0); } По поводу этого алгоритма надо сказать еще пару слов. Его модификация может с успехом применяться для поиска слов в таблице (команд, ключей в базе данных, итп.): список слов превращается в дерево. В таком поисковом алгоритме не требуются таймауты, необ- ходимые при вводе с клавиатуры, поскольку есть явные терминаторы строк - символы '\0', которых нет при вводе с клавиатуры. В чем эффективность такого алгоритма? Сравним последовательный перебор при помощи strcmp и поиск в дереве букв: А. Богатырев, 1992-95 - 386 - Си в UNIX "zzzzzzzzzza" "zzzzzzzzzzb" "zzzzzzzzzzbx" "zzzzzzzzzzc" "zzzzzzzzzzcx" Для линейного перебора (даже в отсортированном массиве) поиск строки zzzzzzzzzzcx потребует zzzzzzzzzza | 11 сравнений, отказ zzzzzzzzzzb | 11 сравнений, отказ zzzzzzzzzzbx | 12 сравнений, отказ zzzzzzzzzzc | 11 сравнений, отказ zzzzzzzzzzcx V 12 сравнений, успех Всего: 57 шагов. Для поиска в дереве: __z__z__z__z__z__z__z__z__z__z__a__\0 |_b__\0 | |_x__\0 | |_c__\0 |_x__\0 потребуется проход вправо (вниз) на 10 шагов, потом выбор среди 'a','b','c', потом - выбор среди '\0' и 'x'. Всего: 15 шагов. За счет того, что общий "корень" проходится ровно один раз, а не каждый раз заново. Но это и требует предварительной подготовки данных: превращения строк в дерево! 8.18. Напишите функцию для "экранного" редактирования вводимой строки в режиме CBREAK. Напишите аналогичную функцию на curses-е. В curses-ной версии надо уметь отрабатывать: забой (удаление символа перед курсором), отмену всей строки, смещение влево/вправо по строке, удаление символа над курсором, вставку пробела над курсором, замену символа, вставку символа, перерисовку экрана. Учтите, что параллельно с изме- нением картинки в окне, вы должны вносить изменения в некоторый массив (строку), которая и будет содержать результат. Эта строка должна быть аргументом функции редак- тирования. Забой можно упрощенно эмулировать как addstr( "\b \b" ); или addch( '\b' ); delch();

x


Исправьте это! 8.19. На curses-е напишите функцию редактирования текста в окне. Функция должна возвращать массив строк с обрезанными концевыми пробелами. Вариант: возвращать одну строку, в которой строки окна разделяются символами '\n'.

x1


используйте алгоритм Брезенхема (минимального отклонения). Ответ: пусть функция

x


void line(int x1, int y1, int x2, int y2, int color){ int dx, dy, i1, i2, i, kx, ky; register int d; /* "отклонение" */ register int x, y; short /* boolean */ l; А. Богатырев, 1992-95 - 387 - Си в UNIX dy = y2 - y1; dx = x2 - x1; if( !dx && !dy ){ putpixel(x1,y1, color); return; } kx = 1; /* шаг по x */ ky = 1; /* шаг по y */ /* Выбор тактовой оси */ if( dx < 0 ){ dx = -dx; kx = -1; } /* Y */ else if( dx == 0 ) kx = 0; /* X */ if( dy < 0 ){ dy = -dy; ky = -1; } if( dx < dy ){ l = 0; d = dx; dx = dy; dy = d; } else l = 1; i1 = dy + dy; d = i1 - dx; i2 = d - dx; x = x1; y = y1; for( i=0; i < dx; i++ ){ putpixel( x, y, color ); if( l ) x += kx; /* шаг по такт. оси */ else y += ky; if( d < 0 ) /* горизонтальный шаг */ d += i1; else{ /* диагональный шаг */ d += i2; if( l ) y += ky; /* прирост высоты */ else x += kx; } } putpixel(x, y, color); /* последняя точка */ }

x


пи


чтобы график выглядел непрерывным; не забывайте приводить double к int, т.к. коорди- наты пикселов|- - целые числа.

count


x


байт 0 | байт 1

биты в байте


x


========================== x=2, count=11 Такой алгоритм используется в растровой машинной графике для рисования горизонтальных прямых линий (тогда массив - это видеопамять компьютера, каждый бит соответствует пикселу на экране).

pattern


void horizLine(char *addr,int x,int count,char pattern){ static char masks[8] = { 0xFF, 0x7F, 0x3F, 0x1F, 0x0F, 0x07, 0x03, 0x01 }; /* индекс в этом массиве равен числу 0-битов слева */ ____________________

picture element


ране. А. Богатырев, 1992-95 - 388 - Си в UNIX register i; char mask; short lbits, rbits; /* число битов слева и справа */ short onebyte; /* единственный байт ? */ addr += x/8; /* в байте 8 бит */ mask = masks[ lbits = x & 7 ]; /* x % 8 */ if( count >= (rbits = 8 - lbits)){ count -= rbits; onebyte = 0; }else{ mask &= ~masks[ lbits = (x+count) & 7 ]; onebyte = 1; } /* Первый байт */ *addr = (*addr & ~mask) | (pattern & mask); addr++; /* Для pattern==0xFF можно просто * *addr++ |= mask; * поскольку (a &~m)|(0xFF & m) = (a &~m) | m = * (a|m) & (~m|m) = (a|m) & 0xFF = a | m * Почему здесь нельзя написать *addr++ = (*addr...) ? * Потому, что ++ может быть сделан ДО вычисления * правой части присваивания! */ if(onebyte) return; /* Средние байты */ for(i = count/8; i > 0; --i) *addr++ = pattern; /* mask==0xFF */ /* Последний байт */ if((lbits = count & 7) == 0) return; /* последний байт был полным */ mask = ~masks[lbits]; *addr = (*addr & ~mask) | (pattern & mask); } Заметим, что для быстродействия подобные алгоритмы обычно пишутся на ассемблере. 8.23. Напишите при помощи curses-а "электронные часы", отображающие текущее время большими цифрами (например, размером 8x8 обычных символов) каждые 5 секунд. Исполь- зуйте alarm(), pause(). 8.24. Составьте программу, реализующую простой диалоговый интерфейс, основанный на меню. Меню хранятся в текстовых файлах вида: А. Богатырев, 1992-95 - 389 - Си в UNIX

menu2


----------------------------------------------- ЗАГОЛОВОК_МЕНЮ +команда_выполняемая_при_входе_в_меню -команда_выполняемая_при_выходе_из_меню альтернатива_1 команда1_1 команда1_2 альтернатива_2 команда2_1 команда2_2 #комментарий команда2_3 альтернатива_3 >menu2_2 #это переход в другое меню альтернатива_4

menu3


... ... ----------------------------------------------- Программа должна обеспечивать: возврат к предыдущему меню по клавише Esc (для этого следует хранить "историю" вызовов меню друг из друга, например в виде "полного имени меню":

rootmenu


menuI


ESC, выдачу подсказки по F1, выдачу полного имени меню по F2. Вызов меню при помощи

замещение


функции


выбора в новом меню и выполнения нужных действий автоматически должно быть выдано то меню, из которого произошел вызов (такой вызов соответствует удлинению полного имени, а возврат из вызова - отсечению последней компоненты). Этот вызов может быть показан на экране как появление нового "выскакивающего" окна поверх окна с предыдущим меню (окно возникает чуть сдвинутым - скажем, на y=1 и x=-2), а возврат - как исчезновение этого окна. Заголовок меню должен высвечиваться в верхней строке меню: |------------------- |--ЗАГОЛОВОК_МЕНЮ---- | | альтернатива_1 | | | альтернатива_2 | | | *альтернатива_3 | | | альтернатива_4 |-- ---------------------

альтернативе


команда


команда


альтернативы


первой букве при помощи нажатия кнопки с этой буквой (в любом регистре): Compile Edit Run program 8.25. Напишите на curses-е функцию, реализующую выбор в меню - прямоугольной таб- лице: А. Богатырев, 1992-95 - 390 - Си в UNIX слово1 слово4 слово7 слово2 *слово5 слово8 слово3 слово6 Строки - элементы меню - передаются в функцию выбора в виде массива строк. Число элементов меню заранее неизвестно и должно подсчитываться внутри функции. Учтите, что все строки могут не поместиться в таблице, поэтому надо предусмотреть "прокручи- вание" строк через таблицу при достижении края меню (т.е. таблица служит как бы "окошком" через которое мы обозреваем таблицу большего размера, возможно перемещая окно над ней). Предусмотрите также случай, когда таблица оказывается заполненной не полностью (как на рисунке). 8.26. Используя библиотеку curses, напишите программу, реализующую клеточный автомат Конвея "Жизнь". Правила: есть прямоугольное поле (вообще говоря бесконечное, но при- нято в конечной модели замыкать края в кольцо), в котором живут "клетки" некоторого организма. Каждая имеет 8 соседних полей. Следующее поколение "клеток" образуется по таким правилам: - если "клетка" имеет 2 или 3 соседей - она выживает. - если "клетка" имеет меньше 2 или больше 3 соседей - она погибает. - в пустом поле, имеющем ровно 3х живых соседей, рождается новая "клетка". Предусмотрите: редактирование поля, случайное заполнение поля, останов при смерти всех "клеток", останов при стабилизации колонии. 8.27. При помощи curses-а напишите экранный редактор кодов доступа к файлу (в форме

rwxrwxrwx


лов, изображая имена файлов и коды доступа в виде таблицы: НАЗВАНИЕ КОДЫ ДОСТУПА файл1 rwxrw-r-- файл2 rw-r-xr-x файл3 rwxrwxr-- Имена файлов задавайте как аргументы для main(). Указание: используйте для получения текущих кодов доступа системный вызов stat(), а для их изменения - системный вызов chmod(). А. Богатырев, 1992-95 - 391 - Си в UNIX

* 9. Приложения. *


9.1. Таблица приоритетов операций языка C++


Операции, расположенные выше, имеют больший приоритет. Операторы Ассоциативность -------------------------------------------------- 1. () [] -> :: . Left to right 2. ! ~ + - ++ -- & * (typecast) sizeof new delete Right to left 3. .* ->* Left to right 4. * / % Left to right 5. + - Left to right 6. << >> Left to right 7. < <= > >= Left to right 8. == != Left to right 9. & Left to right 10. ^ Left to right 11. | Left to right 12. && Left to right 13. || Left to right 14. ?: (условное выражение) Right to left 15. = *= /= %= += -= &= ^= |= <<= >>= Right to left 16. , Left to right Здесь "*" и "&" в строке 2 - это адресные операции; в строке 2 "+" и "-" - унарные; "&" в строке 9 - это побитное "и"; "(typecast)" - приведение типа; "new" и "delete" - операторы управления памятью в C++. Ассоциативность Left to right (слева направо) означает группировку операторов таким образом: A1 @ A2 @ A3 это ((A1 @ A2) @ A3) Ассоциативность Rigth to left (справа налево) это A1 @ A2 @ A3 это (A1 @ (A2 @ A3))

9.2. Правила преобразований типов.


9.2.1. В выражениях. 1. Если операнд имеет тип не int и не double, то сначала приводится: signed char --> int расширением знакового бита (7) unsigned char --> int дополнением нулями слева short --> int расширением знакового бита (15) unsigned short --> unsigned int дополнением нулями слева enum --> int порядковый номер в перечислимом типе float --> double дробная часть дополняется нулями 2. Если любой операнд имеет тип double, то и другой операнд приводится к типу dou- ble. Результат: типа double. Запишем все дальнейшие преобразования в виде схемы: А. Богатырев, 1992-95 - 392 - Си в UNIX если есть то другой результат операнд типа приводится к типу имеет тип if(double) -->double double else if(unsigned long) -->unsigned long unsigned long else if(long) -->long long else if(unsigned int) -->unsigned int unsigned int else оба операнда имеют тип int int При вызове функций их аргументы - тоже выражения, поэтому в них приводятся char,short к int и float к double. Это говорит о том, что аргументы (формальные параметры) функ- ций можно всегда объявлять как int и double вместо char,short и float соответственно. Зато спецификатор unsigned является существенным. 9.2.2. В присваиваниях. op = expr;

expr


более "длинного" типа к более "короткому" при помощи усечения, вроде: int --> char обрубается старший байт. long --> int обрубается старшее слово. float --> int отброс дробной части double --> int и обрубание мантиссы, если не лезет. double --> float округление дробной части. Вот еще некоторые приведения типов: signed --> unsigned виртуально (просто знаковый бит unsigned --> signed считается значащим или наоборот). unsigned int --> long добавление нулей слева. int --> long расширение знакового бита. float --> int преобразование внутреннего int --> float представления: машинно зависимо. Некоторые преобразования могут идти в несколько стадий, например: char --> long это char --> int --> long char --> unsigned long это char --> int --> unsigned long

9.3. Таблица шестнадцатеричных чисел (HEX).


%d %o %X побитно -------------------------------- 0 0 0x0 0000 1 1 0x1 0001 2 2 0x2 0010 3 3 0x3 0011 4 4 0x4 0100 5 5 0x5 0101 6 6 0x6 0110 7 7 0x7 0111 А. Богатырев, 1992-95 - 393 - Си в UNIX -------------------------------- 8 010 0x8 1000 9 011 0x9 1001 10 012 0xA 1010 11 013 0xB 1011 12 014 0xC 1100 13 015 0xD 1101 14 016 0xE 1110 15 017 0xF 1111 16 020 0x10 10000

9.4. Таблица степеней двойки.


n 2**n | n 2**n --------------|--------------- 0 1 | 8 256 1 2 | 9 512 2 4 | 10 1024 3 8 | 11 2048 4 16 | 12 4096 5 32 | 13 8192 6 64 | 14 16384 7 128 | 15 32768 | 16 65536

9.5. Двоичный код: внутреннее представление целых чисел.


двоичного


кода


i


1. 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | 0| 0| 0| 0| 1| 0| 1| 1| 0| 1| 1| 0| 1| 1| 0| 0| +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ Тогда unsigned число, записанное в слове, равно d = 2**15 * b[15] + 2**14 * b[14] + ... 2**1 * b[1] + b[0];

n


чисел биты складываются по правилам: 0 + 0 = 0 0 + 1 = 1 1 + 0 = 1 1 + 1 = 0 и перенос 1 в разряд слева

b


положительно или равно нулю, 1 - отрицательно. Отрицательные числа хранятся в виде

дополнительного


-a = ~a + 1 Например: А. Богатырев, 1992-95 - 394 - Си в UNIX 2 = 0000000000000010 ~2 = 1111111111111101 ~2+1 = 1111111111111110 = -2 -1 = 1111111111111111 -2 = 1111111111111110 -3 = 1111111111111101 -4 = 1111111111111100 -5 = 1111111111111011 Такое представление выбрано исходя из правила a + (-a) = 0 знак| 2 = 0|000000000000010 сложим их -2 = 1|111111111111110 ---------|--------------- сумма: 10|000000000000000 Как видим, произошел перенос 1 в бит номер 16. Но слово содержит лишь биты 0..15 и

b


0000000000000000 = 0 что и требовалось. В двоичном коде вычитание реализуется по схеме a - b = a + (-b) = a + (~b + 1) Восьмеричные числа соответствуют разбиению двоичного числа на группы по 3 бита и записи каждой группы в виде соответствующей восьмеричной цифры (смотри таблицу выше).

nibble


x = 0010011111011001 число: 0010 0111 1101 1001 16-ричное: 0x 2 7 D 9 = 0x27D9 число: 0 010 011 111 011 001 8-ричное: 0 0 2 3 7 3 1 = 023731

10. Примеры.


В данном приложении приводится несколько содержательных и достаточно больших примеров, которые иллюстрируют как сам язык Си, так и некоторые возможности системы UNIX, а также некоторые программистские приемы решения задач при помощи Си. Многие из этих примеров содержат в качестве своих частей ответы на некоторые из задач. Некоторые примеры позаимствованы из других книг, но дополнены и исправлены. Все при- меры проверены в действии. Смысл некоторых функций в примерах может оказаться вам неизвестен; однако в силу того, что данная книга не является учебником, мы отсылаем вас за подробностями к "Оперативному руководству" (man) по операционной системе UNIX и к документации по системе. И в заключение - несколько слов о путях развития языка "C". Чистый язык "C" уже отстал от современных технологий программирования. Такие методы как модули (языки "Modula-2", "Ada", "CLU"), родовые пакеты ("Ada", "CLU"), объектно-ориентированное программирование ("Smalltalk", "CLU") требуют новых средств. Поэтому в настоящее время "C" постепенно вытесняется более мощным и интеллектуальным языком "C++" |-, обладающим средствами для объектно-ориентированного программирования и родовых ____________________ |- C++ как и C разработан в AT&T; произносится "Си плас-плас" А. Богатырев, 1992-95 - 395 - Си в UNIX классов. Существуют также расширения стандартного "C" объектно-ориентированными воз- можностями ("Objective-C"). Большой простор предоставляет также сборка программ из частей, написанных на разных языках программирования (например, "C", "Pascal", "Pro- log"). ____________________ |=|=|= Автор благодарит авторов программ и книг по Си и UNIX, по которым некогда учился я сам; коллег из ИПК Минавтопрома/Демоса; программистов из сетей Usenet и Rel- com, давших материалы для задач и рассуждений; слушателей курсов по Си за многочис- ленный материал для книги. А.Богатырев. Ученью не один мы посвятили год, Потом других учить пришел и нам черед. Какие ж выводы из этой всей науки? Из праха мы пришли, нас ветер унесет. Омар Хайям Оглавление. 0. Напутствие в качестве вступления. .......................................... 1 1. Простые программы и алгоритмы. Сюрпризы, советы. ........................... 3 2. Массивы, строки, указатели. ................................................ 81 3. Мобильность и машинная зависимость программ. Проблемы с русскими буквами. ........................................................................... 122 4. Работа с файлами. .......................................................... 137 5. Структуры данных. .......................................................... 172 6. Системные вызовы и взаимодействие с UNIX. .................................. 186 6.1. Файлы и каталоги. ........................................................ 189 6.2. Время в UNIX. ............................................................ 198 6.3. Свободное место на диске. ................................................ 207 6.4. Сигналы. ................................................................. 212 6.5. Жизнь процессов. ......................................................... 219 6.6. Трубы и FIFO-файлы. ...................................................... 230 6.7. Нелокальный переход. ..................................................... 233 6.8. Хозяин файла, процесса, и проверка привелегий. ........................... 235 6.9. Блокировка доступа к файлам. ............................................. 240 6.10. Файлы устройств. ........................................................ 244 6.11. Мультиплексирование ввода-вывода ......................................... 259 6.12. Простой интерпретатор команд. ........................................... 271 7. Текстовая обработка. ....................................................... 283 8. Экранные библиотеки и работа с видеопамятью. ............................... 348 9. Приложения. ................................................................ 391 9.1. Таблица приоритетов операций языка C++ .................................... 391 9.2. Правила преобразований типов. ............................................ 391 9.3. Таблица шестнадцатеричных чисел (HEX). ................................... 392 9.4. Таблица степеней двойки. ................................................. 393 9.5. Двоичный код: внутреннее представление целых чисел. ...................... 393 10. Примеры. .................................................................. 394 Пример 1. Размен монет. ...................................................... Пример 2. Подсчет букв в файле. .............................................. Пример 3. Центрирование строк. ............................................... Пример 4. Разметка текста для nroff. ......................................... Пример 5. Инвертирование порядка слов в строках. ............................. Пример 6. Пузырьковая сортировка. ............................................ Пример 7. Хэш-таблица. ....................................................... Пример 8. Простая база данных. ............................................... Пример 9. Вставка/удаление строк в файл. ..................................... Пример 10. Безопасный free, позволяющий обращения к автоматическим перемен- ным. ..................................................................... Пример 11. Поимка ошибок при работе с динамической памятью. .................. Пример 12. Копирование/перемещение файла. .................................... Пример 13. Обход поддерева каталогов в MS DOS при помощи chdir. .............. Пример 14. Работа с сигналами. ............................................... Пример 15. Управление скоростью обмена через линию. .......................... Пример 16. Просмотр файлов в окнах. .......................................... Пример 17. Работа с иерархией окон в curses. Часть проекта uxcom. ............ Пример 18. Поддержка содержимого каталога. Часть проекта uxcom. .............. Пример 19. Роллируемое меню. Часть проекта uxcom. ............................ Пример 20. Выбор в строке-меню. Часть проекта uxcom. ......................... Пример 21. Редактор строки. Часть проекта uxcom. ............................. Пример 22. Выбор в прямоугольной таблице. Часть проекта uxcom. ............... Пример 23. UNIX commander - простой визуальный Шелл. Головной модуль проекта uxcom. ................................................................... Пример 24. Общение двух процессов через "трубу". ............................. Пример 25. Общение процессов через FIFO-файл. ................................ Пример 26. Общение процессов через общую память и семафоры. .................. Пример 27. Протоколирование работы программы при помощи псевдотерминала и процессов. ............................................................... Пример 28. Оценка фрагментированности файловой системы. ...................... Пример 29. Восстановление удаленного файла в BSD-2.9. ........................ Пример 30. Копирование файлов из MS DOS в UNIX. .............................. Пример 31. Программа, печатающая свой собственный текст. ..................... Пример 32. Форматирование текста Си-программы. ............................... 1.11. Треугольник из звездочек. ............................................... 6 1.34. Простые числа. .......................................................... 10 1.36. Целочисленный квадратный корень. ........................................ 12 1.39. Вычисление интеграла по Симпсону. ....................................... 14 1.49. Сортировка Шелла. ....................................................... 20 1.50. Быстрая сортировка. ..................................................... 21 1.67. Функция чтения строки. .................................................. 28 1.88. Перестановки элементов. ................................................. 38 1.117. Схема Горнера. ......................................................... 58 1.137. Системная функция qsort - формат вызова. ............................... 67 1.146. Процесс компиляции программ. ........................................... 76 2.58. Функция bcopy. .......................................................... 108 2.59. Функция strdup. ......................................................... 111 2.61. Упрощенный аналог функции printf. ....................................... 112

_


3.12. Программа транслитерации: tr. ........................................... 129 3.16. Функция записи трассировки (отладочных выдач) в файл. ................... 132 3.18. Условная компиляция: #ifdef .............................................. 132 4.39. Быстрый доступ к строкам файла. ......................................... 161 4.45. Эмуляция основ библиотеки STDIO, по мотивам 4.2 BSD. .................... 165 5.12. Отсортированный список слов. ............................................ 180 5.16. Структуры с полями переменного размера. ................................. 183 5.17. Список со "старением". .................................................. 184 6.1.1. Определение типа файла. ................................................ 189 6.1.3. Выдача неотсортированного содержимого каталога (ls). ................... 191 6.1.5. Рекурсивный обход каталогов и подкаталогов. ............................ 192 6.2.9. Функция задержки в микросекундах. ...................................... 201 6.4.3. Функция sleep. ......................................................... 217 6.10.1. Определение текущего каталога: функция getwd. ......................... 252 6.10.2. Канонизация полного имени файла. ...................................... 257 6.11.1. Мультиплексирование ввода из нескольких файлов. ....................... 259 6.11.2. Программа script. ..................................................... 261 7.12. Программа uniq. ......................................................... 285 7.14. Расширение табуляций в пробелы, функция untab. .......................... 285 7.15. Функция tabify. ......................................................... 285 7.25. Поиск методом половинного деления. ...................................... 288 7.31. Программа печати в две полосы. .......................................... 292 7.33. Инвертирование порядка строк в файле. ................................... 296 7.34. Перенос неразбиваемых блоков текста. .................................... 298 7.36. Двоичная сортировка строк при помощи дерева. ............................ 300 7.41. Функция match. .......................................................... 309 7.43. Функция контекстной замены по регулярному выражению. .................... 313 7.44. Алгоритм быстрого поиска подстроки в строке. ............................ 316 7.52. Коррекция правописания. ................................................. 321 7.67. Калькулятор-1. .......................................................... 330 7.68. Калькулятор-2. .......................................................... 336 8.1. Осыпающиеся буквы. ....................................................... 350 8.13. Использование библиотеки termcap. ....................................... 359 8.17. Разбор ESC-последовательностей с клавиатуры. ............................ 371

11. Список литературы.


Язык программирования Си


М.: Финансы и статистика, 1985.

Язык Си


1988.

Язык программирования Си


Введение в программирование на языке Си


связь, 1986.

ОС UNIX и программирование на языке Си


связь, 1989.

Язык Си


тистика, 1988.

Инструментальная мобильная опе-


рационная система ИНМОС


Введение в операционную систему UNIX


1985.

Руководство по операционной системе UNIX


1986.

Введение в операционную систему UNIX


связь, 1986.

Операционная система UNIX


Введение в операционную систему UNIX


The design of the UNIX operating system


Cliffs, N.J., 1986.

Programming in C


The annotated C


1990. /* Пример 1 */ /* Задача о размене монеты: * Поиск всех возможных коэффициентов a0 .. an разложения числа S * в виде * S = a0 * c0 + a1 * c1 + ... + an * cn * где веса c0 .. cn заданы заранее и упорядочены. * Веса и коэффициенты неотрицательны (ai >= 0, ci >= 0). */ #include <stdio.h> /* Достоинства разменных монет (веса ci) */ int cost[] = { 1, 2, 3, 5, 10, 15, 20, 50, 100, 300, 500 /* копеек */ }; #define N (sizeof cost / sizeof(int)) int count[ N ]; /* число монет данного типа (коэффициенты ai) */ long nvar; /* число вариантов */ main( ac, av ) char *av[]; { int coin; if( ac == 1 ){ fprintf( stderr, "Укажите, какую монету разменивать: %s число\n", av[0] ); exit(1); } coin = atoi( av[1] ); printf( " Таблица разменов монеты %d коп.\n", coin ); printf( " Каждый столбец содержит количество монет указанного достоинства.\n" ); printf( "-------------------------------------------------------------------\n" ); printf( "| 5р. | 3р. | 1р. | 50к.| 20к.| 15к.| 10к.| 5к.| 3к.| 2к.| 1к.|\n" ); printf( "-------------------------------------------------------------------\n" ); change( N-1, coin ); printf( "-------------------------------------------------------------------\n" ); printf( "Всего %ld вариантов\n", nvar ); } /* рекурсивный размен */ change( maxcoin, sum ) int sum; /* монета, которую меняем */ int maxcoin; /* индекс по массиву cost[] монеты максимального * достоинства, допустимой в данном размене. */ { register i; if( sum == 0 ){ /* вся сумма разменяна */ /* распечатать очередной вариант */ putchar( '|' ); for( i = N-1 ; i >= 0 ; i-- ) if( count[i] ) printf(" %3d |", count[ i ] ); else printf(" |" ); putchar( '\n' ); nvar++; return; } if( sum >= cost [ maxcoin ] ){ /* если можно выдать монету достоинством cost[maxcoin] , * то выдать ее: */ count[ maxcoin ] ++; /* посчитали выданную монету */ /* размениваем остаток суммы : * Первый аргумент - может быть можно дать еще одну такую монету ? * Второй аргумент - общая сумма убавилась на одну монету cost[maxcoin]. */ change( maxcoin, sum - cost[maxcoin] ); count[ maxcoin ] --; /* ... Теперь попробуем иной вариант ... */ } /* попробовать размен более мелкими монетами */ if( maxcoin ) change( maxcoin-1, sum ); } /* Пример 2 */ /* Подсчет количества вхождений каждой из букв алфавита в файл. * Выдача таблицы. * Подсчет частоты использования битов в байтах файла. */ #include <stdio.h> #include <ctype.h> long bcnt[8]; char masks[8] = { /* маски битов */ 1, 2, 4, 8, 16, 32, 64, 128 }; long cnt[256]; /* счетчики для каждой из 256 букв */ /* распечатка букв в стиле языка СИ */ char *pr( c ){ static char buf[ 20 ]; switch( c ){ case '\n': return " \\n " ; case '\r': return " \\r " ; case '\t': return " \\t " ; case '\b': return " \\b " ; case '\f': return " \\f " ; case '\033': return " ESC" ; case '\0': return " \\0 " ; case 0177: return " ^? " ; } if( c < ' ' ){ sprintf( buf, " ^%c ", c + 'A' - 1 ); }else if( isspace(c)){ sprintf( buf, " '%c'", c ); }else if( ! isprint( c )) sprintf( buf, "\\%3o", c ); else sprintf( buf, " %c ", c ); return buf; } main( argc, argv ) char **argv; { FILE *fp; if( argc == 1 ) process( stdin ); else{ argv++; argc--; while( *argv ){ printf( "----- FILE %s -----\n", *argv ); if((fp = fopen( *argv, "r" )) == NULL ){ printf( "Can not open\n" ); }else{ process( fp ); fclose( fp ); } argv++; argc--; } } exit(0); } /* обработать файл с поинтером fp */ process( fp ) FILE *fp; { register i; int c; int n; /* зачистка счетчиков */ for( i=0; i < 256; i++ ) cnt[i] = 0L; for( i=0; i < 8 ; i++ ) bcnt[i] = 0; while( ( c=getc(fp)) != EOF ){ c &= 0377; /* подсчитать букву */ cnt[ c ] ++; /* подсчет битов */ for( i=0; i < 8; i++ ) if( c & masks[i] ) bcnt[ i ] ++; } /* выдача результатов в COL колонок */ #define COL 4 printf( "\tASCII map\n" ); for( n=i=0; i < 256; i++ ){ /* if( cnt[i] == 0l ) continue; */ printf( "%s %5ld |", pr(i), cnt[i] ); if( ++n == COL ){ n = 0; putchar('\n'); } /* или if((i % COL) == (COL-1)) putchar('\n'); */ } printf( "\n\tBITS map\n" ); for( i=7; i >=0 ; i-- ) printf( "%6d ", i ); putchar( '\n' ); for( i=7; i >=0 ; i-- ) printf( "%6ld ", bcnt[i] ); putchar( '\n' ); putchar( '\n' ); } /* Пример 3 */ /* Центрирование строк текста. Пример на работу с указателями. */ /* Входные строки не должны содержать табуляций */ /* Вызов: a.out < входной_файл */ #include <stdio.h> extern char *gets(); #define WIDTH 60 /* ширина листа */ main(){ char rd[81]; register char *s; char *head, /* начало текста */ *tail; /* конец текста */ register int len, i; int shift; /* отступ */ /* Читать со стандартного ввода в rd по одной строке, * пока файл не кончится. При вводе с клавиатуры конец файла * обозначается нажатием клавиш CTRL+D */ while( gets( rd ) != NULL ){ if( !*rd ){ /* Строка пуста */ putchar( '\n' ); continue; } /* пропуск пробелов в начале строки */ for( s = rd; *s == ' ' ; s++ ); if( ! *s ){ /* Строка состоит только из пробелов */ putchar( '\n' ); continue; } head = s; /* встать на конец строки */ while( *s ) s++; /* искать последний непробел */ s--; while( *s == ' ' && s != rd ) s--; tail = s; /* Длина текста */ len = (tail-head) + 1; /* разность указателей - целое */ shift = (WIDTH - len)/2; if(shift < 0 ){ fprintf(stderr, "Строка длиннее чем %d\n", WIDTH ); shift = 0; } /* Печать результата */ for( i=0; i < shift; i++ ) putchar( ' ' ); while( head <= tail ) putchar( *head++ ); putchar( '\n' ); } } /* Пример 4 */ /* Предварительная разметка текста для nroff */ #include <stdio.h> #include <stdlib.h> #include <ctype.h> #include <string.h> /* прототип strchr() */ #include <locale.h> FILE *fout = stdout; /* канал вывода */ /* Состояния вывода */ #define SPACE 0 /* пробелы */ #define TEXT 1 /* текст */ #define PUNCT 2 /* знаки препинания */ #define UC(c) ((unsigned char)(c)) /* Вывод строки текста из буфера */ void putstr (FILE *fp, unsigned char *s) { /* Punct - знаки препинания, требующие приклеивания к * концу предыдущего слова. * PunctS - знаки, всегда требующие после себя пробела. * PunctN - знаки, которые могут следовать за знаком * препинания без пробела. */ static char Punct [] = ",:;!?.)" ; static char PunctS[] = ",:;" ; static char PunctN[] = " \t\"'" ; #define is(c, set) (strchr(set, UC(c)) != NULL) int c, state = TEXT, cprev = 'X'; while ((c = *s) != '\0') { /* Пробелы */ if(isspace(c)) state = SPACE; /* Знаки препинания. Пробелы перед ними игнорируются. */ else if(is(c, Punct)){ switch(state){ case SPACE: if(is(cprev, Punct ) && cprev==c && c != ')') putc(' ', fp); /* а просто пробелы - игнорировать */ break; case PUNCT: if(is(cprev, PunctS)) putc(' ', fp); break; } putc(cprev = c, fp); /* выводим сам знак */ state = PUNCT; } else { /* Несколько пробелов сворачиваем в один */ switch(state){ case SPACE: putc(' ', fp); break; case PUNCT: if(!is(c, PunctN)) putc(' ', fp); break; } putc(cprev = c, fp); /* сама буква */ state = TEXT; if(c == '\\') putc('e', fp); } s++; } /* пробелы в конце строки просто игнорируются */ putc ('\n', fp); } /* Обработать файл с именем name */ void proceed (char *name) { FILE *fp; static unsigned char inp[2048]; /* достаточно большой буфер ввода */ if (strcmp(name, "-") == 0 ) fp = stdin; else if ((fp = fopen (name, "r")) == NULL) { fprintf (stderr, "Cannot read %s\n", name); return; } while (fgets (inp, sizeof inp, fp) != NULL) { register unsigned char *s, *p; int len = strlen (inp); if (len && inp[len - 1] == '\n') inp[--len] = '\0'; if (!*inp) { /* .sp N - пропуск N пустых строк */ space: fprintf (fout, ".sp 1\n"); continue; } /* обрезать концевые пробелы */ for(p = NULL, s = inp; *s; ++s){ if (!isspace (*s)) p = s; } if(p) p[1] = '\0'; else goto space; /* p указывает на последний непробел */ /* Удалить переносы слов в конце строки: перенос - это минус, прижатый к концу слова */ if (*p == '-' && p != inp /* не в начале строки */ && isalnum(UC(p[-1])) /* после буквы */ ){ int c; *p = '\0'; /* затереть перенос */ /* Читаем продолжение слова из начала следующей строки */ while (isspace (c = getc (fp))); ungetc (c, fp); while ((c = getc (fp)) != '\n' && !isspace (c)) *p++ = c; *p = '\0'; if (c != '\n' ){ /* прочли пробел */ /* вычитываем ВСЕ пробелы */ while (isspace(c = getc (fp))); if(c != '\n') ungetc (c, fp); } } /* .pp - директива начала абзаца. */ if (isspace (*inp)) { fprintf (fout, ".pp\n"); for (s = inp; isspace (*s); s++); putstr (fout, s); } else { if (*inp == '.' || *inp == '\'') fprintf (fout, "\\&"); putstr (fout, inp); } } if( fp != stdin ) fclose (fp); } int main (int argc, char *argv[]) { int i; setlocale(LC_ALL, ""); for (i = 1; i < argc; i++) proceed (argv[i]); return 0; /* exit code */ } /* Пример 5 */ /* Программа, распечатывающая слова в строках файла в обратном порядке */ #include <stdio.h> #include <ctype.h> #include <string.h> #include <locale.h> #define MAXL 255 /* макс. длина строки */ /* Если бы мы не включили ctype.h, то мы должны были бы определить * #define isspace(c) ((c) == ' ' || (c) == '\t' || (c) == '\f') */ main ( argc, argv ) char **argv;{ setlocale(LC_ALL, ""); if( argc == 1 ){ /* программа вызвана без аргументов */ munch( "" ); }else{ /* аргументы программы - имена файлов */ while( argv[ 1 ] ){ munch( argv[1] ); argv++; argc--; } } total(); exit(0); } /* обработать файл с именем name */ munch( name ) char *name; { char l[MAXL]; /* буфер для очередной строки */ int len; /* длина этой строки */ char *words[50]; /* таблица полей строки */ char **s; /* служебная */ int nwords; /* число слов в строке */ FILE *fp; if( name == NULL || !*name ) fp = stdin; /* стандартный ввод */ else if( (fp = fopen( name, "r" )) == NULL ){ fprintf( stderr, "Не могу открыть файл %s\n", name ); return; } printf( "----------------------------%s----\n", name ); while( fgets( l, MAXL, fp ) != NULL ){ len = strlen( l ); if( len && l[len-1] == '\n' ) l[--len] = '\0' ; if( nwords = parse( l, words)){ /* распечатка слов в обратном порядке */ for( --nwords; nwords >= 0; nwords-- ){ printf( "%s ", words[ nwords] ); add( words[ nwords ] ); } } putchar ('\n'); } if( fp != stdin ) fclose( fp ); } /* разобрать строку на слова */ parse( s, tabl ) register unsigned char *s; unsigned char *tabl[]; { char eol = 0; int nwords = 0; while ( !eol ){ /* пропустить пробелы и табуляции */ while(isspace(*s)) s++; if( !*s ) /* строка кончилась */ break; *tabl++ = s; nwords++; /* начало очередного слова */ /* пока не пробел и не конец строки */ while( *s && !isspace(*s))s++; /* указатель стоит на символе, следующем за словом */ if( ! *s ) eol ++; *s = '\0'; /* закрыли Слово, начинаем Дело */ s++; } *tabl = NULL; return nwords; } /* построение таблицы слов, встречающихся в файле */ #define MAXWORDS 1024 struct W{ int ctr; /* число вхождений слова */ char *wrd; /* слово */ }w [MAXWORDS]; /* таблица */ int busy = 0 ; /* занято в таблице */ extern char *malloc(); /* Добавить слово в таблицу */ add( word ) char *word; { register i; static alert = 1; /* нет ли уже слова в таблице ? */ /* если есть - просто увеличить счетчик */ for( i = 0; i < busy ; i++ ){ if( !strcmp( word, w[i].wrd )){ w[i].ctr++; return; } } if( busy >= MAXWORDS ){ if( alert ){ fprintf( stderr, "Переполнение таблицы слов\7\n"); alert = 0; } return; } /* нет, слова нет. Заносим: */ w[busy].wrd = malloc( strlen( word ) + 1 ); /* 1 байт под символ \0 */ if( w[busy].wrd == NULL ){ fprintf( stderr, "Мало памяти\n"); busy = MAXWORDS+1; /* якобы переполнение */ return; } w[busy].ctr = 1; strcpy( w[busy].wrd, word ); busy++; } compare( a, b ) struct W *a, *b; { return strcoll( a-> wrd, b-> wrd ); /* strcoll сравнивает слова в алфавитном порядке */ } /* выдача всех слов, встреченных в тексте, и числа их вхождений */ total(){ register i; /* сортируем слова по алфавиту */ qsort( w, busy, sizeof(struct W), compare ); printf( "-----|-----------ИТОГ---------------\n"); for( i=0; i < busy; i++ ) printf( "%4d | %s\n", w[i].ctr, w[i].wrd ); } /* Пример 6 */ /* Сортировка букв в строке методом "пузырька" (bubble sort) */ #define YES 1 #define NO 0 bsort(s) char *s; { register i; /* индекс сравниваемой буквы */ register need = YES; /* надо ли продолжать сортировку ? */ while( need ){ need = NO; /* не надо */ for(i=0; s[i+1]; i++ ) /* условие цикла: мы сравниваем i-ую и i+1-ую буквы, * поэтому и проверяем наличие i+1ой буквы */ if( s[i] > s[i+1] ){ /* в неверном порядке */ swap( &s[i], &s[i+1] ); /* переставить */ need = YES; /* что-то изменилось: надо будет * повторить просмотр массива букв */ } } } /* А вот вариант сортировки, написанный с указателями */ bpsort(s) char *s; { register char *p; register need = YES; while( need ){ need = NO; for( p = s; p[1] != '\0' ; p++ ) if( *p > *(p+1) ){ swap( p, p+1 ); need = YES; } } } /* обмен двух букв, находящихся по адресам s1 и s2 */ swap( s1, s2 ) register char *s1, *s2; { char tmp; /* temporary */ tmp = *s1; *s1 = *s2; *s2 = tmp; } char sample1[] = "Homo homini lupus est - ergo bibamus!"; char sample2[ sizeof sample1 ]; /* массив такого же размера */ main(){ strcpy( sample2, sample1 ); /* скопировать */ bsort ( sample1 ); printf( "%s\n", sample1 ); bpsort( sample2 ); printf( "%s\n", sample2 ); } /* Пример 7 */ /* Работа с хэш-таблицей. Часть функций написана так, чтобы * быть независимой от типов ключа и значения и легко * подвергаться модификации. */ #include <stdio.h> #include <string.h> /* prototype for strchr() */ extern void *malloc(unsigned size); /* типы ключа и значения: в нашем случае это строки */ typedef unsigned char uchar; typedef uchar *VAL; typedef uchar *KEY; /* Для использования следует реализовать операции int HASHFUNC(KEY); int EQKEY(KEY, KEY); void FREEVAL(VAL); void SETVAL(VAL, VAL); void FREEKEY(KEY); void SETKEY(KEY, KEY); */ #define HASHSIZE 21 /* размер таблицы: очень хорошо 2**n */ uchar *strudup(const uchar *s){ /* создание копии строки в "куче" */ uchar *p = (uchar *) malloc(strlen(s)+1); strcpy(p, s); return p; } /* одна из возможных хэш-функций */ unsigned int hash; /* последнее вычисленное значение хэш-функции */ int HASHFUNC(KEY key){ unsigned int i = 0; uchar *keysrc = key; while(*key){ i = (i << 1)|(i >> 15); /* ROL */ i ^= *key++; } hash = i % HASHSIZE; printf( "hash(%s)=%d\n", keysrc, hash); /* отладка */ return hash; } #define EQKEY(s1, s2) (strcmp(s1, s2) == 0) #define FREEKEY(s) free(s) #define FREEVAL(s) free(s) #define SETVAL(at,s) at = strudup(s) #define SETKEY(at,s) at = strudup(s) #define KEYFMT "%s" #define VALFMT "%s" /* ================== типо-независимая часть ================= */ struct cell { struct cell *next; /* ссылка на очередной элемент */ KEY key; /* ключ */ VAL val; /* значение */ } *hashtable[ HASHSIZE ]; /* хэш-таблица */ /* получение значения по ключу */ struct cell *get(KEY key){ struct cell *p; for(p = hashtable[HASHFUNC(key)]; p; p = p->next) if(EQKEY(p->key, key)) return p; return NULL; /* отсутствует */ } /* занести пару ключ:значение в таблицу */ void set(KEY key, VAL val){ struct cell *p; /* проверить - не было ли звена с таким ключом */ if((p = get(key)) == NULL){ /* не было */ if(!(p = (struct cell *) malloc(sizeof(*p)))) return; SETKEY(p->key, key); p->next = hashtable[hash]; /* hash вычислено в get() */ hashtable[hash] = p; } else /* уже было: изменить значение */ FREEVAL(p->val); SETVAL(p->val, val); } /* удаление по ключу */ int del(KEY key){ int indx = HASHFUNC(key); struct cell *p, *prev = NULL; if((p = hashtable[indx]) == NULL) return 0; for( ;p ;prev = p, p=p->next) if(EQKEY(p->key, key)){ FREEVAL(p->val); FREEKEY(p->key); if( p == hashtable[indx] ) /* голова списка */ hashtable[indx] = p->next; else prev->next = p->next; free((void *) p ); return 1; /* удален */ } return 0; /* не было такого */ } /* распечатать пару ключ:значение */ void printcell(struct cell *ptr){ putchar('('); printf( KEYFMT, ptr->key ); putchar(','); printf( VALFMT, ptr->val ); putchar(')'); } /* распечатка таблицы (для отладки) */ void printtable(){ register i; struct cell *p; printf("----TABLE CONTENTS----\n"); for(i=0; i < HASHSIZE; i++) if((p = hashtable[i]) != NULL){ printf( "%d: ", i); for(; p; p=p->next) printcell(p), putchar(' '); putchar('\n'); } } /* итератор */ struct celliter { int index; struct cell *ptr; }; /* выдать очередное значение */ struct cell *nextpair(struct celliter *ci){ struct cell *result; while((result = ci->ptr) == NULL){ if( ++(ci->index) >= HASHSIZE ) return NULL; /* больше нет */ ci->ptr = hashtable[ci->index]; } ci->ptr = result->next; return result; } /* инициализация итератора */ struct cell *resetiter(struct celliter *ci){ ci->index = (-1); ci->ptr = NULL; return nextpair(ci); /* первое значение */ } /* =========================================================== */ void main(){ /* таблица из имен и размеров файлов текущего каталога */ struct celliter ci; struct cell *cl; char key[40], value[40]; struct cell *val; extern FILE *popen(); FILE *fp; char *s ; /* popen() читает вывод команды, заданной в 1-ом аргументе */ fp = popen( "ls -s", "r" ); while( fscanf( fp, "%s%s", value, key) == 2 ) set(key, value); pclose(fp); /* popen() надо закрывать pclose(); */ for(;;){ printf( "-> " ); /* приглашение */ if( !gets( key )) break; /* EOF */ if( *key == '-' ){ /* -КЛЮЧ :удалить */ printf( del( key+1 ) ? "OK\n" : "нет такого\n"); continue; } if( !*key || !strcmp(key, "=")){ /* = :распечатать таблицу*/ printtable(); continue; } if(s = strchr(key, '=')){ /* КЛЮЧ=ЗНАЧЕНИЕ :добавить */ *s++ = '\0'; set(key, s); continue; } if((val = get( key )) == NULL) /* КЛЮЧ :найти значение */ printf( "нет такого ключа\n"); else{ printf( "значение "); printf(VALFMT, val->val); putchar('\n'); } } /* распечатка таблицы при помощи итератора */ for( cl = resetiter(&ci) ; cl ; cl = nextpair(&ci)) printcell(cl), putchar('\n'); } /* Пример 8 */ /* Пример маленькой базы данных. * Данные хранятся БЕЗ дубликатов. * Надо заметить, что используется плохой (неэффективный) * алгоритм доступа - линейный поиск. */ #include <stdio.h> /* Все записи в базе имеют фиксированный размер */ #define VLEN 20 #define KEY_FREE (-13) /* ключ свободного места. Он выбран произвольно, но не должен встречаться в качестве входных данных */ struct data{ short b_key; /* ключ */ char b_val[VLEN]; /* строка-значение */ }; char BASEF[] = ".base" ; /* имя файла базы */ FILE *fbase; /* pointer на базу */ struct data tmp; /* вспомогательная переменная */ void initBase (void){ /* fopen: r read (чтение) * w write (запись), файл пересоздается. * (создается, если не было, если был - опустошается). * r+ чтение и запись (файл уже существует). * w+ чтение и запись (создается пустой файл). * a append (запись в конец файла), создать если нет: * имеется в виду, что КАЖДАЯ операция записи сначала * ставит указатель записи на конец файла. * В MS DOS нетекстовый файл НЕОБХОДИМО открывать как * rb wb rb+ wb+ ab+ иначе ничего не будет работать. */ if(( fbase = fopen( BASEF, "r+" )) == NULL ){ if(( fbase = fopen( BASEF, "w+" )) == NULL ){ fprintf( stderr, "Не могу открыть базу данных %s\n", BASEF ); exit(1); } fprintf( stderr, "База создана\n" ); } } void closeBase (void){ fclose( fbase ); } /* Учтите, что если вы записываете в файл структуры, то в файле не будет разделения на строки - файл НЕТЕКСТОВЫЙ! Поэтому и читать такой файл можно только структурами: read(), fread() (но не scanf-ом и не fgets-ом) */ /* Поиск по ключу . Выдать (-1), если записи с данным ключом нет, иначе - номер слота, где содержится запись с данным ключом. */ int bget (int key) { int n; /* последовательно просмотреть весь файл */ rewind( fbase ); /* в начало файла. Равно fseek(fbase, 0L, 0); */ n = 0 ; /* int сколько_элементов_массива_действительно_считано = * fread( адрес_массива_куда_считывать, * размер_одного_элемента_массива, * сколько_элементов_считывать_в_массив, канал ); * Заметьте, что количество данных задается НЕ в байтах, * а в 'штуках' */ while( fread( &tmp, sizeof( tmp ), 1, fbase ) == 1 ){ if( tmp.b_key == key ) return n; n++; } return (-1); /* не найдено */ } /* модифицировать запись с индексом ind */ void bmod ( int ind, int key, /* новый ключ */ char *val /* новое значение */ ) { struct data new; fseek( fbase, (long) sizeof( struct data ) * ind, 0 ); new.b_key = key; strncpy( new.b_val, val, VLEN ); /* int сколько_элементов_массива_действительно_записано = * fwrite( адрес_массива_который_записывать, * размер_одного_элемента_массива, * сколько_элементов_массива_записывать, канал ); */ if( fwrite( &new, sizeof new , 1, fbase ) != 1 ) fprintf( stderr, "Ошибка записи.\n" ); } /* удаление записи по ключу */ int bdel (int key){ int ind = bget( key ); if( ind == -1 ) return (-1); /* записи с таким ключом нет */ bmod( ind, KEY_FREE, "" ); /* записать признак свободного места */ return 0; } /* Служебная процедура дописи к концу файла */ void bappend (int key, char *val) { struct data new; /* встать на конец файла */ fseek( fbase, 0L, 2 ); /* и записать новую структуру в конец */ new.b_key = key; strncpy( new.b_val, val, VLEN ); fwrite( &new, sizeof( struct data ) , 1, fbase ); } /* добавление новой записи. Если запись с таким ключом уже есть - выдать ошибку */ int bput (int key, char *val) { int i = bget( key ); if( i != -1 ) return (-1); /* запись уже есть */ /* найти свободное место */ i = bget( KEY_FREE ); if( i == -1 ) { /* нет свободных мест */ bappend( key, val ); return 0; } /* иначе свободное место найдено. * Заменяем дырку на полезную информацию */ bmod( i, key, val ); } /* распечатать всю базу данных подряд */ void bprint (void){ int n; int here = 0; rewind( fbase ); n = 0; printf( "-номер--ключ-------значение-----------------\n" ); while( fread( &tmp, sizeof tmp, 1, fbase ) == 1 ){ if( tmp.b_key == KEY_FREE ){ n++; continue; } printf( "#%-2d| %6d\t| %s\n", n, tmp.b_key, tmp.b_val ); here ++; n++; } printf( "--------------------------------------------\n" ); printf( "Длина базы:%d Занято:%d\n\n", n, here ); } /* замена поля val у записи с ключом key */ int bchange (int key, char *val) { int ind; ind = bget( key ); if( ind == -1 ){ /* запись с таким ключом не существует */ /* Добавить как новую запись */ bput( key, val ); return 0; } bmod( ind, key, val ); return 1; } /* Аналогичная функция, но использующая другой способ. * Кроме того, если такой ключ отсутствует - ничего не делается */ int bchg (int key, char *val) { struct data d; rewind( fbase ); /* в начало файла */ while( fread( &d, sizeof d, 1, fbase ) == 1 ){ /* поиск ключа */ if( d.b_key == key ){ /* вернуться назад от текущей позиции */ fseek( fbase, - (long) sizeof d, 1 ); /* не годится (long)-sizeof d !!! */ d.b_key = key; strncpy( d.b_val, val, VLEN ); fwrite( &d, sizeof d, 1, fbase ); /* между fread и fwrite должен быть * хоть один fseek. (магическое заклинание!) */ fseek( fbase, 0L, 1); /* никуда не сдвигаться */ return 0; /* сделано */ } } return (-1); /* такого ключа не было */ } /* Пример */ void main (void){ int i; initBase(); bprint(); bdel( 8 ); printf( "Создаем базу данных\n" ); bput( 1, "строка 1" ); bput( 2, "строка 2" ); bput( 3, "строка 3" ); bput( 4, "строка 4" ); bprint(); printf( "Удаляем записи с ключами 1 и 3\n" ); bdel( 1 ); bdel( 3 ); bprint(); printf( "Добавляем записи 5, 6 и 7\n" ); bput( 5, "строка 5" ); bput( 6, "строка 6" ); bput( 7, "строка 7" ); bprint(); printf( "Заменяем строку в записи с ключом 2\n" ); bchange( 2, "новая строка 2" ); bprint(); printf( "Заменяем строку в записи с ключом 4\n" ); bchg( 4, "новая строка 4" ); bprint(); printf( "Заменяем строку в записи с ключом 6 и ключ 6 на 8\n" ); i = bget( 6 ); printf( "Сейчас запись с ключом 6 содержит \"%s\"\n", tmp.b_val ); bmod( i, 8, "Новая строка 6/8" ); bprint(); closeBase(); } /* Пример 9 */ /* Вставка/удаление строк в файл */ #include <stdio.h> #define INSERT_BEFORE 1 /* Вставить строку перед указанной */ #define INSERT_AFTER 2 /* Вставить строку после указанной */ #define DELETE 3 /* Удалить строку */ #define REPLACE 4 /* Заменить строку */ /* К каждой строке linenum должно относиться не более 1 операции !!! */ struct lineop { char op; /* Операция */ long linenum; /* Номер строки в файле (с 0) */ char *str; /* Строка (или NULL для DELETE) */ }; long lineno; /* номер текущей строки */ int fileChange (char *name, /* имя файла */ struct lineop ops[], /* задание */ int nops /* число элементов в массиве ops[] */ ){ FILE *fin, *fout; static char TMPNAME[] = " ? "; char buffer[BUFSIZ]; register i; struct lineop tmpop; if ((fin = fopen (name, "r")) == NULL) return (-1); if ((fout = fopen (TMPNAME, "w")) == NULL) { fclose (fin); return (-1); } lineno = 0L; while (fgets (buffer, BUFSIZ, fin) != NULL) { if( nops ) for (i = 0; i < nops; i++) if (lineno == ops[i].linenum) { switch (ops[i].op) { case DELETE: /* удалить */ break; case INSERT_BEFORE: /* вставить перед */ fprintf (fout, "%s\n", ops[i].str); fputs (buffer, fout); break; case INSERT_AFTER: /* вставить после */ fputs (buffer, fout); fprintf (fout, "%s\n", ops[i].str); break; case REPLACE: /* заменить */ fprintf (fout, "%s\n", ops[i].str); break; } /* переставить выполненную операцию в конец массива и забыть */ tmpop = ops[nops-1]; ops[nops-1] = ops[i]; ops[i] = tmpop; nops--; goto next; } /* иначе строка не числится в массиве ops[] : скопировать */ fputs (buffer, fout); next: lineno++; } fclose (fin); fclose (fout); rename (TMPNAME, name); return nops; /* число несделанных операций (0 - все сделано) */ } struct lineop myops[] = { { DELETE, 2L, NULL }, { INSERT_BEFORE, 0L, "inserted before 0" }, { INSERT_BEFORE, 10L, "inserted before 10" }, { INSERT_AFTER, 5L, "inserted after 5" }, { DELETE, 6L, NULL }, { INSERT_AFTER, 8L, "inserted after 8" }, { INSERT_AFTER, 12L, "inserted after 12" }, { REPLACE, 3L, "3 replaced" } }; void main( void ){ int n; n = fileChange( "aFile", myops, sizeof(myops)/sizeof(struct lineop)); printf( "Строк в файле: %ld; осталось операций: %d\n", lineno, n); } /* исходный файл получившийся файл line 0 inserted before 0 line 1 line 0 line 2 line 1 line 3 3 replaced line 4 line 4 line 5 line 5 line 6 inserted after 5 line 7 line 7 line 8 line 8 line 9 inserted after 8 line 10 line 9 inserted before 10 line 10 Строк в файле: 11; осталось операций: 1 */ /* Пример 10 */ /* Проблема: позволить делать вызов free(ptr) * на данные, не отводившиеся malloc()-ом. * Решение: вести список всех данных, * отведенных malloc()ом. * Возможно также отслеживание диапазона адресов, * но последнее является машинно-зависимым решением. * * При большом количестве файлов эта программа - неплохой тест * производительности машины! */ #include <stdio.h> #include <stdlib.h> #include <string.h> typedef struct _cell { void *addr; struct _cell *next; } Cell; typedef struct _entry { int length; int used; Cell *list; } Entry; /* Хэшированная таблица */ #define NENTRIES 64 Entry aTab[NENTRIES]; /* Хэш-функция от адреса */ int aHash(void *addr){ unsigned long x = (unsigned long) addr; x >>= 3; /* деление на 8, так как адреса из malloc() обычно четные, поскольку выровнены на границу double */ return(x % NENTRIES); /* Тут к месту напомнить, что вычисление остатка от деления на степень двойки * можно соптимизировать: * x % (2**N) = x & 0b0001.....1 (N двоичных единиц) * К примеру, x % 64 = x & 0x3F; (6-ая степень двойки) */ } /* Выделить память, записать адрес в таблицу */ void *aCalloc(int n, int m){ void *ptr = calloc(n, m); Entry *ep = &aTab[ aHash(ptr) ]; Cell *p; for(p=ep->list; p; p=p->next) if(p->addr == NULL){ /* Свободная ячейка: переиспользовать */ p->addr = ptr; ep->used++; return ptr; } /* Нет свободных, завести новую */ p = (Cell *) calloc(1, sizeof(Cell)); p->addr = ptr; p->next = ep->list; ep->list = p; ep->length++; ep->used++; return ptr; } /* Освободить память */ int aFree(void *ptr){ Entry *ep = &aTab[ aHash(ptr) ]; Cell *p; for(p=ep->list; p; p=p->next) if(p->addr == ptr){ free(ptr); p->addr = NULL; /* Ячейка не удаляется, но метится как свободная */ ep->used--; return 1; } /* Нет, такой указатель не отводился. * Не делать free() */ return 0; } /* Выдать статистику об использовании хэша */ void aStat(){ int i; int len_all; int used_all; for(i=len_all=used_all=0; i < NENTRIES; i++){ len_all += aTab[i].length; used_all += aTab[i].used; printf("%d/%d%s", aTab[i].used, aTab[i].length, i==NENTRIES-1 ? "\n":" "); } printf("%d/%d=%g%%\n", used_all, len_all, (double)used_all * 100 / len_all); } /* ТЕСТ =================================================================*/ Cell *text; /* Прочитать файл в память */ void fileIn(char *name){ char buf[10000]; FILE *fp; if((fp = fopen(name, "r")) == NULL){ printf("Cannot read %s\n", name); return; } while(fgets(buf, sizeof buf, fp) != NULL){ char *s; Cell *p; s = (char *) aCalloc(1, strlen(buf)+1); strcpy(s, buf); p = (Cell *) aCalloc(sizeof(Cell), 1); p->addr = s; p->next = text; text = p; } fclose(fp); } /* Уничтожить текст в памяти */ void killAll(){ Cell *ptr, *nxtp; ptr = text; while(ptr){ nxtp = ptr->next; if(!aFree(ptr->addr)) printf("No free(1)\n"); if(!aFree(ptr)) printf("No free(2)\n"); ptr = nxtp; } } /* Удалить из текста строки, начинающиеся с определенной буквы */ void randomKill(int *deleted){ unsigned char c = rand() % 256; Cell *ptr, *prevp; unsigned char *s; retry: prevp = NULL; ptr = text; while(ptr){ s = (unsigned char *) ptr->addr; if(*s == c){ /* нашел */ if(!aFree(s)) printf("No free(3)\n"); /* исключить из списка */ if(prevp) prevp->next = ptr->next; else text = ptr->next; if(!aFree(ptr)) printf("No free(4)\n"); /* Заведомо неправильный free if(!aFree(ptr+1)) printf("No free(5)\n"); */ (*deleted)++; goto retry; } prevp = ptr; ptr = ptr->next; } } int main(int ac, char *av[]){ int i, r, d; char buffer[4098]; srand(time(NULL)); for(i=1; i < ac; i++){ printf("File: %s\n", av[i]); fileIn(av[i]); aStat(); d = 0; for(r=0; r < 128; r++) randomKill(&d); printf("%d lines deleted\n", d); aStat(); } killAll(); aStat(); if(!aFree(buffer)) printf("buffer[] - не динамическая переменная.\n"); return 0; } /* Пример 11 */ /* Пакет для ловли наездов областей выделенной памяти * друг на друга, * а также просто повреждений динамически отведенной памяти. */ #include <stdio.h> #include <stdlib.h> #include <fcntl.h> /* O_RDWR */ #include <sys/types.h> #include <ctype.h> #include <locale.h> #define CHECKALL /* ----------------- <--------- ptr | red_zone | головная "пограничная зона" ----------------- | byte[0] | | ... | | byte[size-1] | | placeholder | ----------------- выровнено на границу RedZoneType | red_zone | хвостовая "пограничная зона" ----------------- Основные идеи состоят в следующем: 1) Перед и после области данных строится зона, заполненная заранее известным "узором". Если ее содержимое изменилось, испорчено - значит мы где-то разрушили нашу память. 2) Ведется таблица всех отведенных malloc()-ом сегментов памяти; для экономии места эта таблица вынесена в файл (но зато это очень медленно). 3) Мы не можем пользоваться библиотекой STDIO для обменов с файлом, потому что эта библиотека сама использует malloc() и буфера могут быть разрушены. */ typedef char *RedZoneType; /* выравнивание на границу указателя */ /* Можно выравнивать на границу double: typedef double RedZoneType; */ /* Сегмент, выделяемый в оперативной памяти */ typedef struct _allocFrame { RedZoneType red_zone; /* головная "пограничная зона" */ RedZoneType stuff[1]; /* место для данных */ /* хвостовая "пограничная зона" безымянна */ } AllocFrame; const int RedZoneTypeSize = sizeof(RedZoneType); /* Запись, помещаемая в таблицу всех выделенных malloc()ом * областей памяти. */ typedef struct _memFileRecord { AllocFrame *ptr; /* адрес */ size_t size, adjsize; /* размер выделенной области */ /* (0,0) - означает "сегмент освобожден" */ int serial; } MemFileRecord; char red_table[] = { 0x01, 0x03, 0x02, 0x04, 0x11, 0x13, 0x12, 0x14, 0x21, 0x23, 0x22, 0x24, 0x31, 0x33, 0x32, 0x34 }; char free_table[] = { 'F', 'r', 'e', 'e', 'p', 't', 'r', '\0', 'F', 'r', 'e', 'e', 'p', 't', 'r', '\0' }; /* Файл для хранения таблицы указателей */ static int mem_fd = (-1); #define PTABLE "PointerTable.bin" #define NRECORDS 256 MemFileRecord memrecords[NRECORDS]; /* ============================================================= */ void MEMputTableRecord(AllocFrame *newptr, AllocFrame *oldptr, size_t size, size_t adjsize); void MEMputTableRecordKilled(AllocFrame *ptr); void MEMerasePreviousRecords(AllocFrame *ptr); int MEMcheckRecord(MemFileRecord *rec); int MEMcheck_consistency(AllocFrame *ptr); void MEMmakeRedZones(char *cptr, size_t size, size_t adjsize); void MEMopenFd(); /* ============================================================= */ /* Этим следует пользоваться вместо стандартных функций */ void *MEMmalloc (size_t size); void *MEMrealloc(void *ptr, size_t size); void *MEMcalloc (size_t n, size_t size); void MEMfree (void *ptr); void MEMcheckAll(); /* это можно вызывать в середине программы */ /* ============================================================= */ void MEMopenFd(){ if(mem_fd < 0){ close(creat(PTABLE, 0644)); /* создать файл */ mem_fd = open(PTABLE, O_RDWR); /* чтение+запись */ unlink(PTABLE); /* только для M_UNIX */ atexit(MEMcheckAll); setlocale(LC_ALL, ""); } } /* Поместить запись в таблицу всех указателей на * выделенные области памяти. */ void MEMputTableRecord(AllocFrame *newptr, /* для записи */ AllocFrame *oldptr, /* для стирания */ size_t size, /* размер данных */ size_t adjsize /* размер всей записи с зонами */ ){ MemFileRecord memrecord; static int serial = 0; memrecord.ptr = newptr; memrecord.size = size; memrecord.adjsize = adjsize; memrecord.serial = serial++; MEMopenFd(); #ifdef CHECKALL /* стереть прежние записи про этот адрес */ MEMerasePreviousRecords(oldptr); #endif lseek(mem_fd, 0L, SEEK_END); /* в конец */ write(mem_fd, &memrecord, sizeof memrecord); /* добавить */ } /* Сделать запись об уничтожении области памяти */ void MEMputTableRecordKilled(AllocFrame *ptr){ /* Пометить как size=0, adjsize=0 */ MEMputTableRecord(ptr, ptr, 0, 0); } /* Коды ответа функции проверки */ #define OK 0 /* все хорошо */ #define DAMAGED 1 /* повреждена "погранзона" */ #define FREED 2 /* эта память уже освобождена */ #define NOTHERE (-1) /* нет в таблице */ /* Проверить сохранность "пограничных зон" */ int MEMcheckRecord(MemFileRecord *rec){ int code = OK; char *cptr; register i; AllocFrame *ptr = rec->ptr; size_t size = rec->size; size_t adjsize = rec->adjsize; if(size == 0 && adjsize == 0){ printf("%p [%p] -- сегмент уже освобожден, " "record=#%d.\n", &ptr->stuff[0], ptr, rec->serial ); return FREED; } cptr = (char *) ptr; for(i=0; i < adjsize; i++){ if(i < RedZoneTypeSize || i >= RedZoneTypeSize + size ){ /* головная погранзона ИЛИ хвостовая погранзона */ if( cptr[i] != red_table[ i % RedZoneTypeSize ] ){ printf("%p [%p] -- испорчен байт %4d [%4d]" "= 0x%02X '%c' record=#%d size=%lu.\n", &ptr->stuff[0], ptr, i - RedZoneTypeSize, i, cptr[i] & 0xFF, isprint(cptr[i] & 0xFF) ? cptr[i] & 0xFF : '?', rec->serial, size ); code = DAMAGED; } } } for(i=0; i < RedZoneTypeSize; i++) if(cptr[i] == free_table[i]){ printf("%p -- уже освобождено?\n", ptr); code = FREED; } if(code != OK) putchar('\n'); return code; } /* Проверить сохранность памяти по указателю ptr. */ int MEMcheck_consistency(AllocFrame *ptr){ MemFileRecord mr_found; int nrecords, i, found = 0; size_t size; MEMopenFd(); /* Ищем запись в таблице указателей */ lseek(mem_fd, 0L, SEEK_SET); /* перемотать в начало */ for(;;){ size = read(mem_fd, memrecords, sizeof memrecords); nrecords = size / sizeof(memrecords[0]); if(nrecords <= 0) break; for(i=0; i < nrecords; i++) if(memrecords[i].ptr == ptr){ /* Мы ищем последнюю запись про память * с таким адресом, поэтому * вынуждены прочитать ВЕСЬ файл. */ mr_found = memrecords[i]; found++; } } if(found) { return MEMcheckRecord(&mr_found); } else { printf("%p -- запись в таблице отсутствует.\n", ptr); return NOTHERE; } } /* Уничтожить все прежние записи про ptr, прописывая их adjsize=0 */ void MEMerasePreviousRecords(AllocFrame *ptr){ int nrecords, i, found; size_t size; MEMopenFd(); lseek(mem_fd, 0L, SEEK_SET); /* перемотать в начало */ for(;;){ found = 0; size = read(mem_fd, memrecords, sizeof memrecords); nrecords = size / sizeof(memrecords[0]); if(nrecords <= 0) break; for(i=0; i < nrecords; i++) if(memrecords[i].ptr == ptr){ memrecords[i].adjsize = 0; /* memrecords[i].size = 0; */ found++; } if(found){ lseek(mem_fd, -size, SEEK_CUR); /* шаг назад */ write(mem_fd, memrecords, size); /* перезаписать */ } } } void MEMcheckAll(){ #ifdef CHECKALL int nrecords, i; size_t size; printf("Проверка всех указателей -------------\n"); MEMopenFd(); lseek(mem_fd, 0L, SEEK_SET); /* перемотать в начало */ for(;;){ size = read(mem_fd, memrecords, sizeof memrecords); nrecords = size / sizeof(memrecords[0]); if(nrecords <= 0) break; for(i=0; i < nrecords; i++) if(memrecords[i].adjsize != 0) MEMcheckRecord(&memrecords[i]); } printf("Проверка всех указателей завершена ---\n"); #endif } /* ============================================================= */ /* Заполнение пограничных зон образцом - "следовой дорожкой" */ void MEMmakeRedZones(char *cptr, size_t size, size_t adjsize){ register i; for(i=0; i < adjsize; i++){ if(i < RedZoneTypeSize || i >= RedZoneTypeSize + size ){ /* головная погранзона ИЛИ * хвостовая погранзона + дополнение * до целого числа RedZoneType-ов */ cptr[i] = red_table[ i % RedZoneTypeSize ]; } } } /* ============================================================= */ /* Функция выделения памяти */ void *MEMmalloc(size_t size){ AllocFrame *retptr; int fullRedZoneTypes = (size + RedZoneTypeSize - 1) / RedZoneTypeSize; size_t adjustedSize = sizeof(retptr->red_zone) * 2 + /* две погранзоны */ fullRedZoneTypes * RedZoneTypeSize; retptr = (AllocFrame *) malloc(adjustedSize); if(retptr == NULL) return NULL; MEMmakeRedZones ((char *) retptr, size, adjustedSize); MEMputTableRecord(retptr, retptr, size, adjustedSize); return &retptr->stuff[0]; /* вернуть указатель на зону данных */ } void *MEMrealloc(void *ptr, size_t size){ AllocFrame *retptr; char *cptr = (char *)ptr - RedZoneTypeSize; /* прежний AllocFrame */ AllocFrame *oldptr = (AllocFrame *) cptr; int fullRedZoneTypes = (size + RedZoneTypeSize - 1) / RedZoneTypeSize; size_t adjustedSize = sizeof(retptr->red_zone) * 2 + fullRedZoneTypes * RedZoneTypeSize; /* Проверить сохранность того, что мы сейчас будем realloc-ить */ MEMcheck_consistency(oldptr); retptr = (AllocFrame *) realloc((void *)oldptr, adjustedSize); if(retptr == NULL) return NULL; MEMmakeRedZones ((char *) retptr, size, adjustedSize); MEMputTableRecord(retptr, oldptr, size, adjustedSize); return &retptr->stuff[0]; } void *MEMcalloc(size_t n, size_t size){ size_t newsize = n * size; void *ptr = MEMmalloc(newsize); memset(ptr, '\0', newsize); return ptr; } /* Очистка отведенной памяти. * ptr - это указатель не на AllocFrame, * а на данные - то есть на stuff[0]. */ void MEMfree(void *ptr){ char *cptr = (char *)ptr - RedZoneTypeSize; int i, code; code = MEMcheck_consistency((AllocFrame *) cptr); for(i=0; i < RedZoneTypeSize; i++) cptr[i] = free_table[i]; if(code != FREED) free((void *) cptr); MEMputTableRecordKilled((AllocFrame *) cptr); } /* ============================================================= */ /* Тестовый пример */ /* ============================================================= */ #define MAXPTRS 512 char *testtable[MAXPTRS]; /* Сгенерировать строку случайной длины со случайным содержимым */ char *wildstring(int c){ #define N 1024 char teststring[N + 1]; int len, i; char *ptr; len = rand() % N; for(i=0; i < len; i++) teststring[i] = c; teststring[len] = '\0'; ptr = (char *) MEMmalloc(len + 1); if(ptr){ strcpy(ptr, teststring); } else printf("NULL wildstring()\n"); return ptr; } int main(int ac, char *av[]){ int ilen, len, n, i; srand(time(NULL)); for(n=0; n < MAXPTRS; n++) testtable[n] = wildstring('A'); #define DAMAGE (MAXPTRS/3*2-1) #ifdef DAMAGE /* Навести порчу */ len = strlen(testtable[DAMAGE]); testtable[DAMAGE][len+1] = 'x'; testtable[DAMAGE][-2] = 'y'; printf("ptr=%p len=%d\n", testtable[DAMAGE], len); #endif for(n=0; n < MAXPTRS/2; n++){ char *p = wildstring('B'); int length = strlen(p); char *ptr; i = rand() % MAXPTRS; /* Не забыть присвоить возвращенное realloc() значение * обратно в testtable[i] !!! */ testtable[i] = ptr = (char *) MEMrealloc(testtable[i], length + 1); if(ptr == NULL) printf("Не могу сделать realloc()\n"); else strcpy(ptr, p); #ifdef DAMAGE /* Порча */ if(n == MAXPTRS/3){ ptr[length+2] = 'z'; } #endif MEMfree(p); } for(n=0; n < MAXPTRS; n++){ if(testtable[n]) MEMfree(testtable[n]); } #ifdef DAMAGE MEMfree(testtable[DAMAGE]); #endif return 0; } /* Пример 12 */ /* Программа, совмещающая команды mv и cp. Иллюстрация работы с файлами. * Пример того, как программа может выбирать род работы * по своему названию. * Компиляция: * cc cpmv.c -o copy ; ln copy move * По мотивам книги М.Дансмура и Г.Дейвиса. */ #include <stdio.h> /* буферизованный ввод/вывод */ #include <sys/types.h> /* системные типы данных */ #include <sys/stat.h> /* struct stat */ #include <fcntl.h> /* O_RDONLY */ #include <errno.h> /* системные коды ошибок */ /* #define strrchr rindex /* для версии ДЕМОС (BSD) */ extern char *strrchr(char *, char); /* из библиотеки libc.a */ extern int errno; char MV[] = "move"; char CP[] = "copy"; #define OK 1 /* success - успех */ #define FAILED 0 /* failure - неудача */ #define YES OK #define NO 0 /* Выделить базовое имя файла: * ../wawa/xxx --> xxx * zzz --> zzz * / --> / */ char *basename( char *name ){ char *s = strrchr( name , '/' ); return (s == NULL) ? name : /* нет слэшей */ (s[1] == '\0') ? name : /* корневой каталог */ s + 1; } #define ECANTSTAT (-1) /* файл не существует */ struct ftype { unsigned type; /* тип файла */ dev_t dev; /* код устройства, содержащего файл */ ino_t ino; /* индексный узел файла на этом устройстве */ }; /* Получение типа файла */ struct ftype filetype( char *name /* имя файла */ ) { struct stat st; struct ftype f; if( stat( name, &st ) < 0 ){ f.type = ECANTSTAT; f.dev = f.ino = 0; } else { f.type = st.st_mode & S_IFMT; f.dev = st.st_dev; f.ino = st.st_ino; } return f; } /* Удаляет файлы, кроме устройств */ int unlinkd( char *name, unsigned type ) { if( type == S_IFBLK || type == S_IFCHR || type == S_IFDIR) return 0; return unlink( name ); } /* Функция нижнего уровня: копирование информации большими порциями */ int copyfile( int from, int to ) /* from - дескриптор откуда */ /* to - дескриптор куда */ { char buffer[ BUFSIZ ]; int n; /* число прочитанных байт */ while(( n = read( from, buffer, BUFSIZ )) > 0 ) /* read возвращает число прочитанных байт, * 0 в конце файла */ if( write( to, buffer, n ) != n ){ printf( "Write error.\n" ); return FAILED; } return OK; } /* Копирование файла */ int docopy(char *src, char *dst, unsigned typefrom, unsigned typeto) { int retc; int fdin, fdout; printf( "copy %s --> %s\n", src, dst ); if((fdin = open( src, O_RDONLY )) < 0 ){ printf( "Сan't read %s\n", src ); return FAILED; } if((fdout = creat( dst, 0644 )) < 0 ){ /* rw-r--r-- */ printf( "Can't create %s\n", dst ); return FAILED; } retc = copyfile( fdin, fdout ); close( fdin ); close( fdout ); return retc; } /* Переименование файла. Вернуть OK, если удачно, FAILED - неудачно */ int mlink(char *src, char *dst, unsigned typefrom, unsigned typeto) { switch( typefrom ){ case S_IFDIR: /* переименование каталога */ printf( "rename directory %s --> %s\n", src, dst ); if( access( dst, 0 ) == 0 ){ /* 0 - проверить существование файла */ printf( "%s exists already\n", dst ); /* файл уже существует */ return FAILED; } if( link( src, dst ) < 0 ){ printf( "Can't link to directory %s\n", dst ); perror( "link" ); /* Возможно, что для выполнения link() для каталогов, * программа должна обладать правами суперпользователя. */ return FAILED; } unlink( src ); return OK; default: /* dst - не существует или обычный файл */ printf( "move %s --> %s\n", src, dst ); unlinkd( dst, typeto ); /* зачищаем место, т.к. link() * отказывается выполняться, если * файл dst уже существует (errno==EEXIST). */ if( link( src, dst ) < 0 ) return FAILED; unlinkd( src, typefrom ); /* удаляем старый файл */ return OK; } } /* Если не получилось связать файл при помощи link() - следует * скопировать файл в указанное место, а затем уничтожить старый файл. */ int mcopy(char *src, char *dst, unsigned typefrom, unsigned typeto) { if( typefrom == S_IFDIR ) return FAILED; /* каталог не копируем, поскольку непосредственная запись * в каталог (как целевой файл) разрешена только ядру ОС. */ return docopy( src, dst, typefrom, typeto ); } /* Переименование файла */ int domove(char *src, char *dst, unsigned typefrom, unsigned typeto) { switch( typefrom ){ default: if( ! mlink( src, dst, typefrom, typeto)){ if( ! mcopy( src, dst, typefrom, typeto)){ printf( "Can't move %s\n", src ); return FAILED; } else unlinkd( src, typefrom ); /* стереть старый */ } break; case S_IFDIR: /* каталог переименовываем в каталог */ if( ! strcmp( ".", basename(src))){ printf( "impossible to move directory \".\"\n" ); return FAILED; } if( ! mlink( src, dst, typefrom, typeto )){ if( errno == EXDEV ) printf( "No cross device directory links\n" ); return FAILED; } break; case ECANTSTAT: printf( "%s does not exist\n", src ); return FAILED; } return OK; /* okay */ } int docpmv( char *src, /* файл-источник */ char *dst, /* файл-получатель */ struct ftype typeto, /* тип файла-получателя */ int cp, /* 0 - переименование, 1 - копирование */ int *confirm /* запрашивать подтверждение на перезапись ? */ ){ struct ftype typefrom; /* тип источника */ char namebuf[BUFSIZ]; /* новое имя получателя (если надо) */ typefrom = filetype(src); if(typefrom.type == ECANTSTAT){ /* не существует */ printf("%s does not exist.\n", src); return FAILED; } if( typefrom.type != S_IFDIR && typeto.type == S_IFDIR ){ /* файл в каталоге dst */ sprintf(namebuf, "%s/%s", dst, basename(src)); typeto = filetype(dst = namebuf); } if(typefrom.dev == typeto.dev && typefrom.ino == typeto.ino){ /* Нельзя копировать файл сам в себя */ printf("%s and %s are identical.\n", src, dst); return OK; /* так как файл уже есть - считаем это удачей */ } /* если получатель уже существует, то * запросить подтверждение на перезапись */ if(*confirm && typeto.type == S_IFREG){ char answer[40]; printf("%s already exists. Overwrite (y/n/all) ? ", dst); fflush(stdout); switch( *gets(answer)){ case 'n': default: return OK; /* ничего не делать */ case 'y': break; case 'a': *confirm = NO; /* дальше - без запросов */ break; } } return cp ? docopy(src, dst, typefrom.type, typeto.type) : domove(src, dst, typefrom.type, typeto.type) ; } void main(int argc, char *argv[]) { char *cmd; int cp, i, err, confirm = YES; struct ftype typeto; /* тип файла-получателя */ if( argc < 3 ) { printf( "Usage: %s source... destination\n", argv[0] ); exit(1); /* ненулевой код возврата сигнализирует об ошибке */ } /* выделяем базовое имя программы. */ cmd = basename( argv[0] ); if ( !strcmp( cmd, CP )) cp = 1; else if( !strcmp( cmd, MV )) cp = 0; else{ printf( "%s - wrong program name.\n", cmd ); exit(2); } typeto = filetype( argv[argc-1] ); if(cp && typeto.type != S_IFDIR && typeto.type != S_IFBLK && typeto.type != S_IFCHR && argc > 3){ printf("Group of files can be copied " "to the directory or device only.\n"); exit(3); } if(!cp && typeto.type != S_IFDIR && argc > 3){ printf("Group of files can be moved " "to the directory only.\n"); exit(4); } for(err=0, i=1; i < argc-1; i++) err += ! docpmv(argv[i], argv[argc-1], typeto, cp, &confirm); exit(err); /* 0, если не было ошибок */ } /* Пример 13 */ /* Обход дерева каталогов в MS DOS при помощи смены текущего каталога. * Аналог ls -R в UNIX. По аналогичному алгоритму работает программа * find . -print (напишите команду find, используя match()) */ #define STYLE2 #include <stdio.h> #include <stdlib.h> #include <dir.h> #include <dos.h> #include <alloc.h> /* для malloc() */ #include <string.h> /* strchr(), strrchr(), strcpy(), ... */ /* прототипы */ char *strend(char *s); char *strdup(const char *s); void action(int, char **); void main(int, char **); int listdir(char *); void printdir(int n); #ifdef STYLE2 void lookdir(char *s, int ac, char **av, register int level); #else void lookdir(char *s, int ac, char **av); #endif char root[256]; /* имя стартового каталога */ char cwd[256]; /* полное имя текущего каталога */ char *strend(register char *s){ while(*s)s++; return s; } char *strdup(const char *s){ /* прототип malloc в <stdlib.h> */ char *p = (char *) malloc(strlen(s) + 1); if(p) strcpy(p, s); return p; } stop(){ /* Реакция на control/break */ chdir( root ); /* Это необходимо потому, что MS DOS имеет (в отличие от UNIX) понятие "текущий каталог" как глобальное для всей системы. Если мы прервем программу, то окажемся не в том каталоге, откуда начинали. */ printf( "\nInterrupted by ctrl-break\n"); return 0; /* exit */ } void main(int argc, char **argv){ /* получить имя текущего каталога */ (void) getcwd(root, sizeof root); ctrlbrk( stop ); /* установить реакцию на ctrl/break */ #ifndef STYLE2 lookdir( "." /* корень дерева */, argc, argv ); #else /* для примера: дерево от "\\" а не от "." */ lookdir( "\\", argc, argv, 0 /* начальный уровень */ ); #endif /*STYLE2*/ chdir(root); /* вернуться в исх. каталог */ } # ifndef STYLE2 void lookdir(char *s, int ac, char **av){ static int level = 0; /* уровень рекурсии */ # else void lookdir(char *s, int ac, char **av, register int level){ # endif /*STYLE2*/ struct ffblk dblk, *psd = &dblk; register done; if( chdir(s) < 0 ){ /* войти в каталог */ printf( "Cannot cd %s\n", s ); return; } else if (level == 0){ /* верхний уровень */ (void) getcwd(cwd, sizeof cwd); /* получить полное имя корня поддерева */ } action(ac, av); /* искать имена каталогов, удовлетворяющие шаблону "*" */ /* (не в алфавитном порядке !) */ done = findfirst("*.", psd, FA_DIREC); while( !done ){ if((psd->ff_attrib & FA_DIREC) && psd->ff_name[0] != '.' ){ /* Видим каталог: войти в него! */ char *tail = strend(cwd); char *addplace; if( tail[-1] == '\\' ){ addplace = tail; }else{ *tail = '\\'; addplace = tail+1; } strcpy(addplace, psd->ff_name); #ifndef STYLE2 level++; lookdir( psd->ff_name, ac, av ); level--; #else lookdir( psd->ff_name, ac, av, level+1 ); #endif *tail = '\0'; } /* Искать следующее имя. Информация о точке, где был * прерван поиск, хранится в dblk */ done = findnext(psd); } if( level ) chdir( ".." ); /* выйти вверх */ } /* Выполнить действия в каталоге */ void action(int ac, char **av){ extern int busy; busy = 0; if( ac == 1 ) listdir( "*.*" ); else{ av++; while( *av ) listdir( *av++ ); } printdir( busy ); } #define MAXF 400 struct fst{ char *name; long size; short attr; } files[MAXF]; int busy; /* сколько имен собрано */ /* Собрать имена, удовлетворяющие шаблону. */ int listdir( char *picture ){ int done, n; struct ffblk dentry; for(n=0, done=findfirst(picture, &dentry,0xFF /* все типы */); busy < MAXF && !done ; done = findnext( &dentry )){ files[busy].name = strdup(dentry.ff_name); files[busy].size = dentry.ff_fsize; files[busy].attr = dentry.ff_attrib; n++; busy++; } return n; } /* int cmp(struct fst *a, struct fst *b) */ /* новые веяния в Си требуют такого прототипа: */ int cmp(const void *a, const void *b){ return strcmp(((struct fst *) a) -> name, ((struct fst *) b) -> name ); } /* отсортировать и напечатать */ void printdir(int n){ register i; struct fst *f; qsort( files, n, sizeof files[0], cmp ); printf( "Directory %s\n", cwd ); for( i=0, f = files; i < n; i++, f++ ) printf("\t%-16s\t%10ld\t%c%c%c%c%c%c\n", f->name, f->size, f->attr & FA_DIREC ? 'd':'-', /* directory */ f->attr & FA_RDONLY ? 'r':'-', /* read only */ f->attr & FA_HIDDEN ? 'h':'-', /* hidden */ f->attr & FA_SYSTEM ? 's':'-', /* system */ f->attr & FA_LABEL ? 'l':'-', /* volume label */ f->attr & FA_ARCH ? 'a':'-' /* archive */ ), free(f->name); putchar('\n'); } /* Пример 14 */ /* Демонстрация работы с longjmp/setjmp и сигналами */ /* По мотивам книги М.Дансмура и Г.Дейвиса. */ #include <stdio.h> #include <fcntl.h> #include <signal.h> #include <setjmp.h> /*#define IGN*/ /* потом откомментируйте эту строку */ jmp_buf cs_stack; /* control point */ int in_cs; /* флаг, что мы в критической секции */ int sig_recd; /* флаг signal received */ /* активная задержка */ Delay(){ int i; for( i=0; i < 10000; i++ ){ i += 200; i -= 200; } } interrupt( code ){ fprintf( stderr, "\n\n***\n" ); fprintf( stderr, "*** Обрабатываем сигнал (%s)\n", code == 1 ? "разрешенный" : "отложенный" ); fprintf( stderr, "***\n\n" ); } /* аргумент реакции на сигнал - номер сигнала (подставляется системой) */ void mexit( nsig ){ fprintf( stderr, "\nУбили сигналом #%d...\n\n", nsig ); exit(0); } void main(){ extern void sig_vec(); int code; int killable = 1; signal( SIGINT, mexit ); signal( SIGQUIT, mexit ); fprintf( stderr, "Данная программа перезапускается по сигналу INTR\n" ); fprintf( stderr, "Выход из программы по сигналу QUIT\n\n\n" ); fprintf( stderr, "Сейчас вы еще можете успеть убить эту программу...\n\n" ); Delay(); Delay(); Delay(); for(;;){ if( code = setjmp( cs_stack )){ /* Возвращает не 0, если возврат в эту точку произошел * по longjmp( cs_stack, code ); где code != 0 */ interrupt( code ); /* пришло прерывание */ } /* else setjmp() возвращает 0, * если это УСТАНОВКА контрольной точки (то есть * сохранение регистров SP, PC и других в буфер cs_stack), * а не прыжок на нее. */ signal( SIGINT, sig_vec ); /* вызывать по прерыванию */ if( killable ){ killable = 0; fprintf( stderr, "\7Теперь сигналы INTR обрабатываются особым образом\n\n\n" ); } body(); /* основная программа */ } } body(){ static int n = 0; int i; fprintf( stderr, "\tВошли в тело %d-ый раз\n", ++n ); ecs(); for( i=0; i < 10 ; i++ ){ fprintf( stderr, "- %d\n",i); Delay(); } lcs(); for( i=0; i < 10 ; i++ ){ fprintf( stderr, "+ %d\n",i); Delay(); } } /* запоминание полученных сигналов */ void sig_vec(nsig){ if( in_cs ){ /* we're in critical section */ #ifdef IGN signal( SIGINT, SIG_IGN ); /* игнорировать */ fprintf( stderr, "Дальнейшие прерывания будут игнорироваться\n" ); #else signal( SIGINT, sig_vec ); fprintf( stderr, "Дальнейшие прерывания будут подсчитываться\n" ); #endif fprintf( stderr, "Получен сигнал и отложен\n" ); sig_recd++ ; /* signal received */ /* пометить, что сигнал пришел */ }else{ signal( SIGINT, sig_vec ); fprintf( stderr, "Получен разрешенный сигнал: прыгаем на рестарт\n" ); longjmp( cs_stack, 1); } } ecs(){ /* enter critical section */ fprintf( stderr, "Откладываем прерывания\n" ); sig_recd = 0; in_cs = 1; } lcs(){ /* leave critical section */ fprintf( stderr, "Разрешаем прерывания\n" ); in_cs = 0; if( sig_recd ){ fprintf( stderr, "Прыгаем на рестарт, т.к. есть отложенный сигнал (%d раз)\n", sig_recd ); sig_recd = 0; signal( SIGINT, sig_vec ); longjmp( cs_stack, 2); } } /* Пример 15 */ /* Команда для изменения скорости обмена в линии (baud).*/ /* Пример вызова в XENIX: baud /dev/tty1a 9600 */ /* /dev/tty1a - это коммуникационный последов. порт #1 */ /* Про управление модами терминала смотри man termio */ #include <fcntl.h> #include <termio.h> struct termio old, new; int fd = 2; /* stderr */ struct baudrate{ int speed; char *name;} br[] = { { B0, "HANGUP" }, { B1200, "1200" }, { B9600, "9600" }, { B600, "600" }, { B2400, "2400" }, { EXTA, "19200" }, }; #define RATES (sizeof br/sizeof br[0]) main(ac, av) char *av[]; { register i; char *newbaud; if( ac == 3 ){ if((fd = open(av[1], O_RDWR)) < 0 ){ printf("Не могу открыть %s\n", av[1]); exit(1); } newbaud = av[2]; } else newbaud = av[1]; if( ioctl(fd, TCGETA, &old) < 0 ){ printf("Попытка управлять не терминалом и не портом.\n"); exit(2); } if(newbaud == (char*)0) newbaud = "<не задано>"; new=old; for(i=0; i < RATES; i++) if((old.c_cflag & CBAUD) == br[i].speed) goto ok; printf("Неизвестная скорость\n"); exit(3); ok: printf("Было %s бод\n", br[i].name); for(i=0; i < RATES; i++) if( !strcmp(newbaud, br[i].name)){ new.c_cflag &= ~CBAUD; /* побитное "или" всех масок B... */ new.c_cflag |= br[i].speed; if( ioctl(fd, TCSETA, &new) < 0) perror("ioctl"); /* Скорость обмена может не измениться, если терминал * не открыт ни одним процессом (драйвер не инициализирован). */ exit(0); } printf("Неверная скорость %s\n", newbaud); exit(4); } /* Пример 16 */ /*#!/bin/cc -DUSG wins.c -o wins -lncurses -lx Просмотр двух файлов в перекрывающихся окнах. Редактирование содержимого окон. */ /* _______________________ файл wcur.h __________________________ */ #include "curses.h" /* Макросы, зависимые от реализации curses */ /* число колонок и строк в окне: */ # define wcols(w) ((w)-> _maxx+1 ) # define wlines(w) ((w)-> _maxy+1 ) /* верхний левый угол окна: */ # define wbegx(w) ((w)-> _begx ) # define wbegy(w) ((w)-> _begy ) /* координаты курсора в окне: */ # define wcurx(w) ((w)-> _curx ) # define wcury(w) ((w)-> _cury ) /* доступ к памяти строк окна: */ # define wtext(w) ((w)-> _line) /* chtype **_line; */ /* в других реализациях: ((w)-> _y) */ /* Псевдографика: Для curses Для IBM PC MS DOS */ #define HOR_LINE '\200' /* 196 */ #define VER_LINE '\201' /* 179 */ #define UPPER_LEFT '\210' /* 218 */ #define LOWER_LEFT '\202' /* 192 */ #define UPPER_RIGHT '\212' /* 191 */ #define LOWER_RIGHT '\204' /* 217 */ #define LEFT_JOIN '\205' /* 195 */ #define RIGHT_JOIN '\207' /* 180 */ #define TOP_JOIN '\211' /* 194 */ #define BOTTOM_JOIN '\203' /* 193 */ #define MIDDLE_CROSS '\206' /* 197 */ #define BOX '\272' /* 219 */ #define BOX_HATCHED '\273' /* 177 */ #define LABEL '\274' /* 3 */ #define RIGHT_TRIANG '\234' /* 16 */ #define LEFT_TRIANG '\235' /* 17 */ #define YES 1 #define NO 0 #define MIN(a,b) (((a) < (b)) ? (a):(b)) #define MAX(a,b) (((a) > (b)) ? (a):(b)) #define A_ITALICS A_ALTCHARSET /* в этой версии curses-а - курсив */ #ifndef ESC # define ESC '\033' /* escape */ #endif #define ctrl(c) (c & 037) /* перерисовка экрана */ #define RedrawScreen() { vidattr(curscr->_attrs = A_NORMAL); \ wrefresh(curscr); } /* curscr - служебное окно - копия текущего состояния экрана дисплея * для сравнения со сформированным НОВЫМ образом экрана - newscr. * Поле _attrs в структуре окна содержит текущие атрибуты окна, * именно это поле изменяется wattrset(), wattron(), wattroff(); */ /* _______________________ файл wins.c __________________________ */ #include "wcur.h" #include <signal.h> WINDOW *wbase1, *wbase2; /* окна рамки (фоновые окна) */ WINDOW *w1, *w2; /* окна для текста */ /* Размеры и расположение окон */ /* COLS - предопределенная переменная: число колонок */ /* LINES - // - : число строк на экране */ #define W1ysize (LINES/2) /* высота */ #define W1xsize (COLS/3*2) /* ширина */ #define W1y 5 /* y верхнего левого угла на экране */ #define W1x 20 /* x верхнего левого угла на экране */ #define W2ysize (LINES/2) #define W2xsize (COLS/3*2) #define W2y 10 #define W2x 5 FILE *fp1, *fp2; /* просматриваемые файлы */ /* Завершить работу */ void die(sig){ /* аргумент - номер сигнала */ /* Восстановление режимов терминала */ echo(); /* эхо-отображение вводимых букв */ nocbreak(); /* ввод с системным редактированием строки */ mvcur( -1, -1, LINES-1, 0 ); /* курсор в нижн. левый угол */ endwin(); /* окончание работы с curses-ом */ putchar('\n'); exit(sig); /* завершение работы с кодом sig. 0 - успешно */ } int run; void stop(nsig){ signal(SIGINT, SIG_IGN); run = 0; beep(); } char label[3][5] = { /* Демонстрация псевдографики */ { UPPER_LEFT, TOP_JOIN, UPPER_RIGHT, HOR_LINE, '\0' }, { LEFT_JOIN, MIDDLE_CROSS, RIGHT_JOIN, VER_LINE, '\0' }, { LOWER_LEFT, BOTTOM_JOIN, LOWER_RIGHT, BOX, '\0' } }; /* Нарисовать рамку, название и фон окна */ wborder( w, name ) WINDOW *w; char *name; { register i, j; for(i=1; i < wlines(w)-1; i++ ){ /* поставить курсор и выдать символ */ mvwaddch(w, i, 0, VER_LINE ); /* mvwaddch(w,y,x,c) = wmove(w,y,x); waddch(w,c); */ /* wmove(w,y,x) - логич. курсор в позицию (y,x) */ /* waddch(w,c) - выдать символ в позиции курсора, продвинуть курсор. Аналог putchar */ mvwaddch(w, i, wcols(w)-1, VER_LINE ); } for(j=1; j < wcols(w)-1; j++ ){ mvwaddch(w, 0, j, HOR_LINE ); mvwaddch(w, wlines(w)-1, j, HOR_LINE ); } /* Углы */ mvwaddch(w, 0, 0, UPPER_LEFT); mvwaddch(w, wlines(w)-1, 0, LOWER_LEFT); mvwaddch(w, wlines(w)-1, wcols(w)-1, LOWER_RIGHT); mvwaddch(w, 0, wcols(w)-1, UPPER_RIGHT); /* Рисуем заголовки вверху и внизу на рамке. * Заголовки выдаем в центре рамки. */ if( (j = (wcols(w) - strlen(name))/2 ) > 0 ){ /* логический курсор - в 0 строку, позицию j */ wmove(w, 0, j); /* задать режимы выделений */ wattrset( w, A_BOLD | A_BLINK | A_REVERSE ); waddstr( w, name ); /* выдать строку в окно */ wmove( w, wlines(w)-1, j); wattrset( w, A_ITALICS | A_STANDOUT ); waddstr ( w, name ); wattrset( w, A_NORMAL ); /* нормальные атрибуты */ } } /* режим редактирования текста в окнах */ int mode = 0; /* 0 - замена, 1 - вставка */ main( ac, av ) char **av; { char buffer[512]; int need1, need2; int c; void (*save)(); WINDOW *w; /* активное окно */ if( ac < 3 ){ fprintf( stderr, "Вызов: %s file1 file2\n", av[0] ); exit( 1 ); } if((fp1 = fopen( av[1], "r" )) == NULL ){ fprintf( stderr, "Не могу читать %s\n", av[1] ); exit( 2 ); } if((fp2 = fopen( av[2], "r" )) == NULL ){ fprintf( stderr, "Не могу читать %s\n", av[2] ); exit( 2 ); } /* Инициализировать curses */ initscr(); signal( SIGINT, die ); /* по ctrl/C - умереть */ signal( SIGQUIT,die ); /* Создать окна */ /* высота ширина Y и X верх.левого угла */ wbase1 = newwin( W1ysize, W1xsize, W1y, W1x); if( wbase1 == NULL ){ fprintf( stderr, "Не могу создать wbase1\n" ); goto bad; } wbase2 = newwin( W2ysize, W2xsize, W2y, W2x); if( wbase2 == NULL ){ fprintf( stderr, "Не могу создать wbase2\n" ); goto bad; } /* Создать подокна для текста */ /* база высота ширина Y угла X угла */ w1 = subwin( wbase1, W1ysize - 2, W1xsize - 2, W1y+1, W1x+1); w2 = subwin( wbase2, W2ysize - 2, W2xsize - 2, W2y+1, W2x+1); scrollok( w1, TRUE ); /* разрешить роллирование окон */ scrollok( w2, TRUE ); wattrset( w2, A_REVERSE ); /*установить атрибуты текста в окнах*/ wattrset( stdscr, A_STANDOUT ); wborder( wbase1, av[1] ); wborder( wbase2, av[2] ); /* рамки */ werase( w1 ); werase( w2 ); /* очистить окна */ /* фон экрана */ werase( stdscr ); /* функции без буквы w... работают с окном stdscr (весь экран) */ for(c=0; c < 3; c++) mvwaddstr(stdscr, c, COLS-5, &label[c][0]); move( 1, 10 ); addstr( "F1 - переключить окна" ); mvaddstr( 2, 10, "F5 - переключить режим вставки/замены" ); move( 3, 10 ); printw( "F%d - удалить строку, F%c - вставить строку", 7, '8' ); mvwprintw(stdscr, 4,10, "ESC - выход, CTRL/C - прервать просмотр"); /* wprintw(w, fmt, ...) - аналог printf для окон */ /* В нижний правый угол экрана ничего не выводить: * на некоторых терминалах это роллирует экран и тем самым * портит нам картинку. */ wattrset( stdscr, A_NORMAL ); wmove( stdscr, LINES-1, COLS-1 ); waddch( stdscr, ' ' ); wnoutrefresh( stdscr ); /* виртуальное проявление окна. */ run = need1 = need2 = 1; /* оба файла не достигли конца */ /* прерывать просмотр по CTRL/C */ save = signal(SIGINT, stop); while( run && (need1 || need2)){ if( need1 ){ /* прочесть строку из первого файла */ if( fgets( buffer, sizeof buffer, fp1 ) == NULL ) need1 = 0; /* конец файла */ else{ /* выдать строку в окно */ waddstr( w1, buffer ); } } if( need2 ){ /* прочесть строку из второго файла */ if( fgets( buffer, sizeof buffer, fp2 ) == NULL ) need2 = 0; /* конец файла */ else{ waddstr( w2, buffer ); /* wnoutrefresh( w2 ); */ } } /* Проявить w1 поверх w2 */ touchwin( wbase2 ); wnoutrefresh( wbase2 ); touchwin( w2 ); wnoutrefresh( w2 ); touchwin( wbase1 ); wnoutrefresh( wbase1 ); touchwin( w1 ); wnoutrefresh( w1 ); /* touchwin - пометить окно как целиком измененное. * wnoutrefresh - переписать изменения в новый образ * экрана в памяти. */ /* Проявить изображение на экране терминала * (вывести новый образ экрана). При этом выводятся * лишь ОТЛИЧИЯ от текущего содержимого экрана * (с целью оптимизации). */ doupdate(); } fclose(fp1); fclose(fp2); /* восстановить спасенную реакцию на сигнал */ signal(SIGINT, save); /* Редактирование в окнах */ noecho(); /* выкл. эхо-отображение */ cbreak(); /* немедленный ввод набранных клавиш * (без нажатия кнопки \n) */ keypad( w1, TRUE ); /* распознавать функц. кнопки */ keypad( w2, TRUE ); scrollok( w1, FALSE ); /* запретить роллирование окна */ w = w1; /* текущее активное окно */ for( ;; ){ int y, x; /* координаты курсора в окне */ wrefresh( w ); /* обновить окно. Примерно соответствует * wnoutrefresh(w);doupdate(); */ c = wgetch( w ); /* ввести символ с клавиатуры */ /* заметим, что в режиме noecho() символ не * отобразится в окне без нашей помощи ! */ getyx( w, y, x ); /* узнать координаты курсора в окне */ /* не надо &y &x, т.к. это макрос, превращающийся в пару присваиваний */ switch( c ){ case KEY_LEFT: /* шаг влево */ waddch( w, '\b' ); break; case KEY_RIGHT: /* шаг вправо */ wmove( w, y, x+1 ); break; case KEY_UP: /* шаг вверх */ wmove( w, y-1, x ); break; case KEY_DOWN: /* шаг вниз */ wmove( w, y+1, x ); break; case KEY_HOME: /* в начало строки */ case KEY_LL: /* KEY_END в конец строки */ { int xbeg, xend; wbegend(w, &xbeg, &xend); wmove(w, y, c==KEY_HOME ? xbeg : xend); break; } case '\t': /* табуляция */ x += 8 - (x % 8); if( x >= wcols( w )) x = wcols(w)-1; wmove(w, y, x); break; case KEY_BACKTAB: /* обратная табуляция */ x -= 8 - (x % 8); if( x < 0 ) x = 0; wmove( w, y, x ); break; case '\b': /* забой */ case KEY_BACKSPACE: case '\177': if( !x ) break; /* ничего */ wmove( w, y, x-1 ); /* and fall to ... (и провалиться в) */ case KEY_DC: /* удаление над курсором */ wdelch( w ); break; case KEY_IC: /* вставка пробела над курсором */ winsch( w, ' ' ); break; case KEY_IL: case KEY_F(8): /* вставка строки */ winsertln( w ); break; case KEY_DL: /* удаление строки */ case KEY_F(7): wdeleteln( w ); break; case ESC: /* ESC - выход */ goto out; case KEY_F(1): /* переключение активного окна */ if( w == w1 ){ touchwin( wbase2 ); wnoutrefresh( wbase2 ); touchwin( w2 ); wnoutrefresh( w2 ); w = w2; } else { touchwin( wbase1 ); wnoutrefresh( wbase1 ); touchwin( w1 ); wnoutrefresh( w1 ); w = w1; } break; case KEY_F(5): /* переключение режима редактирования */ mode = ! mode; break; case ctrl('A'): /* перерисовка экрана */ RedrawScreen(); break; case '\n': case '\r': waddch( w, '\n' ); break; default: /* добавление символа в окно */ if( c >= 0400 ){ beep(); /* гудок */ break; /* функц. кнопка - не буква */ } if( mode ){ winsch( w, ' ' ); /* раздвинь строку */ } waddch( w, c ); /* выдать символ в окно */ break; } } out: wrefresh( w ); wsave(w); bad: die(0); /* вызов без возврата */ } /* Сохранить содержимое окна в файл, обрезая концевые пробелы */ wsave(w) WINDOW *w; { FILE *fp = fopen("win.out", "w"); register int x,y, lastnospace; int xs, ys; getyx(w, ys, xs); for( y=0; y < wlines(w); y++ ){ /* поиск последнего непробела */ for( lastnospace = (-1), x=0; x < wcols(w); x++ ) /* читаем символ из координат (x,y) окна */ if((mvwinch(w,y,x) & A_CHARTEXT) != ' ' ) lastnospace = x; /* запись в файл */ for( x=0 ; x <= lastnospace; x++ ){ wmove(w,y,x); putc( winch(w) & A_CHARTEXT, fp ); } putc( '\n', fp ); } fclose(fp); wmove(w, ys, xs ); /* вернуть курсор на прежнее место */ } /* На самом деле * winch(w) = wtext(w)[ wcury(w) ][ wcurx(w) ]; * Предложим еще один, более быстрый способ чтения памяти окна * (для ЗАПИСИ в окно он непригоден, т.к. curses еще * специальным образом помечает ИЗМЕНЕННЫЕ области окон). */ /* Найти начало и конец строки */ int wbegend(w, xbeg, xend) WINDOW *w; int *xbeg, *xend; { /* Тип chtype: 0xFF - код символа; 0xFF00 - атрибуты */ chtype ch, *thisline = wtext(w)[ wcury(w) ]; register x, notset = TRUE; *xbeg = *xend = 0; for(x=0; x < wcols(w); x++) /* & A_CHARTEXT игнорирует атрибуты символа */ if(((ch=thisline[x]) & A_CHARTEXT) != ' '){ if((*xend = x+1) >= wcols(w)) *xend = wcols(w) - 1; if(notset){ notset = FALSE; *xbeg=x; } } return (*xend - *xbeg); } /* Пример 17 */ /* Window management: "стопка" окон * cc -DTEST -DUSG w.c -lncurses -lx * *____ Файл w.h для Пример 17, Пример 19, Пример 21, Пример 23 _____ */ #include "wcur.h" /* Тот же, что в Пример 16 */ extern int botw, topw; extern struct WindowList { /* Элемент списка окон */ WINDOW *w; /* окно */ int next; /* следующее окно в списке */ char busy; /* 0:слот свободен, 1:окно видимо, -1:окно спрятано */ } wins[]; /* значения поля busy: */ #define W_VISIBLE 1 /* окно видимо */ #define W_FREE 0 /* слот таблицы свободен */ #define W_HIDDEN (-1) /* окно спрятано */ #define EOW (-1) #define WIN(n) wins[n].w /* если совсем нет видимых окон... */ #define TOPW (topw != EOW ? WIN(topw) : stdscr) #define BOTW (botw == EOW ? stdscr : WIN(botw)) #define MAXW 15 #define iswindow(n) wins[n].busy int RaiseWin (WINDOW *w); void PopWin (); void DestroyWin(WINDOW *w, int destroy); int HideWin (WINDOW *w); #define KillWin(w) DestroyWin(w, TRUE) #define DropWin(w) DestroyWin(w, FALSE) #define PushWin(w) RaiseWin(w) #define BAR_HOR 01 /* окно имеет горизонтальный scroll bar */ #define BAR_VER 02 /* окно имеет вертикальный scroll bar */ #define DX 2 /* отступ от краев окна */ #define BARWIDTH 2 /* ширина scroll bar-а */ #define BARHEIGHT 1 /* высота */ /* Вычисление координат строки выбора в окне */ #define WY(title, y) ((y) + (title ? 3 : 1)) #define WX(x) ((x) + 1 + DX) #define XEND(w,scrollok) (wcols(w)-((scrollok & BAR_VER) ? BARWIDTH+2 : 1)) void whorline (WINDOW *w, int y, int x1, int x2); void wverline (WINDOW *w, int x, int y1, int y2); void wbox (WINDOW *w, int x1, int y1, int x2, int y2); void wborder (WINDOW *w); void wboxerase (WINDOW *w, int x1, int y1, int x2, int y2); void WinBorder (WINDOW *w, int bgattrib, int titleattrib, char *title, int scrollok, int clear); void WinScrollBar(WINDOW *w, int whichbar, int n, int among, char *title, int bgattrib); /* Спасение/восстановление позиции курсора */ typedef struct { int x, y; } Point; #define SetPoint(p, yy, xx) { (p).x = (xx); (p).y = (yy);} #define GetBack(p, w) wmove((w), (p).y, (p).x) /* _______________________ файл w.c _____________________________ */ /* УПРАВЛЕНИЕ ПОРЯДКОМ ОКОН НА ЭКРАНЕ */ /* ______________________________________________________________ */ #include "w.h" int botw = EOW, topw = EOW; /* нижнее и верхнее окна */ struct WindowList wins[MAXW]; /* список управляемых окон */ /* Прочесть символ из окна, проявив окно (если оно не спрятано) */ int WinGetch (WINDOW *win) { register n, dorefr = YES; if(botw != EOW) for(n=botw; n != EOW; n=wins[n].next) if(wins[n].w == win){ if(wins[n].busy == W_HIDDEN) dorefr = NO; /* спрятано */ break; } if( dorefr ) wrefresh (win); /* проявка */ else doupdate (); for(;;){ n = wgetch (win); /* собственно чтение */ if( n == ctrl('A')){ RedrawScreen(); continue; } return n; } } /* Вычислить новое верхнее окно */ static void ComputeTopWin(){ register n; if(botw == EOW) topw = EOW; /* список стал пуст */ else{ /* ищем самое верхнее видимое окно */ for(topw = EOW, n=botw; n != EOW; n=wins[n].next) /* спрятанное окно не может быть верхним */ if( wins[n].busy == W_VISIBLE) topw = n; /* Может совсем не оказаться видимых окон; тогда * topw == EOW, хотя botw != EOW. Макрос TOPW предложит * в качестве верхнего окна окно stdscr */ } } /* Виртуально перерисовать окна в списке в порядке снизу вверх */ static void WinRefresh(){ register nw; /* чистый фон экрана */ touchwin(stdscr); wnoutrefresh(stdscr); if(botw != EOW) for(nw=botw; nw != EOW; nw=wins[nw].next) if(wins[nw].busy == W_VISIBLE){ touchwin(wins[nw].w); wnoutrefresh(wins[nw].w); } } /* Исключить окно из списка не уничтожая ячейку */ static int WinDelList(WINDOW *w){ register nw, prev; if(botw == EOW) return EOW; /* список пуст */ for(prev=EOW, nw=botw; nw != EOW; prev=nw, nw=wins[nw].next) if(wins[nw].w == w){ if(prev == EOW) botw = wins[nw].next; /* было дно стопки */ else wins[prev].next = wins[nw].next; return nw; /* номер ячейки в таблице окон */ } return EOW; /* окна не было в списке */ } /* Сделать окно верхним, если его еще не было в таблице - занести */ int RaiseWin(WINDOW *w){ int nw, n; if((nw = WinDelList(w)) == EOW){ /* не было в списке */ for(nw=0; nw < MAXW; nw++) /* занести в таблицу */ if( !iswindow(nw)){ wins[nw].w = w; break; } if(nw == MAXW){ beep(); return EOW; } /* слишком много окон */ } /* поместить окно nw на вершину списка */ if(botw == EOW) botw = nw; else{ for(n = botw; wins[n].next != EOW; n=wins[n].next); wins[n].next = nw; } wins[nw].busy = W_VISIBLE; /* окно видимо, слот занят */ wins[topw = nw].next = EOW; WinRefresh(); return nw; } /* Удалить окно из списка и (возможно) уничтожить */ /* Окно при этом исчезнет с экрана */ void DestroyWin(WINDOW *w, int destroy){ int nw; if((nw = WinDelList(w)) != EOW){ /* окно было в списке */ ComputeTopWin(); wins[nw].busy = W_FREE; /* ячейка свободна */ wins[nw].w = NULL; } if(destroy) delwin(w); /* уничтожить curses-ное окно */ WinRefresh(); } void PopWin(){ KillWin(TOPW); } /* Спрятать окно, и при этом сделать его самым нижним. */ int HideWin(WINDOW *w){ register nw, prev; if(botw == EOW) return EOW; /* список пуст */ for(prev = EOW, nw = botw; nw != EOW; prev = nw, nw = wins[nw].next ) if(wins[nw].w == w){ wnoutrefresh(w); /* вместо untouchwin(w); */ wins[nw].busy = W_HIDDEN; /* спрятано */ if( nw != botw ){ wins[prev].next = wins[nw].next; /* удалить из списка */ wins[nw].next = botw; botw = nw; /* на дно стопки */ } WinRefresh(); ComputeTopWin(); return nw; } return EOW; /* нет в списке */ } /* _______________ ОФОРМИТЕЛЬСКИЕ РАБОТЫ _____________________ */ /* Нарисовать горизонтальную линию */ void whorline(WINDOW *w, int y, int x1, int x2){ for( ; x1 <= x2; x1++) mvwaddch(w, y, x1, HOR_LINE); } /* Нарисовать вертикальную линию */ void wverline(WINDOW *w, int x, int y1, int y2){ for( ; y1 <= y2; y1++) mvwaddch(w, y1, x, VER_LINE); } /* Нарисовать прямоугольную рамку */ void wbox(WINDOW *w, int x1, int y1, int x2, int y2){ whorline(w, y1, x1+1, x2-1); whorline(w, y2, x1+1, x2-1); wverline(w, x1, y1+1, y2-1); wverline(w, x2, y1+1, y2-1); /* Углы */ mvwaddch (w, y1, x1, UPPER_LEFT); mvwaddch (w, y1, x2, UPPER_RIGHT); mvwaddch (w, y2, x1, LOWER_LEFT); /* Нижний правый угол нельзя занимать ! */ if(! (wbegx(w) + x2 == COLS-1 && wbegy(w) + y2 == LINES-1)) mvwaddch (w, y2, x2, LOWER_RIGHT); } /* Нарисовать рамку вокруг окна */ void wborder(WINDOW *w){ wbox(w, 0, 0, wcols(w)-1, wlines(w)-1); } /* Очистить прямоугольную область в окне */ void wboxerase(WINDOW *w, int x1, int y1, int x2, int y2){ int x, y; register i, j; getyx(w, y, x); for(i=y1; i <= y2; ++i) for(j=x1; j <= x2; j++) mvwaddch(w, i, j, ' '); wmove(w, y, x); } /* Нарисовать рамку и заголовок у окна */ void WinBorder (WINDOW *w, int bgattrib, int titleattrib, char *title, int scrollok, int clear){ register x, y; wattrset (w, bgattrib); /* задать цвет окна */ if(clear) werase(w); /* заполнить окно цветными пробелами */ wborder (w); /* нарисовать рамку вокруг окна */ if (title) { /* если есть заголовок ... */ for (x = 1; x < wcols (w) - 1; x++){ wattrset(w, bgattrib); mvwaddch (w, 2, x, HOR_LINE); /* очистка поля заголовка */ wattrset(w, titleattrib); mvwaddch (w, 1, x, ' '); } wattrset(w, bgattrib); mvwaddch (w, 2, 0, LEFT_JOIN); mvwaddch (w, 2, wcols (w) - 1, RIGHT_JOIN); wattrset (w, A_BOLD | titleattrib); mvwaddstr(w, 1, (wcols(w)-strlen(title))/2, title); wattrset (w, bgattrib); } if (scrollok & BAR_VER) { /* выделить столбец под scroll bar. */ int ystart = WY(title, 0), xend = XEND(w, scrollok); for (y = ystart; y < wlines (w) - 1; y++) mvwaddch (w, y, xend, VER_LINE); mvwaddch (w, wlines (w)-1, xend, BOTTOM_JOIN); mvwaddch (w, ystart-1, xend, TOP_JOIN); } /* затычка */ if(wcols(w)==COLS && wlines(w)==LINES){ wattrset(w, A_NORMAL); mvwaddch(w, LINES-1, COLS-1, ' '); } wattrset (w, bgattrib); } /* Нарисовать вертикальный scroll bar (горизонтальный не сделан) */ /* Написано не очень аккуратно */ void WinScrollBar(WINDOW *w, int whichbar, int n, int among, char *title, int bgattrib){ register y, i; int starty = WY(title, 0); int endy = wlines (w) - 1; int x = XEND(w, whichbar) + 1; int height = endy - starty ; if(whichbar & BAR_VER){ /* вертикальный */ wattrset (w, A_NORMAL); for (y = starty; y < endy; y++) for (i = 0; i < BARWIDTH; i++) mvwaddch (w, y, x + i, ' '); y = starty; if(among > 1) y += ((long) (height - BARHEIGHT) * n / (among - 1)); wattron(w, A_BOLD); for (i = 0; i < BARWIDTH; i++) mvwaddch (w, y, x + i, BOX); wattrset(w, bgattrib | A_BOLD ); if( wcols(w) >= 10 ) mvwprintw(w, 0, wcols(w)-9, "%03d/%03d", n+1, among); } wattrset (w, bgattrib); } #ifdef TEST main(){ WINDOW *w[5]; register i, y; initscr(); /* запустить curses */ w[0] = newwin(16, 20, 4, 43); /* создать 5 окон */ w[1] = newwin(12, 20, 7, 34); w[2] = newwin(6, 30, 3, 40); w[3] = newwin(7, 35, 12, 38); w[4] = newwin(6, 20, 11, 54); for(i=0; i < 5; i++){ keypad (w[i], TRUE); wattrset(w[i], A_REVERSE); werase(w[i]); wborder (w[i]); mvwprintw(w[i], 1, 2, "Window %d", i); RaiseWin(w[i]); /* сделать верхним окном */ } noecho(); cbreak(); /* прозрачный ввод */ for(;botw != EOW;){ int c; /* нарисовать порядок окон */ for(i=botw, y=0; y < 5; y++, i=(i==EOW ? EOW : wins[i].next)) mvprintw(8 - y, 5, i==EOW ? "~": "%d%c", i, wins[i].busy == W_HIDDEN ? 'h':' '); mvprintw(9, 5, "topw=%3d botw=%3d", topw, botw); wnoutrefresh(stdscr); /* вирт. проявка этих цифр */ c = WinGetch(TOPW); /* здесь происходит doupdate(); * и только в этот момент картинка проявляется */ switch(c){ case KEY_DC: PopWin(); break; case KEY_IC: KillWin(BOTW); break; case '0': case '1': case '2': case '3': case '4': case '5': c -= '0'; if( !iswindow(c)){ beep(); break; } RaiseWin(WIN(c)); break; case 'D': KillWin(w[2]); break; case 'h': HideWin(BOTW); break; case 'H': HideWin(TOPW); break; case ESC: goto out; default: waddch(TOPW, c & 0377); break; } } mvaddstr(LINES-2, 0, "Больше нет окон"); refresh(); out: echo(); nocbreak(); endwin(); } #endif /* Пример 18 */ /* _______________________ файл glob.h ___________________________*/ /* ПОДДЕРЖКА СПИСКА ИМЕН ФАЙЛОВ ЗАДАННОГО КАТАЛОГА */ /* ______________________________________________________________ */ #define FILF #include <sys/types.h> #include <sys/stat.h> #include <dirent.h> # define DIR_SIZE 14 extern char *malloc(unsigned); char *strdup(const char *str); extern char *getenv(); extern char *strchr(char *, char), *strrchr(char *, char); #define ISDIR(mode) ((mode & S_IFMT) == S_IFDIR) #define ISDEV(mode) ((mode & S_IFMT) & (S_IFCHR|S_IFBLK)) #define ISREG(mode) ((mode & S_IFMT) == S_IFREG) #define ISEXE(mode) ((mode & S_IFMT) == S_IFREG && (mode & 0111)) #define isdir(st) ISDIR(st.st_mode) #define isdev(st) ISDEV(st.st_mode) #define isreg(st) ISREG(st.st_mode) #define isexe(st) ISEXE(st.st_mode) #define YES 1 #define NO 0 #define I_DIR 0x01 /* это имя каталога */ #define I_EXE 0x02 /* это выполняемый файл */ #define I_NOSEL 0x04 /* строку нельзя выбрать */ #define I_SYS (I_DIR | I_EXE | I_NOSEL) /* Скопировано из treemk.c * Лучше просто написать #include "glob.h" в файле treemk.c */ #define FAILURE (-1) /* код неудачи */ #define SUCCESS 1 /* код успеха */ #define WARNING 0 /* нефатальная ошибка */ typedef struct _info { /* структура элемента каталога */ char *s; /* имя файла */ short fl; /* флаг */ union _any{ int (*act)(); /* возможно связанное действие */ char *note; /* или комментарий */ unsigned i; /* или еще какой-то параметр */ struct _info *inf; } any; /* вспомогательное поле */ #ifdef FILF /* дополнительные необязательные параметры, получаемые из stat(); */ long size; int uid, gid; unsigned short mode; #endif } Info; typedef union _any Any; extern Info NullInfo; #define MAX_ARGV 256 /* Максимальное число имен в каталоге */ typedef struct { /* Содержимое каталога name */ time_t lastRead; /* время последнего чтения каталога */ Info *files; /* содержимое каталога */ char *name; /* имя каталога */ ino_t ino; dev_t dev; /* I-узел и устройство */ char valid; /* существует ли этот каталог вообще */ short readErrors; /* != 0, если каталог не читается */ } DirContents; /* Виды сортировки имен в каталоге */ typedef enum { SORT_ASC, SORT_DESC, SORT_SUFX, SORT_NOSORT, SORT_SIZE } Sort; extern Sort sorttype; extern int in_the_root; int gcmps (const void *p1, const void *p2); Info *blkcpy(Info *v); void blkfree(Info *v); Info *glob(char **patvec, char *dirname); Info *glb(char *pattern, char *dirname); int ReadDir(char *dirname, DirContents *d); struct savech{ char *s, c; }; #define SAVE(sv, str) (sv).s = (str); (sv).c = *(str) #define RESTORE(sv) if((sv).s) *(sv).s = (sv).c /* _______________________ файл glob.c __________________________ */ #include "glob.h" int in_the_root = NO; /* читаем корневой каталог ? */ Sort sorttype = SORT_SUFX; /* сортировка имен по суффиксу */ Info NullInfo = { NULL, 0 }; /* и прочие поля = 0 (если есть) */ char *strdup(const char *s){ char *p = malloc(strlen(s)+1); if(p)strcpy(p, s); return p; } /* Содержится ли любой из символов в строке ? */ int any(register char *s, register char *p){ while( *s ){ if( strchr(p, *s)) return YES; s++; } return NO; } /* Найти последнюю точку в имени */ static char *lastpoint (char *s) { register char *last; static char no[] = ""; if((last = strchr(s, '.')) == NULL) return no; /* если имя начинается с точки - не считать ее */ return( last == s ? no : last ); } /* Сравнение строк с учетом их суффиксов */ int strsfxcmp (register char *s1, register char *s2){ char *p1, *p2, c1, c2; int code; p1 = lastpoint (s1); p2 = lastpoint (s2); if (code = strcmp (p1, p2)) return code; /* суффиксы разные */ /* иначе: суффиксы равны. Сортируем по головам */ c1 = *p1; c2 = *p2; *p1 = '\0'; *p2 = '\0'; /* временно */ code = strcmp (s1, s2); *p1 = c1; *p2 = c2; return code; } /* Функция сортировки */ int gcmps(const void *p1, const void *p2){ Info *s1 = (Info *) p1, *s2 = (Info *) p2; switch( sorttype ){ default: case SORT_ASC: return strcmp(s1->s, s2->s); case SORT_DESC: return -strcmp(s1->s, s2->s); case SORT_SUFX: return strsfxcmp(s1->s, s2->s); case SORT_NOSORT: return (-1); #ifdef FILF case SORT_SIZE: return (s1->size < s2->size ? -1 : s1->size == s2->size ? 0 : 1 ); #endif } } /* Копирование блока */ Info *blkcpy(Info *v){ register i, len; Info *vect = (Info *) malloc(((len=blklen(v)) + 1) * sizeof(Info)); for(i=0; i < len; i++ ) vect[i] = v[i]; vect[len] = NullInfo; return vect; } /* Измерение длины блока */ int blklen(Info *v){ int i = 0; while( v->s ) i++, v++; return i; } /* Очистка блока (уничтожение) */ void blkfree(Info *v){ Info *all = v; while( v->s ) free((char *) v->s ), v++; free((char *) all ); } /* Сравнение двух блоков */ int blkcmp( register Info *p, register Info *q ){ while( p->s && q->s && !strcmp(p->s, q->s) && (p->fl & I_SYS) == (q->fl & I_SYS)){ p++; q++; } if( p->s == NULL && q->s == NULL ) return 0; /* совпадают */ return 1; /* различаются */ } char globchars [] = "*?["; Info gargv[MAX_ARGV]; int gargc; static short readErrors; void greset() { gargc = 0; readErrors = 0; } /* Расширить шаблон имен файлов в сами имена */ static void globone(char *pattern, char dirname[]){ extern char *strdup(); struct stat st; DIR *dirf; struct dirent *d; if( any(pattern, globchars) == NO ){ /* no glob */ gargv[gargc] = NullInfo; gargv[gargc].s = strdup(pattern); gargc++; gargv[gargc] = NullInfo; return; } if((dirf = opendir(dirname)) == NULL){ readErrors++; goto out; } while(d = readdir(dirf)){ if(match(d->d_name, pattern)){ char fullname[512]; if( sorttype != SORT_NOSORT && !strcmp(d->d_name, ".")) continue; /* В корневом каталоге имя ".." следует пропускать */ if( in_the_root && !strcmp(d->d_name, "..")) continue; /* Проверка на переполнение */ if( gargc == MAX_ARGV - 1){ free(gargv[gargc-1].s); gargv[gargc-1].s = strdup(" Слишком много файлов!!!"); gargv[gargc-1].fl = I_SYS; break; } gargv[gargc] = NullInfo; gargv[gargc].s = strdup(d->d_name); sprintf(fullname, "%s/%s", dirname, d->d_name); if(stat(fullname, &st) < 0) gargv[gargc].fl |= I_NOSEL; else if(isdir(st)) gargv[gargc].fl |= I_DIR; else if(isexe(st)) gargv[gargc].fl |= I_EXE; #ifdef FILF gargv[gargc].size = st.st_size; gargv[gargc].uid = st.st_uid; gargv[gargc].gid = st.st_gid; gargv[gargc].mode = st.st_mode; #endif gargc++; } } closedir(dirf); out: gargv[ gargc ] = NullInfo; } /* Расширить несколько шаблонов */ Info *glob(char **patvec, char *dirname){ greset(); while(*patvec){ globone(*patvec, dirname); patvec++; } qsort(gargv, gargc, sizeof(Info), gcmps); return blkcpy(gargv); } Info *glb(char *pattern, char *dirname){ char *pv[2]; pv[0] = pattern; pv[1] = NULL; return glob(pv, dirname); } /* Прочесть содержимое каталога, если оно изменилось: * Вернуть: 0 - каталог не менялся; * 1 - изменился; * 1000 - изменился рабочий каталог (chdir); * -1 - каталог не существует; */ int ReadDir(char *dirname, DirContents *d){ struct stat st; Info *newFiles; int save = YES; /* сохранять метки у файлов ? */ int dirchanged = NO; /* сделан chdir() ? */ /* каталог мог быть удален, а мы об этом не извещены */ if( stat(dirname, &st) < 0 ){ d->valid = NO; d->lastRead = 0L; if(d->files) blkfree(d->files); d->files = blkcpy( &NullInfo ); return (-1); /* не существует */ } else d->valid = YES; /* не изменился ли адрес каталога, хранимого в *d ? */ if(d->ino != st.st_ino || d->dev != st.st_dev){ /* изменился */ d->ino = st.st_ino; d->dev = st.st_dev; save = NO; d->lastRead = 0L; dirchanged = YES; } /* не изменилось ли имя каталога ? */ if( !d->name || strcmp(d->name, dirname)){ if(d->name) free(d->name); d->name = strdup(dirname); /* save=NO; d->lastRead = 0; */ } /* проверим, был ли модифицирован каталог ? */ if( save==YES && d->files && st.st_mtime == d->lastRead ) return 0; /* содержимое каталога не менялось */ d->lastRead = st.st_mtime; newFiles = glb("*", d->name); /* прочесть содержимое каталога */ if(save == YES && d->files){ register Info *p, *q; if( !blkcmp(newFiles, d->files)){ blkfree(newFiles); return 0; /* не изменилось */ } /* иначе сохранить пометки */ for(p= d->files; p->s; p++) for(q= newFiles; q->s; ++q) if( !strcmp(p->s, q->s)){ q->fl |= p->fl & ~I_SYS; break; } } if(d->files) blkfree(d->files); d->files = newFiles; d->readErrors = readErrors; return 1 + (dirchanged ? 999:0); /* каталог изменился */ } /* Пример 19 */ /* ________________________файл menu.h __________________________ */ /* РОЛЛИРУЕМОЕ МЕНЮ */ /* _______________________________________________________________*/ #include <ctype.h> #include <sys/param.h> #define M_HOT '\\' /* горячий ключ */ #define M_CTRL '\1' /* признак горизонтальной черты */ #define MXEND(m) XEND((m)->win,(m)->scrollok) #define NOKEY (-33) /* горячего ключа нет */ #define MAXLEN MAXPATHLEN /* макс. длина имен файлов */ typedef enum { /* Коды, возвращаемые handler-ом (HandlerReply *reply) */ HANDLER_OUT = 0, /* выйти из функции выбора */ HANDLER_CONTINUE = 1, /* читать очередную букву */ HANDLER_NEWCHAR = 2, /* пойти на анализ кода handler-ом. */ HANDLER_SWITCH = 3, /* пойти на switch() */ HANDLER_AGAIN = 4 /* перезапустить всю функцию выбора */ } HandlerReply; typedef struct _Menu { /* паспорт меню */ int nitems; /* число элементов меню */ Info *items; /* сам массив элементов */ int *hotkeys; /* "горячие" клавиши */ int key; /* клавиша, завершившая выбор */ int current; /* текущая строка списка */ int shift; /* сдвиг окна от начала меню */ int scrollok; /* окно роллируемое ? */ WINDOW *win; /* окно для меню */ int left, top, height, width; /* координаты меню на экране и размер окна win */ int textwidth, textheight; /* размер подокна выбора */ int bg_attrib; /* атрибут фона окна */ int sel_attrib; /* атрибут выбранной строки */ char *title; /* заголовок меню */ Point savep; void (*showMe) (struct _Menu *m); void (*scrollBar) (struct _Menu *m, int n, int among); int *hitkeys; /* клавиши, обрабатываемые особо */ int (*handler) (struct _Menu *m, int c, HandlerReply *reply); } Menu; /* Структура окна с меню: *--------------* +0 | ЗАГОЛОВОК | +1 *-----------*--* +2 |+ стр1ааа | | +3 | стр2ббб |##| <- scroll bar шириной BARWIDTH | стр3ввв | | *___________|__* |DX| len |DX|BS| */ /* Метки у элементов меню */ #define M_BOLD I_DIR /* яркая строка */ #define M_HATCH 0x08 /* строка тусклая */ #define M_LFT 0x10 /* для использования в pulldown menu */ #define M_RGT 0x20 /* для использования в pulldown menu */ #define M_LABEL 0x40 /* строка имеет метку */ #define M_LEFT (-111) #define M_RIGHT (-112) #define TOTAL_NOSEL (-I_NOSEL) #define M_SET(m, i, flg) (((m)->items)[i]). fl |= (flg) #define M_CLR(m, i, flg) (((m)->items)[i]). fl &= ~(flg) #define M_TST(m, i, flg) ((((m)->items)[i]).fl & (flg)) #define M_ITEM(m, i) ((((m)->items)[i]).s) /* Прототипы */ int MnuInit (Menu *m); void MnuDeinit (Menu *m); void MnuDrawItem (Menu * m, int y, int reverse, int selection); int MnuNext (Menu *m); int MnuPrev (Menu *m); int MnuFirst(Menu *m); int MnuLast (Menu *m); int MnuPgUp (Menu *m); int MnuPgDn (Menu *m); int MnuThis (Menu *m); int MnuHot (Menu *m, unsigned c); int MnuName (Menu *m, char *name); void MnuDraw (Menu *m); void MnuHide(Menu *m); void MnuPointAt (Menu *m, int y); void MnuPoint (Menu *m, int line, int eraseOld); int MnuUsualSelect (Menu *m, int block); int is_in(register int c, register int s[]); char *MnuConvert (char *s, int *pos); #define M_REFUSED(m) ((m)->key < 0 || (m)->key == ESC ) #define MNU_DY 1 /* _______________________ файл menu.c __________________________ */ #include "w.h" #include "glob.h" #include "menu.h" #include <signal.h> /* ---------------- implementation module ------------------------- */ /* Не входит ли символ в специальный набор? Массив завершается (-1) */ int is_in(register int c, register int s[]){ while (*s >= 0) { if(*s == c) return YES; s++; } return NO; } char STRING_BUFFER[ MAXLEN ]; /* временный буфер */ /* Снять пометку с "горячей" клавиши. */ char *MnuConvert (char *s, int *pos){ int i = 0; *pos = (-1); while (*s) { if (*s == M_HOT) { *pos = i; s++; } else STRING_BUFFER[i++] = *s++; } STRING_BUFFER[i] = '\0'; return STRING_BUFFER; } /* Рамка вокруг окна с меню */ static void MnuWin (Menu *m) { WinBorder(m->win, m->bg_attrib, m->sel_attrib, m->title, m->scrollok, YES); } /* Нарисовать scroll bar в нужной позиции */ static void MnuWinBar (Menu *m) { WINDOW *w = m -> win; /* окно */ WinScrollBar(m->win, m->scrollok, m->current, m->nitems, m->title, m->bg_attrib); if(m->scrollBar) /* может быть еще какие-то наши действия */ m->scrollBar(m, m->current, m->nitems); } /* Роллирование меню */ /* +---+----->+-МАССИВ--+<-----+ | n|всего |;;;;;;;;;| | shift сдвиг до окна cur| | |;;;;;;;;;| | текущий| | =ОКНО============<---------| элемент| | I ;;;;;;;;; I | y строка окна 0..n-1 | | I ;;;;;;;;; I | | +------>I###:::::::::###I<--+ |h высота окна | I ;;;;;;;;; I | | =================<---------+ | |;;;;;;;;;| +----->|_________| */ static void MnuRoll (Menu *ptr, int aid, /* какой новый элемент выбрать (0..n-1) */ int *cur, int *shift, int h, /* высота окна (строк) */ int n, /* высота items[] (элементов) */ void (*go) (Menu *p, int y, int eraseOld), void (*draw) (Menu *p), int DY ) { int y = *cur - *shift; /* текущая строка окна */ int newshift; /* новый сдвиг */ int AID_UP, AID_DN; if (aid < 0 || aid >= n) return; /* incorrect */ if (y < 0 || y >= h) return; /* incorrect */ AID_UP = MIN (DY, n); AID_DN = MAX (0, MIN (n, h - 1 - DY)); if (aid < *cur && y <= AID_UP && *shift > 0) goto scroll; /* down */ if (aid > *cur && y >= AID_DN && *shift + h < n) goto scroll; /* up */ if (*shift <= aid && aid < *shift + h) { /* роллировать не надо, а просто пойти в нужную строку окна */ (*go) (ptr, aid - *shift, YES); *cur = aid; /* это надо изменять ПОСЛЕ (*go)() !!! */ return; } scroll: if (aid > *cur) newshift = aid - AID_DN; /* вверх up */ else if (aid < *cur) newshift = aid - AID_UP; /* вниз down */ else newshift = *shift; if (newshift + h > n) newshift = n - h; if (newshift < 0) newshift = 0; *shift = newshift; *cur = aid; (*draw) (ptr); /* перерисовать окно */ (*go) (ptr, aid - newshift, NO); /* встать в нужную строку окна */ } /* Инициализация и разметка меню. На входе: m->items Массив строк. m->title Заголовок меню. m->top Верхняя строка окна (y). m->left Левый край (x). m->handler Обработчик нажатия клавиш или NULL. m->hitkeys Специальные клавиши [] или NULL. m->bg_attrib Цвет фона окна. m->sel_attrib Цвет селекции. */ int MnuInit (Menu *m) { int len, pos; char *s; register i; m -> current = m -> shift = 0; m -> scrollok = m -> key = 0; if (m -> hotkeys) { /* уничтожить старые "горячие" ключи */ free ((char *) m -> hotkeys); m -> hotkeys = (int *) NULL; } /* подсчет элементов меню */ for (i = 0; M_ITEM (m, i) != (char *) NULL; i++); m -> nitems = i; /* отвести массив для "горячих" клавиш */ if (m -> hotkeys = (int *) malloc (sizeof (int) * m -> nitems)) { for (i = 0; i < m -> nitems; i++) m -> hotkeys[i] = NOKEY; } /* подсчитать ширину текста */ len = m -> title ? strlen (m -> title) : 0; for (i = 0; i < m -> nitems; i++) { if (*(s = M_ITEM (m, i)) == M_CTRL) continue; s = MnuConvert (s, &pos); if (m -> hotkeys && pos >= 0) m -> hotkeys[i] = isupper (s[pos]) ? tolower (s[pos]) : s[pos]; if ((pos = strlen (s)) > len) len = pos; } /* сформировать окно */ #define BORDERS_HEIGHT (2 + (m -> title ? 2 : 0)) #define BORDERS_WIDTH (2 + 2*DX + (m -> scrollok ? BARWIDTH + 1 : 0)) m -> height = m->nitems + BORDERS_HEIGHT; if (m -> height > LINES * 2 / 3) { /* слишком высокое меню */ m -> scrollok = BAR_VER; /* будет роллироваться */ m -> height = LINES * 2 / 3; } if((m -> width = len + BORDERS_WIDTH) > COLS ) m->width = COLS; m -> textheight = m->height - BORDERS_HEIGHT; m -> textwidth = m->width - BORDERS_WIDTH; /* окно должно лежать в пределах экрана */ if( m->top + m->height > LINES ) m->top = LINES - m->height; if( m->left + m->width > COLS ) m->left = COLS - m->width; if( m->top < 0 ) m->top = 0; if( m->left < 0 ) m->left = 0; if( m->win ){ /* уничтожить старое окно */ KillWin( m->win ); m->win = NULL; } if( m->win == NULL ){ /* создать окно и нарисовать основу */ if((m->win = newwin(m->height, m->width, m->top, m->left)) == NULL) return 0; keypad(m->win, TRUE); MnuWin(m); MnuDraw(m); /* но окно пока не вставлено в список активных окон */ } return ( m->win != NULL ); } /* Деинициализировать меню */ void MnuDeinit (Menu *m) { if( m->win ){ KillWin (m->win); m->win = NULL; } if( m->hotkeys ){ free ((char *) m -> hotkeys); m -> hotkeys = (int *) NULL; } } /* Спрятать меню */ void MnuHide (Menu *m){ if( m->win ) HideWin(m->win); } /* Зачистить место для line-той строки окна меню */ static void MnuBox (Menu *m, int line, int attr) { register WINDOW *w = m -> win; register i, xend = MXEND(m); wattrset (w, attr); for (i = 1; i < xend; i++) mvwaddch (w, line, i, ' '); /* ликвидировать последствия M_CTRL-линии */ wattrset (w, m->bg_attrib); mvwaddch (w, line, 0, VER_LINE); mvwaddch (w, line, xend, VER_LINE); wattrset (w, m->bg_attrib); } /* Нарисовать строку меню в y-ой строке окна выбора */ void MnuDrawItem (Menu *m, int y, int reverse, int selection) { register WINDOW *w = m -> win; int pos, l, attr; int ay = WY (m->title, y), ax = WX (0); char *s, c; int hatch, bold, label, cont = NO, under; if (y + m -> shift >= 0 && y + m -> shift < m -> nitems) { s = M_ITEM (m, y + m -> shift); hatch = M_TST (m, y + m -> shift, I_NOSEL) || M_TST (m, y + m -> shift, M_HATCH); bold = M_TST (m, y + m -> shift, M_BOLD); label = M_TST (m, y + m -> shift, M_LABEL); under = M_TST (m, y + m -> shift, I_EXE); } else { /* строка вне допустимого диапазона */ s = "~"; label = hatch = bold = NO; } if (*s == M_CTRL) { /* нарисовать горизонтальную черту */ int x, xend = MXEND(m); wattrset(w, m->bg_attrib); for(x=1; x < xend; x++) mvwaddch(w, ay, x, HOR_LINE); mvwaddch (w, ay, 0, LEFT_JOIN); mvwaddch (w, ay, xend, RIGHT_JOIN); wattrset (w, m->bg_attrib); return; } l = strlen(s = MnuConvert (s, &pos)); c = '\0'; if (l > m -> textwidth) { /* слишком длинная строка */ c = s[m -> textwidth]; s[m -> textwidth] = '\0'; cont = YES; if (pos > m -> textwidth) pos = (-1); } if (selection) MnuBox (m, ay, reverse ? m->sel_attrib : m->bg_attrib); wattrset (w, attr = (bold ? A_BOLD : 0) | (hatch ? A_ITALICS : 0) | (under ? A_UNDERLINE : 0) | (reverse ? m->sel_attrib : m->bg_attrib)); mvwaddstr (w, ay, ax, s); if( cont ) mvwaddch(w, ay, ax+m->textwidth, RIGHT_TRIANG); /* Hot key letter */ if (pos >= 0) { wattron (w, bold ? A_ITALICS : A_BOLD); mvwaddch (w, ay, WX(pos), s[pos]); } if (label){ /* строка помечена */ wattrset (w, attr | A_BOLD); mvwaddch (w, ay, 1, LABEL); } if (under){ wattrset (w, A_BOLD); mvwaddch (w, ay, ax-1, BOX_HATCHED); } if (c) s[m->textwidth] = c; wattrset (w, m->bg_attrib); SetPoint (m->savep, ay, ax-1); /* курсор поставить перед словом */ } /* Выбор в меню подходящего элемента */ int MnuNext (Menu *m) { char *s; register y = m -> current; for (++y; y < m -> nitems; y++) if ((s = M_ITEM (m, y)) && *s != M_CTRL && !M_TST (m, y, I_NOSEL)) return y; return (-1); } int MnuPrev (Menu *m) { char *s; register y = m -> current; for (--y; y >= 0; --y) if ((s = M_ITEM (m, y)) && *s != M_CTRL && !M_TST (m, y, I_NOSEL)) return y; return (-1); } int MnuPgUp (Menu *m) { char *s; register n, y = m -> current; for (--y, n = 0; y >= 0; --y) { if ((s = M_ITEM (m, y)) && *s != M_CTRL && !M_TST (m, y, I_NOSEL)) n++; if (n == m -> textheight) return y; } return MnuFirst (m); } int MnuPgDn (Menu *m) { char *s; register n, y = m -> current; for (++y, n = 0; y < m -> nitems; y++) { if ((s = M_ITEM (m, y)) && *s != M_CTRL && !M_TST (m, y, I_NOSEL)) n++; if (n == m -> textheight) return y; } return MnuLast (m); } int MnuFirst (Menu *m) { char *s; register y; for (y = 0; y < m -> nitems; y++) if ((s = M_ITEM (m, y)) && *s != M_CTRL && !M_TST (m, y, I_NOSEL)) return y; return (-1); } int MnuLast (Menu *m) { char *s; register y; for (y = m -> nitems - 1; y >= 0; --y) if ((s = M_ITEM (m, y)) && *s != M_CTRL && !M_TST (m, y, I_NOSEL)) return y; return (-1); } int MnuThis (Menu *m) { char *s; if (m -> current < 0 || m -> current >= m -> nitems) return (-1); /* error */ if ((s = M_ITEM (m, m -> current)) && *s != M_CTRL && !M_TST (m, m -> current, I_NOSEL)) return m -> current; return (-1); } int MnuName (Menu *m, char *name) { char *s; register y; int pos; for(y = 0; y < m -> nitems; ++y) if ((s = M_ITEM (m, y)) && *s != M_CTRL && !M_TST (m, y, I_NOSEL) && strcmp(name, MnuConvert(s, &pos)) == 0 ) return y; return (-1); } int MnuHot (Menu *m, unsigned c) { register y; char *s; if (m -> hotkeys == (int *) NULL) return (-1); if (c < 0400 && isupper (c)) c = tolower (c); for (y = 0; y < m -> nitems; y++) if (c == m -> hotkeys[y] && (s = M_ITEM (m, y)) && *s != M_CTRL && !M_TST (m, y, I_NOSEL)) return y; return (-1); } /* Нарисовать содержимое меню для выбора */ void MnuDraw (Menu *m) { register i, j; for (i = 0; i < m -> textheight; i++) MnuDrawItem (m, i, NO, m -> scrollok ? YES : NO); } /* Поставить курсор в line-тую строку окна. */ void MnuPoint(Menu *m, int line, int eraseOld /* стирать старую селекцию? */){ int curline = m->current - m->shift; /* текущая строка окна */ if (line < 0 || line >= m -> textheight) return; /* ошибка */ if (eraseOld && curline != line) /* стереть старый выбор */ MnuDrawItem (m, curline, NO, YES); MnuDrawItem (m, line, YES, YES); /* подсветить новую строку */ } /* Перейти к y-той строке массива элементов, изменить картинку */ void MnuPointAt (Menu *m, int y) { char *s; if (y < 0 || y >= m->nitems) return; /* ошибка! */ if ((s = M_ITEM (m, y)) == NULL || *s == M_CTRL) return; MnuRoll (m, y, &m -> current, &m -> shift, m -> textheight, m -> nitems, MnuPoint, MnuDraw, MNU_DY); if (m -> scrollok) MnuWinBar(m); /* сдвинуть scroll bar */ GetBack(m->savep, m->win); /* вернуть курсор в начало строки селекции, * откуда он был сбит MnuWinBar-ом */ } /* Выбор в меню без участия "мыши". */ int MnuUsualSelect (Menu *m, int block) { int sel, snew, c, done = 0; m -> key = (-1); if( ! m->win ) return TOTAL_NOSEL; if((sel = MnuThis (m)) < 0) if((sel = MnuFirst (m)) < 0) return TOTAL_NOSEL; /* в меню нельзя ничего выбрать */ RaiseWin (m->win); /* сделать окно верхним */ MnuPointAt (m, sel); /* проявить */ if(m->showMe) m->showMe(m); /* может быть изменить позицию ? */ for (;;) { c = WinGetch (m->win); INP: if (m -> hitkeys && m -> handler) { HandlerReply reply; if (is_in (c, m -> hitkeys)) { c = (*m -> handler) (m, c, &reply); /* восстановить scroll bar */ MnuPointAt (m, m -> current); switch (reply) { case HANDLER_CONTINUE: continue; case HANDLER_NEWCHAR: goto INP; case HANDLER_OUT: goto out; case HANDLER_SWITCH: default: break; /* goto switch(c) */ } } } switch (c) { case KEY_UP: if ((snew = MnuPrev (m)) < 0) break; goto mv; case KEY_DOWN: next: if ((snew = MnuNext (m)) < 0) break; goto mv; case KEY_HOME: if ((snew = MnuFirst (m)) < 0) break; goto mv; case KEY_END: if ((snew = MnuLast (m)) < 0) break; goto mv; case KEY_NPAGE: if ((snew = MnuPgDn (m)) < 0) break; goto mv; case KEY_PPAGE: if ((snew = MnuPgUp (m)) < 0) break; goto mv; case KEY_IC: /* поставить/снять пометку */ if (M_TST (m, sel, M_LABEL)) M_CLR (m, sel, M_LABEL); else M_SET (m, sel, M_LABEL); MnuPointAt (m, sel); /* Если вы вычеркнете goto next; * и оставите просто break; * то вставьте в это место * MnuPoint( m, m->current - m->shift, NO ); */ goto next; case KEY_DC: if (M_TST (m, sel, M_HATCH)) M_CLR (m, sel, M_HATCH); else M_SET (m, sel, M_HATCH); MnuPointAt (m, sel); goto next; case KEY_LEFT: if (block & M_LFT) { sel = M_LEFT; goto out; } break; case KEY_RIGHT: if (block & M_RGT) { sel = M_RIGHT; goto out; } break; case 0: break; default: if (c == '\n' || c == '\r' || c == ESC) goto out; if ((snew = MnuHot (m, c)) < 0) { beep(); break; } /* иначе найден HOT KEY (горячая клавиша) */ done++; goto mv; } continue; mv: MnuPointAt (m, sel = snew); if(done){ wrefresh(m->win); /* проявить новую позицию */ break; } } out: wnoutrefresh(m->win); return((m->key = c) == ESC ? -1 : sel); /* Меню автоматически НЕ ИСЧЕЗАЕТ: если надо - * явно делайте MnuHide(m); после MnuUsualSelect(); */ } /* Пример 20 */ /* ______________________________________________________________ */ /* PULL_DOWN меню (меню-строка) */ /* _______________________ файл pull.h __________________________ */ typedef struct { Info info; /* строка в меню */ Menu *menu; /* связанное с ней вертикальное меню */ char *note; /* подсказка */ } PullInfo; typedef struct _Pull { /* Паспорт меню */ int nitems; /* количество элементов в меню */ PullInfo *items;/* элементы меню */ int *hotkeys; /* горячие ключи */ int key; /* клавиша, завершившая выбор */ int current; /* выбранный элемент */ int space; /* интервал между элементами меню */ int bg_attrib; /* цвет фона строки */ int sel_attrib; /* цвет выбранного элемента */ Point savep; void (*scrollBar) (struct _Pull *m, int n, int among); } PullMenu; #define PYBEG 0 /* строка, в которой размещается меню */ #define PM_BOLD I_DIR #define PM_NOSEL I_NOSEL #define PM_LFT M_LFT #define PM_RGT M_RGT #define PM_SET(m, i, flg) (m)->items[i].info.fl |= (flg) #define PM_CLR(m, i, flg) (m)->items[i].info.fl &= ~(flg) #define PM_TST(m, i, flg) ((m)->items[i].info.fl & (flg)) #define PM_ITEM(m, i) ((m)->items[i].info.s) #define PM_MENU(m, i) ((m)->items[i].menu) #define PM_NOTE(m, i) ((m)->items[i].note) #define COORD(m, i) ((m)->space * (i+1) + PullSum(m, i)) int PullInit(PullMenu *m); int PullSum(PullMenu *m, int n); void PullDraw(PullMenu *m); int PullShow(PullMenu *m); void PullHide(PullMenu *m); void PullDrawItem(PullMenu *m, int i, int reverse, int selection); void PullPointAt(PullMenu *m, int y); int PullHot(PullMenu *m, unsigned c); int PullPrev(PullMenu *m); int PullNext(PullMenu *m); int PullFirst(PullMenu *m); int PullThis(PullMenu *m); int PullUsualSelect(PullMenu *m); #define PullWin stdscr #define PM_REFUSED(m) ((m)->key < 0 || (m)->key == ESC ) /* _______________________ файл pull.c __________________________ */ #include "glob.h" #include "w.h" #include "menu.h" #include "pull.h" int PullSum(PullMenu *m, int n){ register i, total; int pos; for(i=0, total = 0; i < n; i++ ) total += strlen( MnuConvert(PM_ITEM(m, i), &pos )); return total; } /* Разметка меню. На входе: p->items массив элементов с M_HOT-метками и связанных меню. p->bg_attrib цвет фона строки. p->sel_attrib цвет выбранного элемента. Меню всегда размещается в окне stdscr (PullWin). */ int PullInit(PullMenu *m){ /* подменю не должны быть инициализированы, * т.к. все равно будут сдвинуты в другое место */ int total, pos; char *s; register i; m->key = m->current = 0; if(m->hotkeys){ free((char *) m->hotkeys); m->hotkeys = (int *) NULL; } /* подсчитать элементы меню */ m->nitems = 0; for( i=0, total = 0; PM_ITEM(m, i) != NULL; i++ ){ total += strlen(s = MnuConvert(PM_ITEM(m, i), &pos)); m->nitems++; } if( total > wcols(PullWin)){ /* меню слишком широкое */ err: beep(); return 0; } m->space = (wcols(PullWin) - total - 2) / (m->nitems + 1); if( m->space <= 0 ) goto err; /* разметить горячие клавиши */ if( m-> hotkeys = (int *) malloc( sizeof(int) * m->nitems )){ for(i=0; i < m->nitems; i++ ) m->hotkeys[i] = NOKEY; } for( i=0; i < m->nitems; i++ ){ if( PM_MENU(m,i)){ PM_MENU(m,i)->left = COORD(m, i) - 1; PM_MENU(m,i)->top = PYBEG + 1; PM_MENU(m,i)->bg_attrib = m-> bg_attrib; PM_MENU(m,i)->sel_attrib = m-> sel_attrib; if( PM_MENU(m,i)->win ) MnuDeinit( PM_MENU(m,i)); MnuInit( PM_MENU(m,i)); } if( m->hotkeys ){ s = MnuConvert(PM_ITEM(m, i), &pos); if( pos >= 0 ) m->hotkeys[i] = isupper(s[pos]) ? tolower(s[pos]) : s[pos]; } } keypad(PullWin, TRUE); return 1; } /* Проявить pull-down меню */ int PullShow(PullMenu *m){ register i; int first, last; first = last = (-1); for(i=0; i < m->nitems; i++ ){ PM_SET(m, i, PM_LFT | PM_RGT ); if( !PM_TST(m, i, PM_NOSEL)){ if( first < 0 ) first = i; last = i; } } if( first < 0 ) return (TOTAL_NOSEL); if(first == last ){ PM_CLR(m, first, PM_LFT | PM_RGT ); }else{ PM_CLR(m, first, PM_LFT); PM_CLR(m, last, PM_RGT); } wmove(PullWin, PYBEG, 0); wattrset(PullWin, m->bg_attrib); wclrtoeol(PullWin); PullDraw(m); return 1; } void PullDraw(PullMenu *m){ register i; for(i=0; i < m->nitems; i++ ) PullDrawItem(m, i, NO, NO); } /* Спрятать pull-down меню. Сама строка остается, подменю исчезают */ void PullHide(PullMenu *m){ register i; for(i=0; i < m->nitems; i++ ) if( PM_MENU(m, i)) MnuHide( PM_MENU(m, i)); PullDraw(m); } /* Нарисовать элемент меню */ void PullDrawItem(PullMenu *m, int i, int reverse, int selection){ int x, pos, hatch = PM_TST(m, i, PM_NOSEL ); char *s; x = COORD(m, i); s = MnuConvert( PM_ITEM(m, i), &pos ); wattrset(PullWin, (reverse ? m->sel_attrib : m->bg_attrib) | (hatch ? A_ITALICS : 0 )); /*mvwaddch(PullWin, PYBEG, x-1, reverse ? LEFT_TRIANG : ' ');*/ mvwaddstr(PullWin, PYBEG, x, s); /*waddch(PullWin, reverse ? RIGHT_TRIANG : ' ');*/ if( pos >= 0 ){ /* Hot key letter */ wattron(PullWin, A_BOLD); mvwaddch(PullWin, PYBEG, x + pos, s[pos]); } wmove (PullWin, PYBEG, x-1); SetPoint(m->savep, PYBEG, x-1); wattrset(PullWin, m->bg_attrib); } int PullPrev(PullMenu *m){ register y; for( y = m->current - 1; y >= 0; y-- ) if( !PM_TST(m, y, PM_NOSEL )) return y; return (-1); } int PullNext(PullMenu *m){ register y; for( y = m->current+1; y < m->nitems; y++ ) if( !PM_TST(m, y, PM_NOSEL)) return y; return (-1); } int PullFirst(PullMenu *m){ register y; for( y = 0; y < m->nitems; y++ ) if( !PM_TST(m, y, PM_NOSEL)) return y; return (-1); } int PullThis(PullMenu *m){ register y; if( m->current < 0 || m->current >= m->nitems ) return (-1); if( PM_TST(m, m->current, PM_NOSEL)) return (-1); return m->current; } int PullHot(PullMenu *m, unsigned c){ register y; if( m-> hotkeys == (int *) NULL ) return (-1); if( c < 0400 && isupper(c)) c = tolower(c); for( y=0; y < m->nitems; y++ ) if( c == m->hotkeys[y] && !PM_TST(m, y, PM_NOSEL)) return y; return (-1); } /* Указать на элемент n */ void PullPointAt( PullMenu *m, int n){ if( n < 0 || n >= m->nitems ) return ; /* error */ if( n != m->current ){ if( PM_MENU(m, m->current)) MnuHide( PM_MENU(m, m->current)); PullDrawItem( m, m->current, NO, YES ); } m -> current = n; PullDrawItem( m, n, YES, YES ); if( m->scrollBar ){ m->scrollBar( m, n, m->nitems ); GetBack(m->savep, PullWin); } } /* Выбор в меню */ int PullUsualSelect(PullMenu *m){ int autogo = NO, c, code, done = 0, snew, sel, reply = (-1); m->key = (-1); if((sel = PullThis(m)) < 0 ) if((sel = PullFirst(m)) < 0 ) return TOTAL_NOSEL; if( PullShow(m) < 0 ) return TOTAL_NOSEL; PullPointAt(m, sel); /* начальная позиция */ for(;;){ if( autogo ){ /* Автоматическая проявка подменю */ if( PM_MENU(m, m->current) == NULL) goto ask; code = MnuUsualSelect(PM_MENU(m, m->current), PM_TST(m, m->current, PM_LFT) | PM_TST(m, m->current, PM_RGT)); MnuHide(PM_MENU(m, m->current)); c = PM_MENU(m, m->current)->key; if(code == (-1)){ reply = (-1); goto out; } /* в подменю ничего нельзя выбрать */ if( code == TOTAL_NOSEL) goto ask; /* MnuUsualSelect выдает специальные коды для * сдвигов влево и вправо */ if( code == M_LEFT ) goto left; if( code == M_RIGHT ) goto right; reply = code; goto out; } else ask: c = WinGetch(PullWin); switch(c){ case KEY_LEFT: left: if((snew = PullPrev(m)) < 0 ) goto ask; goto mv; case KEY_RIGHT: right: if((snew = PullNext(m)) < 0 ) goto ask; goto mv; case ESC: reply = (-1); goto out; case '\r': case '\n': if( PM_MENU(m, m->current) == NULL){ reply = 0; goto out; } autogo = YES; break; default: if((snew = PullHot(m, c)) < 0 ) break; if( PM_MENU(m, snew) == NULL){ reply=0; done++; } autogo = YES; goto mv; } continue; mv: PullPointAt(m, sel = snew); if( done ) break; } out: wnoutrefresh(PullWin); PullHide(m); m->key = c; wattrset(PullWin, A_NORMAL); /* NOT bg_attrib */ return reply; /* номер элемента, выбранного в меню PM_MENU(m, m->current) */ } /* Пример 21 */ /* РЕДАКТОР СТРОКИ И ИСТОРИЯ РЕДАКТИРУЕМЫХ СТРОК */ /* _______________________ файл hist.h __________________________ */ /* ИСТОРИЯ. ЗАПОМИНАНИЕ СТРОК И ВЫДАЧА ИХ НАЗАД ПО ТРЕБОВАНИЮ. */ /* ______________________________________________________________ */ typedef struct { /* Паспорт истории */ Info *list; /* запомненные строки */ int sz; /* размер истории (макс.) */ int len; /* текущее число строк */ Menu mnu; /* меню для выборки из истории */ } Hist; void HistInit(Hist *h, int n); void HistAdd (Hist *h, char *s, int fl); Info *HistSelect(Hist *h, int x, int y); /* _______________________ файл hist.c __________________________ */ #include "w.h" #include "glob.h" #include "menu.h" #include "hist.h" /* Проинициализировать новую "историю" емкостью n строк */ void HistInit(Hist *h, int n){ register i; if( h->list ){ blkfree( h->list ); h->list = NULL; } h->len = 0; h->mnu.title = "History"; h->mnu.bg_attrib = A_NORMAL; h->mnu.sel_attrib = A_REVERSE; h->list = (Info *) malloc( (n+1) * sizeof(Info)); if( ! h->list ){ h->sz = 0; return; }else h->sz = n; for( i=0; i < n+1 ; i++ ) h->list[i] = NullInfo; } /* Добавить строку s с меткой fl в историю */ void HistAdd (Hist *h, char *s, int fl){ register i, j; Info tmp; if( h->sz == 0 ) return; /* А нет ли уже такой строки ? */ for( i=0; i < h->len; i++ ) if( !strcmp(s, h->list[i].s )){ /* есть ! */ if( i == 0 ) return; /* первая */ /* сделать ее первой строкой */ tmp = h->list[i]; for( j=i-1; j >= 0; --j ) h->list[j+1] = h->list[j]; h->list[0] = tmp; return; } if( h->len < h->sz ){ for( i=h->len-1; i>= 0; i-- ) h->list[i+1] = h->list[i]; h->len ++ ; }else{ /* выкинуть самую старую строку из истории */ free( h->list[ h->sz - 1 ].s ); for( i=h->sz - 2; i >= 0; i-- ) h->list[i+1] = h->list[i]; } (h->list)[0].s = strdup(s); (h->list)[0].fl = fl; } /* Выборка строки из истории */ Info *HistSelect(Hist *h, int x, int y){ if( h->len == 0 ) return (Info *) NULL; h->mnu.top = y; h->mnu.left = x; h->mnu.items = h->list; MnuInit( & h->mnu ); if( h->mnu.hotkeys ){ register i; for(i=0 ; i < h->mnu.nitems; i++ ) h->mnu.hotkeys[i] = h->list[i].s[0] & 0377; } MnuUsualSelect( & h->mnu, 0 ); MnuDeinit ( & h->mnu ); if( M_REFUSED ( & h->mnu )) return (Info *) NULL; return & h->list[ h->mnu.current ]; } /* _______________________ файл line.h __________________________ */ /* РЕДАКТОР ДЛИННЫХ СТРОК (ВОЗМОЖНО ШИРЕ ЭКРАНА) */ /* ______________________________________________________________ */ typedef struct _LineEdit { /* Паспорт редактора строки */ WINDOW *win; /* окно для редактирования */ int width; /* ширина поля редактирования */ int left, top; /* координаты поля редактирования в окне */ int pos; /* позиция в строке */ int shift; /* число символов скрытых левее поля */ char *line; /* строка которая редактируется */ int maxlen; /* максимальная длина строки */ int len; /* текущая длина строки */ int insert; /* 1 - режим вставки; 0 - замены */ int nc; /* 1 - стирать строку по первому нажатию */ int cursorOn; /* курсор включен (для графики) */ int bg_attrib; /* цвет текста */ int fr_attrib; /* цвет пустого места в поле */ int wl_attrib; /* цвет краев строки */ int sel_attrib; /* цвет символа под курсором */ Hist *histIn; /* история для выборки строк */ Hist *histOut; /* история для запоминания строк */ int key; /* кнопка, завершившая редактирование */ Point savep; /* функции проявки и убирания окна (если надо) */ int (*showMe)(struct _LineEdit *le); /* 1 при успехе */ void (*hideMe)(struct _LineEdit *le); void (*posMe) (struct _LineEdit *le); /* установка позиции */ /* Функция рисования scroll bar-а (если надо) */ void (*scrollBar)(struct _LineEdit *le, int whichbar, int n, int among); /* Специальная обработка клавиш (если надо) */ int *hitkeys; int (*handler)(struct _LineEdit *le, int c, HandlerReply *reply); } LineEdit; void LePutChar( LineEdit *le, int at); void LeCursorHide( LineEdit *le ); void LeCursorShow( LineEdit *le ); void LePointAt( LineEdit *le, int at ); void LePoint( LineEdit *le, int x, int eraseOld ); void LeDraw( LineEdit *le ); void LeReport( LineEdit *le ); void LeDelCh ( LineEdit *le ); void LeInsCh ( LineEdit *le, int c ); void LeRepCh ( LineEdit *le, int c ); int LeInsStr( LineEdit *le, char *s); int LeWerase( LineEdit *le, char *to ); int LeEdit( LineEdit *le ); #define LINE_DX 1 #define LE_REFUSED(m) ((m)->key < 0 || (m)->key == ESC ) /* _______________________ файл line.c __________________________ */ /* Редактор строки. Эта версия была изначально написана * * для графики, поэтому здесь не совсем CURSES-ные алгоритмы */ #include "w.h" #include "glob.h" #include "menu.h" #include "hist.h" #include "line.h" /* Удалить букву из строки */ static char cdelete(register char *s, int at) { char c; s += at; if((c = *s) == '\0') return c; while( s[0] = s[1] ) s++; return c; } /* Вставить букву в строку */ static void insert(char *s, int at, int c){ register char *p; s += at; p = s; while(*p) p++; /* найти конец строки */ p[1] = '\0'; /* закрыть строку */ for( ; p != s; p-- ) p[0] = p[-1]; *s = c; } /* Нарисовать видимую часть строки с позиции from */ static void LeDrawLine( LineEdit *le, int from ){ LeCursorHide( le ); for( ; from < le->width; from++ ) LePutChar(le, from); /* курсор остается спрятанным */ } /* Выдать символ строки в позиции at */ void LePutChar( LineEdit *le, int at){ int off = le->shift + at; int bgcolor = le->bg_attrib, wall; wall = /* символ на краю поля и строка выходит за этот край ? */ ( at == 0 && le->shift || ( at >= le->width - 1 && le->shift + le->width < le->len )); bgcolor = ( off < le->len ) ? le->bg_attrib : ( at >= le->width || off >= le->maxlen ) ? (le->bg_attrib | A_ITALICS): /* чистое место в поле */ le->fr_attrib ; wattrset( le->win, wall? le->wl_attrib|A_BOLD|A_ITALICS: bgcolor); mvwaddch( le->win, le->top, le->left + at, off < le->len ? le->line[off] : ' ' ); wattrset( le->win, le->bg_attrib); } /* Спрятать курсор. x в интервале 0..le->width */ void LeCursorHide( LineEdit *le ){ int x = le->pos - le->shift; if( x < 0 || x > le->width || le->cursorOn == NO ) return; LePutChar( le, x ); le->cursorOn = NO; } /* Проявить курсор */ void LeCursorShow( LineEdit *le ){ int x = le->pos - le->shift, saveattr = le->bg_attrib; if( x < 0 || x > le->width || le->cursorOn == YES ) return; le->bg_attrib = le->sel_attrib | (le->insert==NO ? A_BOLD : 0); LePutChar(le, x); le->bg_attrib = saveattr; wmove(le->win, le->top, le->left + x); le->cursorOn = YES; SetPoint(le->savep, le->top, le->left+x); } /* Функция прокрутки длинной строки через окошко */ static void LeRoll( LineEdit *ptr, int aid, int *cur, int *shift, int width, /* ширина окна */ int len, int maxlen, void (*go) (LineEdit *p, int x, int eraseOld), void (*draw)(LineEdit *p), /* перерисовщик поля */ int LDX ){ int x = *cur - *shift, oldshift = *shift, newshift = oldshift; int AID_LFT, AID_RGT, drawn = NO; if( aid < 0 || aid > len ) return; /* incorrect */ if( x < 0 || x > width ) return; /* incorrect */ AID_LFT = MIN(LDX, maxlen); AID_RGT = MAX(0, MIN(maxlen, width-1 - LDX)); if( aid < *cur && x <= AID_LFT && oldshift > 0 ) goto Scroll; else if( aid > *cur && x >= AID_RGT && oldshift + width < maxlen ) goto Scroll; if( oldshift <= aid && aid < oldshift + width ) /* прокрутка не нужна - символ уже видим */ goto Position; Scroll: if( aid >= *cur ) newshift = aid - AID_RGT; else newshift = aid - AID_LFT; if( newshift + width > maxlen || (len == maxlen && aid == len)) newshift = maxlen - width; if( newshift < 0 ) newshift = 0; if( newshift != oldshift ){ *shift = newshift; (*draw)(ptr); drawn = YES; } Position: if((x = aid - newshift) >= width && len != maxlen ) beep(); /* ERROR */ (*go)(ptr, x, !drawn ); *cur = aid; } /* Поставить курсор на at-тый символ строки */ void LePointAt( LineEdit *le, int at ){ /* at == len допустимо */ if( at < 0 || at > le->len ) return; if( le->pos == at ) return; /* уже на месте */ LeCursorHide( le ); LeRoll( le, at, & le->pos, & le->shift, le->width, le->len, le->maxlen, LePoint, LeDraw, LINE_DX); le->pos = at; LeCursorShow( le ); } /* Нарисовать подходящий scroll bar */ void LePoint( LineEdit *le, int x, int eraseOld ){ if(le->scrollBar) (*le->scrollBar)(le, BAR_HOR, x + le->shift, le->maxlen+1 ); GetBack( le->savep, le->win); } /* Нарисовать подходящий scroll bar */ /* Вызывай это каждый раз, когда len изменится */ void LeReport( LineEdit *le ){ if(le->scrollBar) le->scrollBar (le, BAR_VER, le->len, le->maxlen+1 ); GetBack( le->savep, le->win); } /* Нарисовать видимую часть строки */ void LeDraw( LineEdit *le ){ LeDrawLine( le, 0); } /* Удаление буквы из строки */ void LeDelCh( LineEdit *le ){ if( le->len <= 0 || le->pos < 0 || le->pos >= le->len ) return; LeCursorHide( le ); (void) cdelete( le->line, le->pos ); le->len --; LeDrawLine( le, le->pos - le->shift ); LeReport( le ); } /* Вставка буквы в строку */ void LeInsCh( LineEdit *le, int c ){ if( le->len < 0 || le->pos < 0 || le->pos > le->len ) return; LeCursorHide( le ); insert( le->line, le->pos, c ); le->len++; LeDrawLine( le, le->pos - le->shift ); LeReport( le ); } /* Замена буквы в строке */ void LeRepCh( LineEdit *le, int c ){ if( le->len <= 0 || le->pos < 0 || le->pos >= le->len ) return; LeCursorHide( le ); le->line[ le->pos ] = c; LePutChar( le, le->pos - le-> shift ); } /* Вставка подстроки в строку редактирования */ int LeInsStr( LineEdit *le, char *s){ int len = le->len, slen = strlen(s); register i; if( len + slen > le->maxlen ) slen = le->maxlen - len; if( ! slen ) return 0; for( i=0; i < slen ; i ++ ) insert( le->line, le->pos+i, s[i] ); le->len += slen; LeCursorHide( le ); LeDrawLine( le, le->pos - le->shift ); LePointAt( le, le->pos + slen ); LeReport( le ); return slen ; } /* Стирание слова */ int LeWerase( LineEdit *le, char *to ){ register i; register char *s = le->line; char c; if( to ) *to = '\0'; i = le->pos; if( s[i] == ' ' || s[i] == '\0' ){ /* найти конец слова */ for( --i; i >= 0 ; i-- ) if( s[i] != ' ' ) break; if( i < 0 || le->len == 0 ){ beep(); return NO; } } /* найти начало слова */ for( ; i >= 0 && s[i] != ' ' ; i-- ); i++; /* i < 0 || s[i] == ' ' */ LeCursorHide( le ); LePointAt( le, i ); while( s[i] != ' ' && s[i] != '\0' ){ c = cdelete( s, i ); if( to ) *to++ = c; le->len --; } /* удалить пробелы после слова */ while( s[i] == ' ' ){ c = cdelete( s, i ); le->len --; } if( to ) *to = '\0'; LeDrawLine( le, i - le->shift ); LeReport( le ); return YES; } /* Редактор строки le->line что редактировать. le->maxlen макс. длина строки. le->win окно, содержащее поле редактирования. le->width ширина поля редактирования. le->top коорд-ты поля редактирования le->left в окне win. le->insert = YES режим вставки. le->nc = YES стирать строку при первом нажатии. le->histIn входная история или NULL. le->histOut выходная история или NULL. le->showMe функция проявки окна или NULL. le->hideMe функция спрятывания окна или NULL. le->hitkeys специальные клавиши или NULL. le->handler обработчик специальных клавиш или NULL. le->scrollBar рисовалка scroll bar-ов или NULL. le->posMe установка позиции в строке при входе. le->bg_attrib цвет поля. le->fr_attrib цвет незаполненной части поля. le->wl_attrib цвет краев поля при продолжении. le->sel_attrib цвет символа под курсором. */ int LeEdit( LineEdit *le ){ int c; int nchar = 0; /* счетчик нажатых клавиш */ Info *inf; /* проявить окно */ if( le->showMe ) if( (*le->showMe) (le) <= 0 ) return (-1); if( !le->win ) return (le->key = -1); Again: le -> pos = 0; le -> len = strlen( le->line ); le -> shift = 0; le -> cursorOn = NO; le->key = (-1); LeDraw( le ); if(le->posMe) (*le->posMe)(le); LePointAt(le, le->pos ); LePoint( le, le->pos - le->shift, NO ); LeReport( le ); for (;;) { LeCursorShow( le ); c = WinGetch(le->win); /* прочесть символ с клавиатуры */ nchar++; /* число нажатых клавиш */ INP: if( le->hitkeys && le->handler ){ HandlerReply reply; if( is_in(c, le->hitkeys)){ /* спецсимвол ? */ c = (*le->handler)(le, c, &reply); /* Восстановить scroll bars */ LePoint( le, le->pos - le->shift, NO ); LeReport( le ); switch( reply ){ case HANDLER_CONTINUE: continue; case HANDLER_NEWCHAR: goto INP; case HANDLER_OUT: goto out; case HANDLER_AGAIN: /* reset */ LeCursorHide(le); goto Again; case HANDLER_SWITCH: default: break; /* goto switch(c) */ } } } sw: switch (c) { case KEY_RIGHT: /* курсор вправо */ if (le->pos != le->len && le->len > 0) LePointAt( le, le->pos + 1); break; case KEY_LEFT: /* курсор влево */ if (le->pos > 0) LePointAt(le, le->pos - 1); break; case '\t': /* табуляция вправо */ if (le->pos + 8 > le->len) LePointAt(le, le->len); else LePointAt(le, le->pos + 8); break; case KEY_BACKTAB: /* табуляция влево */ case ctrl('X'): if( le->pos - 8 < 0 ) LePointAt(le, 0); else LePointAt(le, le->pos - 8 ); break; case KEY_HOME: /* в начало строки */ LePointAt(le, 0); break; case KEY_END: /* в конец строки KEY_LL */ if( le->len > 0 ) LePointAt(le, le->len); break; case 0177: /* стереть символ перед курсором */ case KEY_BACKSPACE: case '\b': if (le->pos == 0) break; LePointAt(le, le->pos - 1); /* налево */ /* и провалиться в DC ... */ case KEY_F (6): /* стереть символ над курсором */ case KEY_DC: if (! le->len || le->pos == le->len) break; LeDelCh(le); break; case KEY_UP: /* вызвать историю */ case KEY_DOWN: case KEY_NPAGE: case KEY_PPAGE: case KEY_F(4): if( ! le->histIn ) break; /* иначе позвать историю */ inf = HistSelect( le->histIn, wbegx(le->win) + le->pos - le->shift + 2, le->top + 1); if( inf == (Info *) NULL ) break; LeCursorHide( le ); strncpy( le->line, inf->s, le->maxlen ); goto Again; out: case '\r': case '\n': case ESC: /* ввод завершен - выйти */ LeCursorHide( le ); if( c != ESC && le->histOut && *le->line ) /* запомнить строку в историю */ HistAdd( le->histOut, le->line, 0); if( le->hideMe ) /* спрятать окно */ (*le->hideMe)(le); return (le->key = c); case KEY_F (8): /* стереть всю строку */ case ctrl('U'): le->line[0] = '\0'; le->len = le->pos = le->shift = 0; LeCursorHide( le ); LeReport( le ); goto REWRITE; case KEY_F(0): /* F10: стереть до конца строки */ if( le->pos == le->len ) break; le->line[ le->pos ] = '\0'; le->len = strlen( le->line ); LeCursorHide( le ); LeDrawLine( le, le->pos - le->shift ); LeReport( le ); break; case ctrl('W'): /* стереть слово */ LeWerase( le, NULL ); break; case ctrl('A'): /* перерисовка */ LeCursorHide(le); /* RedrawScreen(); */ REWRITE: LeDraw(le); break; case KEY_F(7): /* переключить режим вставки/замены */ le->insert = ! le->insert; LeCursorHide( le ); break; #ifndef M_UNIX case ctrl('V'): /* ввод заэкранированного символа */ nchar--; c = WinGetch(le->win); nchar++; if( c >= 0400 ) goto sw; goto Input; #endif case 0: break; default: /* ввод обычного символа */ if (c >= 0400 || ! isprint(c)) break; Input: if( le->nc && nchar == 1 && le->insert && /*le->pos == 0 &&*/ le->len != 0 ){ /* если это первая нажатая кнопка, то /* удалить все содержимое строки /* и заменить ее нажатой буквой */ le->shift = 0; le->len = le->pos = 1; le->line[0] = c; le->line[1] = '\0'; LeCursorHide( le ); LeReport( le ); goto REWRITE; } if (!le->insert) { /* REPLACE - режим замены */ if (le->pos == le->len) goto AddChar; /* временный INSERT */ LeRepCh( le, c ); LePointAt( le, le->pos + 1 ); } else { /* INSERT - режим вставки */ AddChar: if( le->len >= le->maxlen ){ beep(); /* строка переполнена */ break; } LeInsCh( le, c ); LePointAt( le, le->pos + 1 ); } /* endif */ } /* endswitch */ } /* endfor */ } /* endfunc */ /* Пример 22 */ /* ______________________________________________________________ */ /* ПРЯМОУГОЛЬНАЯ ТАБЛИЦА-МЕНЮ */ /* _______________________ файл table.h _________________________ */ typedef struct _Table { /* Паспорт таблицы */ int nitems; /* количество элементов в таблице */ Info *items; /* массив элементов */ char *fmt; /* формат вывода */ int key; /* кнопка, завершившая выбор в таблице */ int current; /* номер выбранного элемента */ int shift; /* число элементов перед окном */ WINDOW *win; /* окно в котором размещена таблица */ int left, top, height, width; /* размеры и расположение таблицы в окне */ int space; /* интервал между колонками */ int elen; /* макс. ширина колонки */ int tcols; /* число колонок в таблице */ int cols; /* число колонок в видимой части таблицы */ int cutpos; /* позиция для обрубания слишком длинных строк */ int scrollok; /* роллируется ? */ int exposed; /* нарисована ? */ int elems; /* текущее число эл-тов в подокне */ int maxelems; /* максимальное число эл-тов в подокне */ int maxshift; /* максимальный сдвиг */ int bg_attrib, sel_attrib; /* цвет фона и выбранного элемента */ Point savep; /* Функции проявки/спрятывания окна */ int (*showMe)(struct _Table *tbl); void (*hideMe)(struct _Table *tbl); void (*scrollBar)(struct _Table *tbl, int whichbar, int n, int among); /* Обработчик специальных клавиш */ int *hitkeys; int (*handler)(struct _Table *tbl, int c, HandlerReply *reply); } Table; #define T_BOLD M_BOLD #define T_NOSEL I_NOSEL #define T_HATCH M_HATCH #define T_LABEL M_LABEL #define T_SET(m, i, flg) (((m)->items)[i]).fl |= (flg) #define T_CLR(m, i, flg) (((m)->items)[i]).fl &= ~(flg) #define T_TST(m, i, flg) ((((m)->items)[i]).fl & (flg)) #define T_ITEM(m, i) ((((m)->items)[i]).s) /* Формат 'd' ниже вставлен лишь для текущего состояния использования * форматов в нашем проекте: */ #define T_ITEMF(m, i, cut) \ ((m)->fmt && *(m)->fmt != 'd' ? \ TblConvert(T_ITEM((m), i), (m)->fmt, (cut)) : T_ITEM((m), i)) #define T_VISIBLE(tbl, new) ((tbl)->exposed == YES && \ (new) >= (tbl)->shift && (new) < (tbl)->shift + (tbl)->elems) #define TLABSIZE 2 /* ширина поля меток */ #define T_REFUSED(t) ((t)->key < 0 || (t)->key == ESC ) int TblCount( Table *tbl ); void TblInit( Table *tbl, int forcedOneColumn ); void TblChkCur ( Table *tbl ); int TblChkShift( Table *tbl ); void TblChk ( Table *tbl ); char *TblConvert( char *s, char *fmt, int cutpos ); void TblPointAt ( Table *tbl, int snew ); void TblPoint ( Table *tbl, int snew, int eraseOld ); void TblDraw ( Table *tbl ); void TblDrawItem( Table *tbl, int at, int reverse, int selection); void TblBox ( Table *tbl, int at, int reverse, int hatched, int width, int axl, int axi, int ay); int TblClear( Table *tbl ); int TblPlaceByName( Table *tbl, char *p ); void TblReport( Table *tbl ); void TblTag ( Table *tbl, int at, int flag); void TblUntag( Table *tbl, int at, int flag); void TblRetag( Table *tbl, int at, int flag); void TblTagAll( Table *tbl, char *pattern, int flag ); void TblUntagAll( Table *tbl, char *pattern, int flag ); int TblUsualSelect( Table *tbl ); /* _______________________ файл table.c _________________________ */ #include "w.h" #include "glob.h" #include "menu.h" #include "table.h" extern char STRING_BUFFER[MAXLEN]; /* imported from menu.c */ /* надо указать размер, чтоб работал sizeof(STRING_BUFFER) */ /* Переформатировать строку по формату fmt для выдачи в таблицу. * Пока предложена простейшая интерпретация. */ char *TblConvert( char *s, char *fmt, int cutpos ){ if( fmt && *fmt == 'd'){ register i, j, len; char *p = strrchr(s, '.'); if((len = strlen(s)) < DIR_SIZE && *s != '.' && p ){ int sufxlen = strlen(p); for(i=0; i < len - sufxlen ; ++i) STRING_BUFFER[i] = s[i]; for(; i < DIR_SIZE - sufxlen; ++i) STRING_BUFFER[i] = ' '; for(j=0; i < DIR_SIZE; j++, ++i) STRING_BUFFER[i] = p[j]; STRING_BUFFER[i] = '\0'; } else strcpy(STRING_BUFFER, s); if(cutpos > 0 && cutpos < sizeof(STRING_BUFFER)) STRING_BUFFER[cutpos] = '\0'; } else { /* без формата, только обрубание */ if( cutpos <= 0 ) cutpos = 32000; /* Обрубание выключено */ strncpy(STRING_BUFFER, s, MIN(sizeof(STRING_BUFFER) - 1, cutpos)); } return STRING_BUFFER; } /* Обрубить s до длины cutpos букв */ char *TblCut( char *s, int cutpos ){ if( cutpos <= 0 ) return s; strncpy(STRING_BUFFER, s, MIN(sizeof(STRING_BUFFER) - 1, cutpos)); return STRING_BUFFER; } /* Подсчет элементов таблицы и ширины столбца */ int TblCount( Table *tbl ){ register i, L, LL; char *s; L = i = 0; if( tbl->items) while((s = T_ITEM(tbl, i)) != NULL ){ if( tbl->fmt ) s = TblConvert(s, tbl->fmt, 0); LL = strlen(s); if( LL > L ) L = LL; i++; } tbl->nitems = i; return L; } /* Разметка таблицы. На входе: t->items Массив данных, показываемый в меню. t->exposed = NO Таблица уже нарисована ? t->fmt Формат строк, выводимых в таблицу. t->win Окно для размещения таблицы. t->showMe Функция проявки окна. t->hideMe Функция упрятывания окна. t->hitkeys Специальные клавиши []. Конец -1. t->handler Обработчик или NULL. t->width Ширина поля таблицы. t->height Высота поля таблицы. t->left Левый край таблицы в окне. t->top Верхний край таблицы в окне. t->scrollBar Функция рисования scroll-bar-а или NULL. t->bg_attrib Цвет фона (== цвету фона окна). t->sel_attrib Цвет выбранного элемента. forcedOneColumn == YES делает таблицу в 1 колонку. */ void TblInit( Table *tbl, int forcedOneColumn ){ int mlen = TblCount( tbl ); /* самый широкий элемент таблицы */ /* усечь до ширины таблицы */ if( mlen > tbl->width || forcedOneColumn ) mlen = tbl->width; /* слишком широко */ /* ширина столбца таблицы = ширина элемента + поле меток + разделитель */ tbl->elen = mlen + TLABSIZE + 1; /* #####строка_элемент| */ /* метки элемент 1 */ /* число столбцов во всей таблице */ tbl->tcols = (tbl->nitems + tbl->height - 1) / tbl->height; /* число столбцов, видимых через окно (+1 для ошибок округления) */ tbl->cols = tbl->width / (tbl->elen + 1); if( tbl->cols == 0 ){ /* слишком широкая таблица */ tbl->cols = 1; /* таблица в одну колонку */ tbl->elen = tbl->width - 2; mlen = tbl->elen - (TLABSIZE + 1); tbl->cutpos = mlen; /* и придется обрубать строки */ } else tbl->cutpos = 0; /* без обрубания */ tbl->cols = MIN(tbl->cols, tbl->tcols); /* интервал между колонками */ tbl->space = (tbl->width - tbl->cols * tbl->elen)/(tbl->cols+1); if( tbl->space < 0 ){ beep(); tbl->space = 0; } /* сколько элементов умещается в окно */ tbl->maxelems = tbl-> cols * tbl->height; tbl->maxshift = (tbl->tcols * tbl->height) - tbl->maxelems; if( tbl->maxshift < 0 ) tbl->maxshift = 0; /* требуется ли роллирование таблицы через окно */ tbl->scrollok = (tbl->nitems > tbl->maxelems); tbl->elems = tbl->shift = tbl->current = 0; /* пока */ tbl->exposed = NO; /* таблица еще не нарисована */ tbl->key = (-1); } /* Проверить корректность текущей позиции */ void TblChkCur( Table *tbl ){ if( tbl->current >= tbl->nitems ) tbl->current = tbl->nitems - 1; if( tbl->current < 0 ) tbl->current = 0; } /* Проверить корректность сдвига (числа элементов ПЕРЕД окном) */ int TblChkShift( Table *tbl ){ register int oldshift = tbl->shift; /* в колонке должно быть видно достаточно много элементов */ if( tbl->cols == 1 && /* таблица в 1 колонку */ tbl->tcols > 1 && /* но всего в ней не одна колонка */ tbl->nitems - tbl->shift < tbl->height / 2 + 1 ) tbl->shift = tbl->nitems - (tbl->height/2 + 1); if( tbl->shift > tbl->maxshift ) tbl->shift = tbl->maxshift; if( tbl->shift < 0 ) tbl->shift = 0; return tbl->shift != oldshift; /* скорректировано ? */ } /* Проверить корректность параметров таблицы */ void TblChk( Table *tbl ){ again: TblChkCur( tbl ); TblChkShift( tbl ); if( tbl -> maxelems ){ if( tbl -> current >= tbl->shift + tbl->maxelems ){ tbl->shift = tbl->current - (tbl->maxelems - 1); goto again; } if( tbl->current < tbl->shift ){ tbl->shift = tbl->current; goto again; } } } /* Указать на snew-тый элемент списка, перерисовать картинку */ void TblPointAt( Table *tbl, int snew ){ int curCol; /* текущий столбец всей таблицы (для current) */ int newCol; /* нужный столбец таблицы (для snew) */ int colw; /* нужный столбец ОКНА (для snew) */ int gap; /* зазор */ int newshift = tbl->shift; /* новый сдвиг окна от начала массива */ int drawn = NO; /* таблица целиком перерисована ? */ /* ПРоверить корректность номера желаемого элемента */ if( snew < 0 ) snew = 0; if( snew >= tbl->nitems ) snew = tbl->nitems - 1; if( tbl->current == snew && tbl->exposed == YES) return; /* уже стоим на требуемом элементе */ #define WANTINC 1 #define WANTDEC (tbl->cols-1-WANTINC) gap = (tbl->height - (tbl->shift % tbl->height)) % tbl->height; /* gap - это смещение, которое превращает строгую постолбцовую структуру --0-- --3-- --1-- --4-- --2-- --5-- в сдвинутую структуру ____ |------ gap=2___/ пусто g0 --1-- g3 | --4-- g6 .... \____пусто g1 --2-- g4 | --5-- g7 --0-- g2 --3-- g5 | --6-- g8 |------ shift=4 */ /* операция прокрутки данных через таблицу: TblRoll() _________________*/ /* Элемент уже виден в текущем окне ? */ /* Параметр elems вычисляется в TblDraw() */ if( T_VISIBLE(tbl, snew)) goto ThisWindow; /* smooth scrolling (гладкое роллирование) */ if( snew == tbl->shift + tbl->elems && /* элемент непосредственно следующий ЗА окном */ tbl->current == tbl->shift + tbl->elems - 1 /* курсор стоит в нижнем правом углу окна */ ){ newshift++; gap--; if ( gap < 0 ) gap = tbl->height - 1 ; goto do_this; } if( snew == tbl->shift - 1 && /* элемент непосредственно стоящий ПЕРЕД окном */ tbl->current == tbl->shift /* и курсор стоит в верхнем левом углу окна таблицы */ ){ newshift --; gap = (gap + 1) % tbl->height; goto do_this; } /* jump scrolling (прокрутка скачком) */ curCol = (tbl->current+gap) / tbl->height; newCol = (snew +gap) / tbl->height; if( tbl->cols > 1 ){ if( newCol > curCol ) colw = WANTINC; else colw = WANTDEC; } else colw = 0; newshift = (newCol - colw) * tbl->height - gap ; do_this: if( tbl->shift != newshift || tbl->exposed == NO){ tbl->shift = newshift; TblChkShift( tbl ); /* >= 0 && <= max */ TblDraw( tbl ); /* перерисовать все окно с нового места */ drawn = YES; /* перерисовано целиком */ } ThisWindow: /* поставить курсор в текущем окне без перерисовки окна */ TblPoint( tbl, snew, !drawn ); /* tbl->current = snew; сделается в TblPoint() */ } /* Поставить курсор на элемент в текущем окне */ void TblPoint ( Table *tbl, int snew, int eraseOld ){ if( ! T_VISIBLE(tbl, snew)){ beep(); /* ERROR !!! */ return; } if( eraseOld && tbl->current != snew ) TblDrawItem( tbl, tbl->current, NO, YES ); TblDrawItem( tbl, snew, YES, YES ); tbl->current = snew; TblReport( tbl ); } /* Нарисовать scroll bar в нужной позиции. Кроме того, * в эту функцию можно включить и другие действия, например * выдачу имени T_ITEM(tbl, tbl->current) на рамке окна. */ void TblReport( Table *tbl ){ if ( tbl->scrollBar ) (*tbl->scrollBar)( tbl, BAR_VER|BAR_HOR, tbl->current, tbl->nitems); GetBack( tbl->savep, tbl->win ); /* курсор на место ! */ } /* Перерисовать все окно таблицы */ void TblDraw( Table *tbl ){ register next; /* число элементов в таблице (может остаться незанятое * место в правой нижней части окна */ tbl->elems = MIN(tbl->nitems - tbl->shift, tbl->maxelems ); for( next = 0; next < tbl->maxelems; next++ ) TblDrawItem(tbl, next + tbl->shift, NO, tbl->scrollok ? YES : NO); tbl->exposed = YES; /* окно изображено */ } /* Нарисовать элемент таблицы */ void TblDrawItem( Table *tbl, int at, int reverse, int selection){ register WINDOW *w = tbl->win; int pos; char *s; int hatch, bold, label, under; int ax, axl, ay, column; if( at >= 0 && at < tbl->nitems ){ s = T_ITEM( tbl, at ); if( tbl->fmt ) s = TblConvert(s, tbl->fmt, tbl->cutpos); else if( tbl->cutpos > 0 ) s = TblCut(s, tbl->cutpos); /* выделения */ hatch = T_TST( tbl, at, T_HATCH ); bold = T_TST( tbl, at, T_BOLD ); label = T_TST( tbl, at, T_LABEL ); under = T_TST( tbl, at, I_EXE ); } else { s = "~"; label = hatch = bold = under = NO; } at -= tbl->shift; /* координату в списке перевести в коорд. окна */ ay = tbl->top + at % tbl->height; column = at / tbl->height; /* начало поля меток */ axl = tbl->left + tbl->space + column * (tbl->space + tbl->elen); /* начало строки-элемента */ ax = axl + TLABSIZE; if(selection) TblBox( tbl, at, reverse, reverse && hatch, strlen(s), axl, ax, ay ); wattrset (w, reverse ? tbl->sel_attrib : tbl->bg_attrib); if( hatch ) wattron(w, A_ITALICS); if( bold ) wattron(w, A_BOLD); if( under ) wattron(w, A_UNDERLINE); mvwaddstr(w, ay, ax, s); wattrset(w, tbl->bg_attrib | (bold ? A_BOLD:0)); if( label ) mvwaddch(w, ay, axl, LABEL); if( under ){ wattron(w, A_BOLD); mvwaddch(w, ay, axl+1, BOX_HATCHED);} wattrset(w, tbl->bg_attrib); if( column != tbl->cols-1 ) /* не последний столбец */ mvwaddch(w, ay, axl+tbl->elen-1 + (tbl->space+1)/2, VER_LINE); wmove(w, ay, ax-1); /* курсор перед началом строки */ SetPoint(tbl->savep, ay, ax-1); /* запомнить координаты курсора */ } /* Зачистить область окна для рисования элемента таблицы */ void TblBox(Table *tbl, int at, int reverse, int hatched, int width, int axl, int axi, int ay){ register WINDOW *w = tbl->win; int len = tbl->elen; wattrset (w, tbl->bg_attrib); wboxerase(w, axl, ay, axl+len-1, ay); wattrset (w, reverse ? tbl->sel_attrib : tbl->bg_attrib); /* если ниже задать axl+len+1, то подсвеченный * прямоугольник будет фиксированного размера */ wboxerase(w, axi, ay, axl+width-1, ay); wattrset (w, tbl->bg_attrib); } /* Зачистить прямоугольную рабочую область окна tbl->win, * в которой будет изображаться таблица. * Эта функция нигде не вызывается ЯВНО, поэтому ВЫ должны * вызывать ее сами после каждого TblInit() - * для этого удобно поместить ее в демон (*showMe)(); */ int TblClear( Table *tbl ){ tbl->exposed = NO; tbl->elems = 0; /* Это всегда происходит при exposed:= NO */ wboxerase( tbl->win, tbl->left, tbl->top, tbl->left + tbl->width - 1, tbl->top + tbl->height - 1); return 1; } /* Пометить элемент в таблице */ void TblTag( Table *tbl, int at, int flag){ if( T_TST(tbl, at, flag)) return; T_SET(tbl, at, flag); if( T_VISIBLE(tbl, at)) TblDrawItem(tbl, at, tbl->current == at ? YES:NO, YES ); } /* Снять пометку с элемента таблицы */ void TblUntag( Table *tbl, int at, int flag){ if( ! T_TST(tbl, at, flag)) return; T_CLR(tbl, at, flag); if( T_VISIBLE(tbl, at)) TblDrawItem(tbl, at, tbl->current == at ? YES:NO, YES ); } /* Изменить пометку элемента таблицы */ void TblRetag( Table *tbl, int at, int flag){ if( T_TST(tbl, at, flag)) T_CLR(tbl, at, flag); else T_SET(tbl, at, flag); if( T_VISIBLE(tbl, at)) TblDrawItem(tbl, at, tbl->current == at ? YES:NO, YES ); } /* Используется в match() для выдачи сообщения об ошибке */ void TblMatchErr(){} /* Пометить элементы, чьи имена удовлетворяют шаблону */ void TblTagAll( Table *tbl, char *pattern, int flag ){ register i; for(i=0; i < tbl->nitems; i++) if( !T_TST(tbl, i, I_DIR) && match( T_ITEMF(tbl, i, 0), pattern)) TblTag( tbl, i, flag ); } /* Снять пометки с элементов по шаблону имени */ void TblUntagAll( Table *tbl, char *pattern, int flag ){ register i; for(i=0; i < tbl->nitems; i++) if( match( T_ITEMF(tbl, i, 0), pattern)) TblUntag( tbl, i, flag ); } /* Указать на элемент по шаблону его имени */ int TblPlaceByName( Table *tbl, char *p ){ register i; char *s; for( i=0; i < tbl->nitems; i++ ){ s = T_ITEMF(tbl, i, 0); if( match( s, p )){ if( tbl->exposed == NO ){ /* Задать некорректный shift, * чтобы окно полностью перерисовалось */ tbl->shift = tbl->nitems+1; tbl->elems = 0; } TblPointAt( tbl, i ); return i; } } return (-1); } /* Перемещение по таблице набором первых букв названия элемента */ static int TblTrack( Table *tbl, int c){ char *s; register i; int from; /* с какого элемента начинать поиск */ int found = 0; /* сколько было найдено */ int plength = 0; int more = 0; char pattern[20]; if( c >= 0400 || iscntrl(c)){ beep(); return 0; } AddCh: from = 0; pattern[plength] = c; pattern[plength+1] = '*'; pattern[plength+2] = '\0'; plength++; More: for(i = from; i < tbl->nitems; i++){ s = T_ITEMF(tbl, i, 0); if( match(s, pattern)){ ++found; from = i+1; TblPointAt( tbl, i ); c = WinGetch( tbl->win ); switch(c){ case '\t': /* find next matching */ more++; goto More; case KEY_BACKSPACE: case '\177': case '\b': if( plength > 1 ){ plength--; pattern[plength] = '*'; pattern[plength+1] = '\0'; from = 0; more++; goto More; } else goto out; default: if( c >= 0400 || iscntrl(c)) return c; if( plength >= sizeof pattern - 2 ) goto out; goto AddCh; } } } /* не найдено */ if(more && found){ /* нет БОЛЬШЕ подходящих, но ВООБЩЕ - есть */ beep(); more = found = from = 0; goto More; } out: beep(); return 0; } /* Выбор в таблице */ int TblUsualSelect( Table *tbl ){ int c, want; tbl->key = (-1); if( tbl->items == NULL || tbl->nitems <= 0 ) return TOTAL_NOSEL; TblChk( tbl ); if( tbl->showMe ) if((*tbl->showMe)(tbl) <= 0 ) return (-1); if( !tbl->win ) return TOTAL_NOSEL; if( tbl->exposed == NO ){ TblDraw ( tbl ); } /* Указать текущий элемент */ TblPoint( tbl, tbl->current, NO); TblReport( tbl ); for( ;; ){ c = WinGetch(tbl->win); INP: if( tbl->hitkeys && tbl->handler ){ HandlerReply reply; if( is_in(c, tbl->hitkeys)){ c = (*tbl->handler)(tbl, c, &reply); TblReport( tbl ); /* restore scroll bar */ switch( reply ){ case HANDLER_CONTINUE: continue; case HANDLER_NEWCHAR: goto INP; case HANDLER_OUT: goto out; case HANDLER_SWITCH: default: break; /* goto switch(c) */ } } } sw: switch( c ){ case KEY_LEFT: want = tbl->current - tbl->height; goto mv; case KEY_RIGHT: want = tbl->current + tbl->height; goto mv; case KEY_UP: want = tbl->current - 1; goto mv; case KEY_DOWN: next: want = tbl->current + 1; goto mv; case KEY_HOME: want = 0; goto mv; case KEY_END: want = tbl->nitems - 1; goto mv; case KEY_NPAGE: want = tbl->current + tbl->elems; goto mv; case KEY_PPAGE: want = tbl->current - tbl->elems; goto mv; case KEY_IC: if( T_TST(tbl, tbl->current, T_LABEL )) T_CLR(tbl, tbl->current, T_LABEL ); else T_SET(tbl, tbl->current, T_LABEL); if( tbl->current == tbl->nitems - 1 /* LAST */){ TblPoint(tbl, tbl->current, NO ); break; } TblPointAt(tbl, tbl->current ); /* if not goto next; * but break; * then use * TblPoint(tbl, tbl->current, NO); * here */ goto next; case KEY_DC: if( T_TST(tbl, tbl->current, T_HATCH )) T_CLR(tbl, tbl->current, T_HATCH ); else T_SET(tbl, tbl->current, T_HATCH); if( tbl->current == tbl->nitems - 1 /* LAST */){ TblPoint(tbl, tbl->current, NO ); break; } TblPointAt(tbl, tbl->current ); goto next; case ESC: case '\r': case '\n': goto out; case 0: break; default: c = TblTrack(tbl, c); if( c ) goto INP; break; } continue; mv: TblPointAt( tbl, want ); } out: wnoutrefresh( tbl->win ); if( tbl->hideMe ) (*tbl->hideMe)(tbl); return ((tbl->key = c) == ESC ? -1 : tbl->current ); } # Пример 23 - simple visual shell. # UNIX commander ######################################################################### # Это файл Makefile для проекта uxcom - простого меню-ориентированного # экранного интерфейса для переходов по файловой системе. # Ключ -Iкаталог указывает из какого каталога должны браться # include-файлы, подключаемые по #include "имяФайла". # Проект состоит из нескольких файлов: # Пример 17, Пример 18, Пример 19, Пример 21, Пример 23 и других. # # + Left Right _Commands Tools Sorttype + # | /usr/a+---------------------008/013-+ | # +-----------------| Главное меню |---+--+ # | .. +--------------------------+--+ | | # | .BAD | Current directory | | | | # | .contents.m| Root directory | | |##| # | DUMP | Menus | | | | # | Makefile +--------------------------+ | | | # | PLAN | Help | | | | # | _points | Unimplemented | | | | # | table | Change sorttype |##| | | # | #unbold | _Look directory history | | | | # | #uxcom +--------------------------+ | | | # | x.++ | Quit | | | | # | 00 +--------------------------+ | | | # | 11 | Redraw screen | | | | # | LOOP_p +--------------------------+--+ | | # | LOOP_q .c | etc | | # | LOOP_strt .c | install | | # +-------------------------+-------------------------+ | # | points 165 -r--r-- | .cshrc 2509 -rw-r--r-- | | # +-------------------------+-------------------------+ | # | История путешествий | | # +---------------------------------------------------+--+ # SHELL=/bin/sh SRCS = glob.c w.c menu.c pull.c match.c pwd.c hist.c line.c table.c \ main.c treemk.c OBJS = glob.o w.o menu.o pull.o match.o pwd.o hist.o line.o table.o \ main.o treemk.o # INCLUDE = /usr/include # LIB = -lncurses INCLUDE = -I../../src/curses LIB = ../../src/curses/libncurses.a DEFINES = -DUSG -DTERMIOS CC = cc -O # стандартный C-compiler + оптимизация #CC = gcc -O # GNU C-compiler uxcom: $(OBJS) $(CC) $(OBJS) -o $@ $(LIB) sync; ls -l $@; size $@ glob.o: glob.c glob.h # это файл "Пример 18" $(CC) -c glob.c w.o: w.c w.h # это файл "Пример 17" $(CC) -c $(INCLUDE) $(DEFINES) w.c menu.o: menu.c glob.h w.h menu.h # это файл "Пример 19" $(CC) -c $(INCLUDE) $(DEFINES) menu.c pull.o: pull.c glob.h w.h menu.h pull.h # это файл "Пример 20" $(CC) -c $(INCLUDE) $(DEFINES) pull.c match.o: match.c $(CC) -c -DMATCHONLY \ -DMATCH_ERR="TblMatchErr()" match.c pwd.o: pwd.c $(CC) -c -DU42 -DCWDONLY pwd.c treemk.o: treemk.c $(CC) -c $(DEFINES) \ -DERR_CANT_READ=tree_err_cant_read \ -DERR_NAME_TOO_LONG=tree_name_too_long \ -DTREEONLY -DU42 treemk.c hist.o: hist.c hist.h glob.h menu.h w.h # это файл "Пример 21" $(CC) -c $(INCLUDE) $(DEFINES) hist.c line.o: line.c w.h glob.h menu.h hist.h line.h # "Пример 21" $(CC) -c $(INCLUDE) $(DEFINES) line.c table.o: table.c w.h glob.h menu.h table.h # "Пример 22" $(CC) -c $(INCLUDE) $(DEFINES) table.c main.o: main.c glob.h w.h menu.h hist.h line.h pull.h table.h $(CC) -c $(INCLUDE) $(DEFINES) main.c w.h: wcur.h touch w.h /* _______________________ файл main.c __________________________ */ /* Ниже предполагается, что вы раскрасили в /etc/termcap * * выделения A_STANDOUT и A_REVERSE в РАЗНЫЕ цвета ! */ #include "w.h" #include "glob.h" #include "menu.h" #include "hist.h" #include "line.h" #include "table.h" #include "pull.h" #include <signal.h> #include <ustat.h> #include <locale.h> void t_enter(), t_leave(); LineEdit edit; /* редактор строки */ Hist hcwd, hedit, hpat; /* истории: */ /* посещенные каталоги, набранные команды, шаблоны имен */ Menu mwrk, msort; /* должны иметь класс static */ PullMenu pull; typedef enum { SEL_WRK=0, SEL_PANE1, SEL_PANE2, SEL_PULL, SEL_HELP } Sel; Sel current_menu; /* текущее активное меню */ Sel previous_menu; /* предыдущее активное меню */ #define SEL_PANE (current_menu == SEL_PANE1 || current_menu == SEL_PANE2) typedef struct { Table t; /* таблица с именами файлов */ DirContents d; /* содержимое каталогов */ } FileWidget; FileWidget tpane1, tpane2; /* левая и правая панели */ FileWidget *A_pane = &tpane1; /* активная панель */ FileWidget *B_pane = &tpane2; /* противоположная панель */ #define A_tbl (&A_pane->t) #define A_dir (&A_pane->d) #define B_tbl (&B_pane->t) #define B_dir (&B_pane->d) #define TblFW(tbl) ((tbl) == A_tbl ? A_pane : B_pane) void ExchangePanes(){ /* Обменять указатели на панели */ FileWidget *tmp = A_pane; A_pane = B_pane; B_pane = tmp; current_menu = (current_menu == SEL_PANE1 ? SEL_PANE2 : SEL_PANE1); } #define Other_pane(p) ((p) == A_pane ? B_pane : A_pane) #define Other_tbl(t) ((t) == A_tbl ? B_tbl : A_tbl ) WINDOW *panewin; /* окно, содержащее обе панели = stdscr */ typedef enum { NORUN=0, RUNCMD=1, CHDIR=2, TAG=3, FIND=4 } RunType; #define REPEAT_KEY 666 /* псевдоклавиша "повтори выбор в меню" */ #define LEAVE_KEY 777 /* псевдоклавиша "покинь это меню" */ #define NOSELECTED (-1) /* в меню ничего пока не выбрано */ #define CENTER (COLS/2-2) /* линия раздела панелей */ int done; /* закончена ли программа ? */ char CWD[MAXLEN]; /* полное имя текущего каталога */ char SELECTION[MAXLEN]; /* имя выбранного файла */ /*-----------------------------------------------------------------*/ /* Выдать подсказку в строке редактора */ /*-----------------------------------------------------------------*/ #include <stdarg.h> void Message(char *s, ... ){ char msg[80]; va_list args; int field_width; va_start(args, s); vsprintf(msg, s, args); va_end(args); wattrset (panewin, A_tbl->sel_attrib); field_width = A_tbl->width + B_tbl->width - 3; mvwprintw (panewin, LINES-2, tpane1.t.left+1, " %*.*s ", -field_width, field_width, msg); wattrset (panewin, A_tbl->bg_attrib); wnoutrefresh(panewin); } /*-----------------------------------------------------------------* * Меню порядка сортировки имен файлов. * *-----------------------------------------------------------------*/ Info sort_info[] = { { "По возрастанию", 0}, { "По убыванию", 0}, { "По суффиксу", 0}, { "Без сортировки", 0}, { "По размеру", M_HATCH}, { NULL, 0} }; /* При входе в меню сортировки указать текущий тип сортировки */ void sort_show(Menu *m){ MnuPointAt(&msort, (int) sorttype); } /* Выбрать тип сортировки имен файлов */ static void SelectSortType(int sel){ if( sel == NOSELECTED ) sel = MnuUsualSelect(&msort, NO); MnuHide(&msort); current_menu = previous_menu; if(M_REFUSED(&msort)) return; sorttype = (Sort) sel; A_dir->lastRead = B_dir->lastRead = 0L; /* форсировать перечитку */ /* но ничего явно не пересортировывать и не перерисовывать */ } /*-----------------------------------------------------------------* * Отслеживание содержимого каталогов и переинициализация меню. * *-----------------------------------------------------------------*/ #define NON_VALID(d) ((d)->readErrors || (d)->valid == NO) /* Сменить содержимое таблицы и списка файлов */ void InitTblFromDir(FileWidget *wd, int chdired, char *savename){ char *msg, *name; Table *tbl = &(wd->t); DirContents *d = &wd->d; int saveind = tbl->current, saveshift = tbl->shift; char *svname = NULL; if(tbl->nitems > 0 ) svname = strdup(T_ITEMF(tbl, saveind, 0)); /* Несуществующие и нечитаемые каталоги выделить особо */ if( NON_VALID(d)) wattrset(tbl->win, A_REVERSE); TblClear(tbl); if(d->valid == NO){ msg = "Не существует"; name = d->name; goto Report; } else if(d->readErrors){ /* тогда d->files->s == NULL */ msg = "Не читается"; name = d->name; Report: mvwaddstr(tbl->win, tbl->top + tbl->height/2, tbl->left + (tbl->width - strlen(name))/2, name); mvwaddstr(tbl->win, tbl->top + tbl->height/2+1, tbl->left + (tbl->width - strlen(msg))/2, msg); } wattrset(tbl->win, tbl->bg_attrib); tbl->items = d->files; TblInit(tbl, NO); /* Постараться сохранить позицию в таблице */ if( chdired ) TblPlaceByName(tbl, savename); else { if( svname == NULL || TblPlaceByName(tbl, svname) < 0 ){ tbl->shift = saveshift; tbl->current = saveind; TblChk(tbl); } } if(svname) free(svname); } /* Перейти в каталог и запомнить его полное имя */ int mychdir(char *newdir){ int code = chdir(newdir); if( code < 0 ) return code; getwd(CWD); in_the_root = (strcmp(CWD, "/") == 0); HistAdd(&hcwd, CWD, 0); /* запомнить в истории каталогов */ t_enter(&tpane1.t); /* на рамке нарисовать имя текущего каталога */ return code; } /* Изменить текущий каталог и перечитать его содержимое */ int cd(char *newdir, FileWidget *wd, char *oldname){ char oldbase[MAXLEN], *s, *strrchr(char *,char); /* Спасти в oldbase базовое имя старого каталога oldname (обычно CWD) */ if(s = strrchr(oldname, '/')) s++; else s = oldname; strcpy(oldbase, s); if( mychdir(newdir) < 0){ /* не могу перейти в каталог */ Message("Не могу перейти в %s", *newdir ? newdir : "???"); beep(); return (-1); } if( ReadDir(CWD, &wd->d)){ /* содержимое изменилось */ InitTblFromDir (wd, YES, oldbase); return 1; } return 0; } /* Проверить содержимое обеих панелей */ void checkBothPanes(){ /* Случай NON_VALID нужен только для того, чтобы Init... восстановил "аварийную" картинку в панели */ if( ReadDir(tpane1.d.name, &tpane1.d) || NON_VALID(&tpane1.d)) InitTblFromDir(&tpane1, NO, NULL); if( tpane1.t.exposed == NO ) TblDraw(&tpane1.t); if( ReadDir(tpane2.d.name, &tpane2.d) || NON_VALID(&tpane2.d)) InitTblFromDir(&tpane2, NO, NULL); if( tpane2.t.exposed == NO ) TblDraw(&tpane2.t); } /*-----------------------------------------------------------------* * Ввод команд и выдача подсказки. * *-----------------------------------------------------------------*/ /* Особая обработка отдельных клавиш в редакторе строки */ char e_move = NO; /* кнопки со стрелками <- -> двигают курсор по строке/по таблице */ int e_hit[] = { KEY_LEFT, KEY_RIGHT, KEY_UP, KEY_DOWN, KEY_F(0), KEY_IC, ctrl('G'), ctrl('E'), ctrl('L'), ctrl('F'), ctrl('X'), ctrl('Y'), -1 }; int e_handler (LineEdit *le, int c, HandlerReply *reply){ *reply = HANDLER_CONTINUE; switch(c){ /* Перемещение по таблице без выхода из редактора строки */ case KEY_LEFT: if( !SEL_PANE || !e_move){ *reply=HANDLER_SWITCH; return c; } TblPointAt(A_tbl, A_tbl->current - A_tbl->height); break; case KEY_RIGHT: if( !SEL_PANE || !e_move){ *reply=HANDLER_SWITCH; return c; } TblPointAt(A_tbl, A_tbl->current + A_tbl->height); break; case KEY_DOWN: if( !SEL_PANE){ *reply=HANDLER_SWITCH; return c; } TblPointAt(A_tbl, A_tbl->current + 1); break; case KEY_UP: if( !SEL_PANE){ *reply=HANDLER_SWITCH; return c; } TblPointAt(A_tbl, A_tbl->current - 1); break; case KEY_F(0): /* F10 */ e_move = !e_move; break; case KEY_IC: if( !SEL_PANE){ *reply=HANDLER_SWITCH; return c; } TblRetag(A_tbl, A_tbl->current, T_LABEL); TblPointAt(A_tbl, A_tbl->current+1); break; /* Подстановки */ case ctrl('G'): /* подставить полное имя домашнего каталога */ LeInsStr(le, getenv("HOME")); LeInsStr(le, " "); break; case ctrl('E'): /* подставить имя выбранного файла */ if( A_tbl->nitems ) LeInsStr(le, T_ITEMF(A_tbl, A_tbl->current, 0)); LeInsStr(le, " "); break; case ctrl('L'): /* подставить имя выбранного файла из другой панели */ LeInsStr(le, T_ITEMF(B_tbl, B_tbl->current, 0)); LeInsStr(le, " "); break; case ctrl('X'): case ctrl('Y'): /* подстановка имен помеченных файлов */ { int label = (c == ctrl('X') ? T_LABEL : T_HATCH); register i; for(i=0; i < A_tbl->nitems && le->len < le->maxlen; ++i ) if( T_TST(A_tbl, i, label)){ LeInsStr(le, " "); LeInsStr(le, T_ITEMF(A_tbl, i, 0)); } } break; case ctrl('F'): /* подставить имя текущего каталога */ LeInsStr(le, CWD); LeInsStr(le, " "); break; } return c; } /* При начале редактирования ставь курсор в конец строки */ void e_pos (LineEdit *le){ le->pos = le->len; } /* Обозначить, что мы покинули редактор строки */ void e_hide(LineEdit *le){ le->sel_attrib = le->fr_attrib = le->bg_attrib = A_ITALICS; LeDraw(le); } /* Отредактировать строку в предпоследней строке окна */ char *Edit(WINDOW *w, char *src, RunType dorun){ static char CMD[MAXLEN]; /* буфер для строки команды */ int c; if(w != TOPW){ beep(); return NULL; }/* это должно быть верхнее окно */ keypad(w, TRUE); /* Проинициализировать редактор строки */ switch(dorun){ case NORUN: edit.histIn = edit.histOut = NULL; break; case RUNCMD: edit.histIn = edit.histOut = &hedit; break; case FIND: case TAG: edit.histIn = edit.histOut = &hpat; break; case CHDIR: edit.histIn = &hcwd; edit.histOut = NULL; break; } edit.line = CMD; edit.maxlen = sizeof(CMD)-1; edit.top = wlines(w)-2; edit.left = 2; edit.width = wcols (w)-4 - (1+BARWIDTH); edit.insert = YES; edit.nc = YES; edit.win = w; edit.wl_attrib = edit.bg_attrib=A_REVERSE; edit.fr_attrib=A_STANDOUT; edit.sel_attrib = A_NORMAL|A_BLINK; edit.posMe = e_pos; edit.hitkeys = (SEL_PANE ? e_hit : e_hit+5); edit.handler = e_handler; /* edit.hideMe = e_hide; вызывается ЯВНО */ /* остальные поля равны 0, т.к. edit - статическое данное */ for(;;){ strcpy(CMD, src); if(*src){ strcat(CMD, " "); } c = LeEdit( &edit ); if( LE_REFUSED(&edit) || dorun != RUNCMD || !*CMD || c != '\n' ) break; /* курсор в нижнюю строку экрана */ attrset(A_NORMAL); move(LINES-1, 0); refresh(); resetterm(); /* приостановить работу curses-а */ putchar('\n'); /* промотать экран на строку */ system(CMD); /* выполнить команду внешним Шеллом */ fprintf(stderr,"Нажми ENTER чтобы продолжить --- ");gets(CMD); fixterm(); /* возобновить работу curses-а */ RedrawScreen(); /* перерисовать экран */ if(w == panewin){ checkBothPanes(); if(A_tbl->nitems) TblPoint(A_tbl, A_tbl->current, NO); } src = ""; /* во второй раз ничего не подставлять */ } wattrset(w, A_NORMAL); /* ? */ e_hide ( &edit ); return ( *CMD && !LE_REFUSED(&edit)) ? CMD : NULL; } /* Выдача подсказки а также сообщений об ошибках. */ /* В этом же окне можно набирать команды (dorun==RUNCMD). */ char *help(char *msg, RunType dorun){ register i; char *s; static char *helptext[] = { "ESC - выход в главное меню", "F1 - подсказка", "INS - пометить файл", "ctrl/E - подставить имя выбранного файла", "ctrl/L - подставить имя из другой панели", "ctrl/X - подставить помеченные файлы", "ctrl/Y - подставить помеченные курсивом", "ctrl/G - подставить имя домашнего каталога", "ctrl/F - подставить имя текущего каталога", "F4 - история", "F7 - переключить режим вставки/замены", "F10 - переключить перемещения по строке/по панели", }; #define HELPLINES (sizeof(helptext)/sizeof helptext[0]) Sel save_current_menu = current_menu; /* "выскакивающее" POP-UP window */ WINDOW *w = newwin(2+1+HELPLINES+1, 70, 2, (COLS-70)/2); if( w == NULL ) return NULL; current_menu = SEL_HELP; wattrset(w, A_REVERSE); /* это будет инверсное окно */ werase (w); /* заполнить инверсным фоном */ wborder(w); RaiseWin(w); /* окно появляется */ if(*msg){ wattron (w, A_BOLD); mvwaddstr(w, 1+HELPLINES, 2, msg); wattroff(w, A_BOLD); } for(i=0; i < HELPLINES; i++) mvwaddstr(w, 1+i, 2, helptext[i]); s = Edit(w, "", dorun); PopWin(); /* окно исчезает */ current_menu = save_current_menu; return s; } /*-----------------------------------------------------------------* * Управляющее меню. * *-----------------------------------------------------------------*/ int f_left(), f_right(), f_pull(), f_help(), f_sort(), f_dir(), f_bye(), f_redraw(),f_cdroot(); /* Обратите внимание, что можно указывать не все поля структуры, * а только первые. Остальные равны 0 */ #ifndef __GNUC__ Info mwrk_info[] = { /* строки для главного меню */ { "\\Current directory", 0 , f_left }, /* 0 */ { "\\Root directory", M_HATCH , f_right }, /* 1 */ { "\\Menus", 0 , f_pull }, /* 2 */ { "\1", /* гориз. черта */ 0 }, /* 3 */ { "\\Help", 0 , f_help }, /* 4 */ { "Un\\implemented", I_NOSEL }, /* 5 */ { "Change \\sorttype", 0 , f_sort }, /* 6 */ { "Look directory \\history", 0 , f_dir }, /* 7 */ { "\1", /* гориз. черта */ 0 }, /* 8 */ { "\\Quit", M_BOLD , f_bye }, /* 9 */ { "\1", /* гориз. черта */ 0 }, /* 10 */ { "\\Redraw screen", M_HATCH , f_redraw}, /* 11 */ { "Chdir both panels to /", M_HATCH , f_cdroot}, /* 12 */ { NULL, 0 } }; #else /* GNU C-компилятор 1.37 не может инициализировать поля-union-ы */ static char _gnu_[] = "Compiled with GNU C-compiler"; Info mwrk_info[] = { /* строки для главного меню */ { "\\Current directory", 0 }, { "\\Root directory", M_HATCH }, { "\\Menus", 0 }, { "\1", /* гориз. черта */ 0 }, { "\\Help", 0 }, { "Un\\implemented", I_NOSEL }, { "Change \\sorttype", 0 }, { "Look directory \\history", 0 }, { "\1", /* гориз. черта */ 0 }, { "\\Quit", M_BOLD }, { "\1", /* гориз. черта */ 0 }, { "\\Redraw screen", M_HATCH }, { "Chdir both panels to /", M_HATCH }, { NULL, 0 } }; void mwrk_init(){ mwrk_info [0].any.act = f_left; mwrk_info [1].any.act = f_right; mwrk_info [2].any.act = f_pull; mwrk_info [4].any.act = f_help; mwrk_info [6].any.act = f_sort; mwrk_info [7].any.act = f_dir; mwrk_info [9].any.act = f_bye; mwrk_info[11].any.act = f_redraw; mwrk_info[12].any.act = f_cdroot; } #endif char *mwrk_help[] = { "Перейти в левую панель", "Перейти в правую панель", "Перейти в строчное меню", "", "Выдать подсказку", "Не реализовано", "Изменить тип сортировки имен", "История путешествий", "", "Выход", "", "Перерисовка экрана", "Обе панели поставить в корневой каталог", NULL }; void m_help(Menu *m, int n, int among){ Message(mwrk_help[n]); } /* Выбор в рабочем (командном) меню */ void SelectWorkingMenu(int sel){ if(sel == NOSELECTED) sel = MnuUsualSelect( & mwrk, NO); if( M_REFUSED(&mwrk)) help("Выбери Quit", NORUN); else if(mwrk.items[sel].any.act) (*mwrk.items[sel].any.act)(); if( !done) MnuHide( & mwrk ); } f_left () { current_menu = SEL_PANE1; return 0; } f_right() { current_menu = SEL_PANE2; return 0; } f_pull () { current_menu = SEL_PULL; return 0; } f_help () { help("Нажми ENTER или набери команду:", RUNCMD); return 0; } f_sort () { SelectSortType(NOSELECTED); return 0; } f_dir () { Info *idir; if(idir = HistSelect(&hcwd, 20, 3)) cd(idir->s, &tpane2, CWD); current_menu = SEL_PANE2; return 0; } f_bye () { done++; return 0; } f_redraw() { RedrawScreen(); return 0; } f_cdroot() { cd("/", &tpane1, CWD); cd("/", &tpane2, CWD); checkBothPanes(); return 0; } /*-----------------------------------------------------------------* * Выдача информации про файл, редактирование кодов доступа. * *-----------------------------------------------------------------*/ void MYwaddstr(WINDOW *w, int y, int x, int maxwidth, char *s){ register pos; for(pos=0; *s && *s != '\n' && pos < maxwidth; ++s){ wmove(w, y, x+pos); if( *s == '\t') pos += 8 - (pos & 7); else if( *s == '\b'){ if(pos) --pos; } else if( *s == '\r') pos = 0; else { ++pos; waddch(w, isprint(*s) ? *s : '?'); } } } /* Просмотр начала файла в противоположной панели. */ void fastView( char *name, /* имя файла */ unsigned mode, /* некоторые типы файлов не просматривать */ Table *otbl /* противоположная панель */ ){ FILE *fp; register int x, y; char buf[512]; TblClear(otbl); Message("Нажми ENTER для окончания. " "ПРОБЕЛ - изменяет код доступа. " "ESC - откатка."); if( !ISREG(mode)) goto out; if((fp = fopen(name, "r")) == NULL){ Message("Не могу читать %s", name); return; } for(y=0; y < otbl->height && fgets(buf, sizeof buf, fp); y++) MYwaddstr(panewin, otbl->top+y, otbl->left+1, otbl->width-2, buf); fclose(fp); out: wrefresh(otbl->win); /* проявить */ } static struct attrNames{ unsigned mode; char name; char acc; int off; } modes[] = { { S_IREAD, 'r', 'u', 0 }, { S_IWRITE, 'w', 'u', 1 }, { S_IEXEC, 'x', 'u', 2 }, { S_IREAD >> 3, 'r', 'g', 3 }, { S_IWRITE >> 3, 'w', 'g', 4 }, { S_IEXEC >> 3, 'x', 'g', 5 }, { S_IREAD >> 6, 'r', 'o', 6 }, { S_IWRITE >> 6, 'w', 'o', 7 }, { S_IEXEC >> 6, 'x', 'o', 8 }, }; #define NMODES (sizeof(modes)/sizeof(modes[0])) /* Позиция в которой изображать i-ый бит кодов доступа */ #define MODE_X_POS(tbl, i) (tbl->left + DIR_SIZE + 12 + modes[i].off) #define MODE_Y_POS(tbl) (tbl->top + tbl->height + 1) #ifdef FILF /* Изобразить информацию о текущем выбранном файле */ void showMode(Table *tbl, int attr){ Info *inf = & tbl->items[tbl->current]; /* файл */ register i; unsigned mode = inf->mode; /* коды */ int uid = inf->uid, gid = inf->gid; /* хозяин */ /* идентификаторы хозяина и группы процесса-коммандера */ static char first = YES; static int myuid, mygid; WINDOW *win = tbl->win; int xleft = tbl->left + 1, y = MODE_Y_POS(tbl); if( first ){ first = NO; myuid = getuid(); mygid = getgid(); } wattron (win, attr); mvwprintw(win, y, xleft, " %*.*s %8ld ", /* имя файла */ -DIR_SIZE, DIR_SIZE, inf->s ? (!strcmp(inf->s, "..") ? "<UP-DIR>": inf->s) : "(EMPTY)", inf->size); /* тип файла (обычный|каталог|устройство) */ wattron (win, A_ITALICS|A_BOLD); waddch (win, ISDIR(mode) ? 'd': ISDEV(mode) ? '@' : '-'); wattroff(win, A_ITALICS|A_BOLD); /* коды доступа */ for(i=0; i < NMODES; i++){ if((modes[i].acc == 'u' && myuid == uid) || (modes[i].acc == 'g' && mygid == gid) || (modes[i].acc == 'o' && myuid != uid && mygid != gid)) ; else wattron(win, A_ITALICS); mvwaddch(win, y, MODE_X_POS(tbl, i), mode & modes[i].mode ? modes[i].name : '-'); wattroff(win, A_ITALICS); } waddch(win, ' '); wattroff(win, attr); } #define newmode (tbl->items[tbl->current].mode) /* Редактирование кодов доступа к файлам. */ int editAccessModes(FileWidget *wd){ Table *tbl = &wd->t; Table *otbl = &(Other_pane(wd)->t); /* или Other_tbl(tbl); */ unsigned prevmode, oldmode; /* старый код доступа */ char *name; /* имя текущего файла */ WINDOW *win = tbl->win; int position = 0, c; for(;;){ /* Цикл выбора файлов в таблице */ name = T_ITEMF(tbl, tbl->current, 0); oldmode = newmode; /* запомнить */ fastView(name, newmode, otbl); /* показать первые строки файла */ for(;;){ /* Цикл обработки выбранного файла */ wmove(win, MODE_Y_POS(tbl), MODE_X_POS(tbl, position)); switch(c = WinGetch(win)){ /* Некоторые клавиши вызывают перемещение по таблице */ case KEY_BACKTAB: TblPointAt(tbl, tbl->current - tbl->height); goto mv; case '\t': TblPointAt(tbl, tbl->current + tbl->height); goto mv; case KEY_UP: TblPointAt(tbl, tbl->current - 1); goto mv; case KEY_DOWN: TblPointAt(tbl, tbl->current + 1); goto mv; case KEY_HOME: TblPointAt(tbl, 0); goto mv; case KEY_END: TblPointAt(tbl, tbl->nitems-1); goto mv; /* Прочие клавиши предназначены для редактирования кодов доступа */ case KEY_LEFT: if(position) --position; break; case KEY_RIGHT: if(position < NMODES-1) position++; break; default: goto out; case ESC: /* Восстановить старые коды */ prevmode = newmode = oldmode; goto change; case ' ': /* Инвертировать код доступа */ prevmode = newmode; /* запомнить */ newmode ^= modes[position].mode; /* инвертировать */ change: if( chmod(name, newmode) < 0){ beep(); Message("Не могу изменить доступ к %s", name); newmode = prevmode; /* восстановить */ } else /* доступ изменен, показать это */ showMode(tbl, A_REVERSE); break; } } /* Конец цикла обработки выбранного файла */ mv: ; } /* Конец цикла выбора файлов в таблице */ out: /* Очистить противоположную панель после fastView(); */ Message(""); TblClear(otbl); return c; } #undef newmode #else void editAccessModes(FileWidget *wd){} #endif long diskFree(){ struct ustat ust; struct stat st; long freespace; if(stat(".", &st) < 0) return 0; ustat(st.st_dev, &ust); freespace = ust.f_tfree * 512L; freespace /= 1024; Message("В %*.*s свободно %ld Кб.", -sizeof(ust.f_fname), sizeof(ust.f_fname), *ust.f_fname ? ust.f_fname : ".", freespace); doupdate(); /* проявить окно для Message() */ return freespace; } /*-----------------------------------------------------------------* * Специальные команды, использующие обход дерева *-----------------------------------------------------------------*/ /* Выдача сообщений об ошибках (смотри Makefile) */ int tree_err_cant_read(char *name){ Message("Не могу читать \"%s\"", name); return WARNING; } int tree_name_too_long(){ Message("Слишком длинное полное имя"); return WARNING; } char canRun; /* продолжать ли поиск */ /* Прерывание обхода по SIGINT */ void onintr_f(nsig){ canRun = NO; Message("Interrupted"); } /* ==== место, занимаемое поддеревом ==== */ long tu(int *count){ struct stat st; register i; long sum = 0L; *count = 0; for(i=0; i < A_tbl->nitems ;++i ) if( T_TST(A_tbl, i, T_LABEL)){ stat(T_ITEMF(A_tbl, i, 0), &st); #define KB(s) (((s) + 1024L - 1) / 1024L) sum += KB(st.st_size); (*count)++; } return sum; } void diskUsage(){ long du(), size, sizetagged; int n; char msg[512]; Message("Измеряем объем файлов..."); doupdate(); size = du("."); diskFree(); sizetagged = tu(&n); sprintf(msg, "%ld килобайт в %s, %ld кб в %d помеченных файлах", size, CWD, sizetagged, n); help(msg, NORUN); } /* ==== поиск файла ===================== */ extern char *find_PATTERN; /* imported from treemk.c */ extern Info gargv[]; extern int gargc; /* imported from glob.c */ /* Проверить очередное имя и запомнить его, если подходит */ static int findCheck(char *fullname, int level, struct stat *st){ char *basename = strrchr(fullname, '/'); if(basename) basename++; else basename = fullname; if( canRun == NO ) return FAILURE; /* поиск прерван */ if( match(basename, find_PATTERN)){ /* imported from match.c */ gargv[gargc] = NullInfo; /* зачистка */ gargv[gargc].s = strdup(fullname); gargv[gargc++].fl= ISDIR(st->st_mode) ? I_DIR : 0; gargv[gargc] = NullInfo; Message("%s", fullname); doupdate(); } /* Страховка от переполнения gargv[] */ if ( gargc < MAX_ARGV - 1 ) return SUCCESS; else { Message("Найдено слишком много имен."); return FAILURE; } } /* Собрать имена файлов, удовлетворяющие шаблону */ static Info *findAndCollect(char *pattern){ void (*old)() = signal(SIGINT, onintr_f); Sort saveSort; find_PATTERN = pattern; canRun = YES; Message("Ищем %s от %s", pattern, CWD); doupdate(); greset(); /* смотри glob.c, gargc=0; */ walktree(CWD, findCheck, NULL, findCheck); signal(SIGINT, old); saveSort = sorttype; sorttype = SORT_ASC; if(gargc) qsort( gargv, gargc, sizeof(Info), gcmps); sorttype = saveSort; return gargc ? blkcpy(gargv) : NULL; } /* Обработать собранные имена при помощи предъявления меню с ними */ void findFile(FileWidget *wd){ static Info *found; static Menu mfind; int c; Table *tbl = & wd->t; char *pattern = help("Введи образец для поиска, вроде *.c, " "или ENTER для прежнего списка", FIND); if( LE_REFUSED( &edit)) return; /* отказались от поиска */ /* Если набрана пустая строка, help() выдает NULL */ if( pattern ){ /* задан новый образец - ищем */ /* Уничтожить старый список файлов и меню */ if( found ) blkfree( found ); MnuDeinit( &mfind ); found = findAndCollect(pattern); /* поиск */ HistAdd( &hpat, pattern, 0); /* Образуем меню из найденных файлов */ if( found ){ /* если что-нибудь нашли */ mfind.items = found; mfind.title = pattern ? pattern : "Найденные файлы"; mfind.top = 3; mfind.left = COLS/6; mfind.bg_attrib = A_STANDOUT; mfind.sel_attrib = A_REVERSE; MnuInit (&mfind); } } /* else набрана пустая строка - просто вызываем список * найденных ранее файлов. */ if( found == NULL ){ Message("Ничего не найдено"); beep(); return; } c = MnuUsualSelect(&mfind, NO); /* Выбор файла в этом меню вызовет переход в каталог, * в котором содержится этот файл */ if( !M_REFUSED( &mfind )){ char *s = M_ITEM(&mfind, mfind.current); /* пометить выбранный элемент */ M_SET(&mfind, mfind.current, M_LABEL); /* если это каталог - войти в него */ if( M_TST(&mfind, mfind.current, I_DIR)) cd(s, wd, CWD); /* иначе войти в каталог, содержащий этот файл */ else { char *p; struct savech svch; /* смотри glob.h */ SAVE( svch, strrchr(s, '/')); *svch.s = '\0'; p = strdup(s); RESTORE(svch); if( !strcmp(CWD, p)) /* мы уже здесь */ TblPlaceByName(tbl, svch.s+1); /* указать курсором */ else /* изменить каталог и указать курсором на файл s */ cd(p, wd, s); free(p); } } MnuHide(&mfind); /* спрятать меню, не уничтожая его */ } /*-----------------------------------------------------------------* * Работа с панелями, содержащими имена файлов двух каталогов. * *-----------------------------------------------------------------*/ /* Восстановить элементы, затертые рамкой WinBorder */ void t_restore_corners(){ mvwaddch(panewin, LINES-3, 0, LEFT_JOIN); mvwaddch(panewin, LINES-3, COLS-2-BARWIDTH, RIGHT_JOIN); mvwaddch(panewin, LINES-5, 0, LEFT_JOIN); mvwaddch(panewin, LINES-5, COLS-2-BARWIDTH, RIGHT_JOIN); mvwaddch(panewin, 2, CENTER, TOP_JOIN); wattron (panewin, A_BOLD); mvwaddch(panewin, LINES-3, CENTER, BOTTOM_JOIN); mvwaddch(panewin, LINES-5, CENTER, MIDDLE_CROSS); wattroff(panewin, A_BOLD); } /* Нарисовать нечто при входе в панель. Здесь изменяется * заголовок окна: он становится равным имени каталога, * просматриваемого в панели */ void t_enter(Table *tbl){ WinBorder(tbl->win, tbl->bg_attrib, tbl->sel_attrib, CWD, BAR_VER|BAR_HOR, NO); t_restore_corners(); } /* Стереть подсветку при выходе из панели */ void t_leave(Table *tbl){ TblDrawItem( tbl, tbl->current, NO, YES ); } /* Рисует недостающую часть рамки, которая не изменяется впоследствии */ void t_border_common(){ WinBorder(panewin, A_tbl->bg_attrib, A_tbl->sel_attrib, A_dir->name, BAR_VER|BAR_HOR, NO); wattron (panewin, A_BOLD); whorline(panewin, LINES-3, 1, COLS-1-BARWIDTH-1); whorline(panewin, LINES-5, 1, COLS-1-BARWIDTH-1); wverline(panewin, CENTER, A_tbl->top, A_tbl->top + A_tbl->height+2); wattroff(panewin, A_BOLD); t_restore_corners(); } /* Функция, изображающая недостающие части панели при входе в нее */ int t_show(Table *tbl){ #ifdef FILF showMode(A_tbl, A_STANDOUT); showMode(B_tbl, A_STANDOUT); #endif return 1; } void t_scrollbar(Table *tbl, int whichbar, int n, int among){ WinScrollBar(tbl->win, BAR_VER|BAR_HOR, n, among, "Yes", tbl->bg_attrib); #ifdef FILF showMode(tbl, A_REVERSE); #endif } /* Особая обработка клавиш при выборе в таблице */ int t_hit[] = { '\t', KEY_F(1), KEY_F(2), KEY_F(3), KEY_F(4), KEY_F(8), ' ', '+', '-', ctrl('R'), ctrl('L'), ctrl('F'), -1 }; Info t_info[] = { { "TAB Перейти в другую панель", 0}, { "F1 Выдать подсказку", 0}, { "F2 Ввести команду", 0}, { "F3 Перейти в родительский каталог", 0}, { "F4 Перейти в каталог по имени", 0}, { "F8 Удалить помеченные файлы", 0}, { "ПРОБЕЛ Редактировать коды доступа", 0}, { "+ Пометить файлы", 0}, { "- Снять пометки", 0}, { "ctrl/R Перечитать каталог", 0}, { "ctrl/L Выдать размер файлов в каталоге",0}, { "ctrl/F Поиск файла", 0}, { NULL, 0} }; int t_help(){ static Menu mth; int c = 0; if( mth.items == NULL ){ mth.items = t_info; mth.title = "Команды в панели"; mth.top = 3; mth.left = COLS/6; mth.bg_attrib = A_STANDOUT; mth.sel_attrib = A_REVERSE; MnuInit (&mth); mth.hotkeys = t_hit; } c = MnuUsualSelect(&mth, 0); /* Спрятать меню, не уничтожая его. Уничтожение выглядело бы так: * mth.hotkeys = NULL; (т.к. они не выделялись malloc()-ом) * MnuDeinit(&mth); */ MnuHide(&mth); if( M_REFUSED(&mth)) return 0; /* ничего не делать */ return t_hit[c]; /* клавиша, соответствующая выбранной строке */ } int t_handler (Table *tbl, int c, HandlerReply *reply){ int i, cnt=0; extern int unlink(), rmdir(); char *answer; FileWidget *wd = TblFW (tbl); switch(c){ case '\t': /* перейти в соседнюю панель */ ExchangePanes(); *reply = HANDLER_OUT; return LEAVE_KEY; /* покинуть эту панель */ case KEY_F(1): *reply = HANDLER_NEWCHAR; return t_help(); case KEY_F(2): (void) Edit(tbl->win, T_ITEMF(tbl, tbl->current, 0), RUNCMD); break; case KEY_F(3): cd(".." , wd, CWD); break; case KEY_F(4): if(answer = help("Введи имя каталога, в который надо перейти",CHDIR)) cd(answer , wd, CWD); break; case ctrl('R'): break; case KEY_F(8): for(i=0; i < tbl->nitems; i++) if(T_TST(tbl, i, M_LABEL)){ int code; cnt++; if((code = (T_TST(tbl, i, I_DIR) ? rmdir : unlink) (T_ITEMF(tbl, i,0))) < 0) T_SET(tbl, i, M_HATCH); } if(cnt==0) help("Нет помеченных файлов", NORUN); break; case '+': if(answer = help("Шаблон для пометки", TAG)) TblTagAll(tbl, answer, T_LABEL); break; case '-': if(answer = help("Шаблон для снятия пометок", TAG)) TblUntagAll(tbl, answer, T_LABEL); break; case ctrl('L'): /* команда "disk usage" */ diskUsage(); break; case ctrl('F'): /* поиск файла */ findFile(wd); break; case ' ': /* редактирование кодов доступа */ editAccessModes(wd); break; } *reply = HANDLER_OUT; return REPEAT_KEY; /* вернуться в эту же панель */ } /* Выбор в одной из панелей. */ int SelectPane(FileWidget *wd){ Table *tbl = & wd->t; DirContents *d = & wd->d; int sel, retcode = 0; RaiseWin( tbl->win ); /* войти в указанный каталог, поправить CWD */ if(mychdir( d->name ) < 0) checkBothPanes(); /* t_enter( tbl ); /* войти в указанную панель, поправить рамку */ for(;;){ /* Проверить, не устарело ли содержимое таблиц */ checkBothPanes(); if((sel = TblUsualSelect( tbl )) == TOTAL_NOSEL ){ current_menu = SEL_PULL; goto out; } if( T_REFUSED(tbl)) break; /* нажат ESC */ if( tbl->key == LEAVE_KEY ){ retcode=1; break; } strcpy(SELECTION, T_ITEMF(tbl, sel, 0)); if( tbl->key == REPEAT_KEY ) continue; if(T_TST(tbl, sel, I_DIR)){ /* это каталог */ /* попытаться перейти в этот каталог */ cd(SELECTION, wd, CWD); } else if(T_TST(tbl, sel, I_EXE)){ /* выполняемый файл */ (void) Edit(tbl->win, SELECTION, RUNCMD); } else { editAccessModes(wd); /* На самом деле надо производить подбор команды по * типу файла (набор соответствий должен программироваться * вами в специальном файле, считываемом при запуске коммандера). * runCommand( classify(SELECTION)); * где классификация в простейшем случае - по имени и суффиксу, * а в более развитом - еще и по кодам доступа (включая тип файла) * и по первой строке файла (или "магическому числу"). */ } } /* end for */ t_leave( tbl ); out: if( !retcode ) current_menu = SEL_PULL; /* выход по ESC */ return retcode; } /*-----------------------------------------------------------------* * Горизонтальное командное меню (вызывается по ESC). * *-----------------------------------------------------------------*/ PullInfo pm_items [] = { /* подсказка */ {{ " \\Left ", 0 }, NULL, "Left pane" }, /* 0 */ {{ " \\Commands ", 0 }, &mwrk, "Do some commands"}, /* 1 */ {{ " \\Tools ", PM_NOSEL }, NULL, "" }, /* 2 */ {{ " \\Sorttype ", 0 }, &msort, "Change sort type"}, /* 3 */ {{ " \\Right ", 0 }, NULL, "Right pane" }, /* 4 */ {{ NULL, 0 }, NULL, NULL } }; void p_help(PullMenu *p, int n, int among){ Message( PM_NOTE(p, n)); } /* Выбор в меню-строке */ void SelectPullMenu(){ int c, sel; Menu *m; for(;current_menu == SEL_PULL;){ c = PullUsualSelect(&pull); sel = pull.current; if( PM_REFUSED(&pull)){ current_menu = previous_menu; return;} switch(sel){ case 0: current_menu = SEL_PANE1; return; case 1: SelectWorkingMenu(c); return; case 2: return; /* не бывает */ case 3: SelectSortType(c); return; case 4: current_menu = SEL_PANE2; return; } } } /*-----------------------------------------------------------------* * Инициализация и завершение. * *-----------------------------------------------------------------*/ void die(int sig){ echo(); nocbreak(); mvcur(-1,-1,LINES-1,0); refresh(); endwin (); putchar('\n'); if(sig) printf("Signal %d\n", sig); if(sig == SIGSEGV) abort(); else exit(sig); } void main (void) { setlocale(LC_ALL, ""); /* получить информацию о языке диагностик */ initscr (); /* включить curses */ signal(SIGINT, die); /* по сигналу вызывать die(); */ signal(SIGBUS, die); /* по нарушению защиты памяти */ signal(SIGSEGV,die); refresh(); /* обновить экран: это очистит его */ noecho(); cbreak(); /* выключить эхо, включить прозрачный ввод */ /* Проинициализировать истории */ HistInit(&hcwd, 20); hcwd. mnu.title = "История пути"; HistInit(&hedit, 20); hedit.mnu.title = "История команд"; HistInit(&hpat, 8); hpat. mnu.title = "Шаблоны имен"; /* Разметить меню сортировки */ msort.items = sort_info; msort.title = "Вид сортировки каталога"; msort.top = 1; msort.left = 2; msort.showMe = sort_show; msort.bg_attrib = A_NORMAL; msort.sel_attrib = A_STANDOUT; /* MnuInit (&msort); инициализируется в pull-menu */ /* Разметить рабочее меню */ mwrk.items = mwrk_info; mwrk.title = "Главное меню"; mwrk.top = 1; mwrk.left = COLS/3; mwrk.handler = NULL; mwrk.hitkeys = NULL; mwrk.bg_attrib = A_STANDOUT; mwrk.sel_attrib = A_REVERSE; mwrk.scrollBar = m_help; #ifdef __GNUC__ mwrk_init(); #endif /* MnuInit (&mwrk); инициализируется в pull-menu */ /* Разметить левую и правую панели */ tpane1.t.width = CENTER - 1; tpane2.t.width = COLS - tpane1.t.width - 2 - (2 + BARWIDTH); tpane1.t.height = tpane2.t.height = (LINES - 8); tpane1.t.win = tpane2.t.win = panewin = stdscr; tpane1.t.left = 1; tpane2.t.left = CENTER+1; tpane1.t.top = tpane2.t.top = 3; tpane1.t.bg_attrib = tpane2.t.bg_attrib = A_NORMAL; tpane1.t.sel_attrib = tpane2.t.sel_attrib = A_STANDOUT; tpane1.t.scrollBar = tpane2.t.scrollBar = t_scrollbar; tpane1.t.hitkeys = tpane2.t.hitkeys = t_hit; tpane1.t.handler = tpane2.t.handler = t_handler; tpane1.t.showMe = tpane2.t.showMe = t_show; tpane1.t.hideMe = tpane2.t.hideMe = NULL; /* Разметить имена для файловых объектов */ tpane1.d.name = strdup("Текущий каталог"); tpane2.d.name = strdup("Корневой каталог"); /* Изобразить рамки (но пока не проявлять их) * Это надо сделать до первого cd(), т.к. иначе при неудаче будет выдано * сообщение, которое проявит НЕЗАВЕРШЕННУЮ картинку */ t_border_common(); t_restore_corners(); /* Доразметить левую панель */ mychdir("."); /* узнать полное имя текущего каталога в CWD[] */ /* прочитать содержимое каталога CWD в tpane1.d */ cd( CWD , &tpane1, CWD); tpane1.t.fmt = "directory"; InitTblFromDir(&tpane1, NO, NULL); /* Доразметить правую панель */ tpane2.t.fmt = NULL; /* прочитать содержимое каталога "/" в tpane2.d */ cd( "/", &tpane2, CWD); /* теперь стоим в корне */ /* Вернуться в рабочий каталог */ cd( tpane1.d.name, &tpane1, CWD); /* Нарисовать обе панели */ TblDraw(A_tbl); TblDraw(B_tbl); /* Разметить pulldown меню */ pull.bg_attrib = A_REVERSE; pull.sel_attrib = A_NORMAL; pull.items = pm_items; pull.scrollBar = p_help; PullInit(&pull); /* Основной цикл */ for(done=NO, current_menu=SEL_PANE1, A_pane= &tpane1, B_pane= &tpane2; done == NO; ){ Message(""); if(SEL_PANE) previous_menu = current_menu; switch(current_menu){ case SEL_WRK : SelectWorkingMenu(NOSELECTED); break; case SEL_PULL: SelectPullMenu(); break; case SEL_PANE1: if( SelectPane(&tpane1) < 0) M_SET(&mwrk, 0, I_NOSEL); break; case SEL_PANE2: if( SelectPane(&tpane2) < 0) M_SET(&mwrk, 0, I_NOSEL); break; } } die(0); /* Завершить работу */ } /* Пример 24 */ /* Пример коммуникации процессов при помощи программных каналов * (трубы, pipes). * Данная программа превращается в две программы, * соединенные трубами в таком порядке: * * stdout stdin * /------------ PIP1 -----------> cmd2 * cmd1 <----------PIP2---------------/ * stdin stdout */ /* файл LOOP_strt.c */ #include <stdio.h> #define eq(s1,s2) ( strcmp(s1,s2) == 0 ) /* истина, если строки равны */ #define SEP "---" /* разделитель команд при наборе */ main( c, v ) char **v; { char **p, **q; int pid; int PIP1[2]; /* труба cmd1-->cmd2 */ int PIP2[2]; /* труба cmd2-->cmd1 */ if( c==1 ){ printf( "Call: strt cmd1... %s cmd2...\n", SEP ); exit(1); } /* разбор аргументов */ v++; /* в p - аргументы первой команды */ p = v; while( *v && !eq( *v, SEP )) v++; *v = NULL; v++; /* в q - аргументы второй команды */ q = v; pipe( PIP1 ); /* создаем две трубы */ pipe( PIP2 ); /* PIP[0] - открыт на чтение, PIP[1] - на запись */ if( pid = fork()){ /* развилка: порождаем процесс */ /* ПОРОЖДЕННЫЙ ПРОЦЕСС */ fprintf( stderr, "сын=%s pid=%d\n", p[0], getpid()); /* перенаправляем stdout нового процесса в PIP1 */ dup2( PIP1[1], 1 ); close( PIP1[1] ); /* канал чтения мы не будем использовать */ close( PIP1[0] ); /* перенаправляем stdin из PIP2 */ dup2( PIP2[0], 0 ); close( PIP2[0] ); /* канал записи мы не будем использовать */ close( PIP2[1] ); /* начинаем выполнять программу, содержащуюся в * файле p[0] с аргументами p (т.е. cmd1) */ execvp( p[0], p ); /* возврата из сисвызова exec не бывает */ }else{ /* ПРОЦЕСС-РОДИТЕЛЬ */ fprintf( stderr, "отец=%s pid=%d\n", q[0], getpid()); /* перенаправляем stdout в PIP2 */ dup2( PIP2[1], 1 ); close( PIP2[1] ); close( PIP2[0] ); /* перенаправляем stdin из PIP1 */ dup2( PIP1[0], 0 ); close( PIP1[0] ); close( PIP1[1] ); /* запускаем cmd2 */ execvp( q[0], q ); } } /* Ниже приводятся тексты двух программ, которые можно запустить * как тест. Сервер компилируется в программу cmd2, * клиент - в программу cmd1. Если запускающая программа * скомпилирована в strt, то наберите команду * strt cmd1 --- cmd2 * либо strt cmd2 --- cmd1 */ /* файл LOOP_p.c --------------------------------------------- * Процесс-клиент (cmd1) */ #include <stdio.h> int trace = 1; /* вести трассировку своих действий */ main(c , v) char **v; { FILE *fp; int pid; char buf[128]; fprintf( stderr, "P: process pid=%d\n", getpid()); fp = fopen( "LOOP_p.c", "r" ); /* открываем файл с текстом этой команды */ /* читаем его построчно */ while( fgets( buf, sizeof buf, fp ) != NULL ){ if( trace ) fprintf( stderr, "P посылает: %s", buf ); /* посылаем его в стандартный вывод: трубу PIP1 */ printf( "%s", buf ); fflush( stdout ); /* ожидать ответа из трубы PIP2 */ fgets( buf, sizeof buf, stdin ); if( trace ) fprintf( stderr, "P получил: %s", buf ); } fclose( stdout ); /* отключиться от трубы PIP1. Если этого не сделать, сервер * не прочитает из нее EOF */ while((pid = wait(NULL)) > 0 ) fprintf( stderr, "P: %d умер\n", pid ); } /* файл LOOP_q.c ------------------------------------------------ * процесс-сервер (cmd2) */ #include <stdio.h> int trace = 1; main(c , v) char **v; { char buf[128]; int pid; fprintf( stderr, "Q: process pid=%d\n", getpid()); /* читать поступающие из трубы PIP1 строки */ while( fgets( buf, sizeof(buf), stdin ) != NULL ){ /* напечатать полученное сообщение */ if( trace ) fprintf( stderr, "Q прочел: %s", buf ); if( trace ) fprintf( stderr, "Q отвечает: OK=%s", buf ); /* ответить в трубу PIP2 */ printf( "OK=%s", buf ); fflush( stdout ); } fclose( stdout ); /* отключиться от трубы PIP2 */ while((pid = wait(NULL)) > 0 ) fprintf( stderr, "Q: %d умер\n", pid ); } /* Пример 25 */ /* Пример использования именованных "труб" (pipes) FIFO-файлов * для коммуникации независимых процессов * (FIFO - first in, first out : первым пришел - первым ушел). * По мотивам книги М.Дансмура и Г.Дейвиса. */ /* файл P_packet.h --------------------------------------------*/ #include <sys/types.h> #include <sys/stat.h> /* S_IFIFO */ /* структура пакета-запроса */ struct packet { int pk_pid; /* идентификатор процесса-отправителя */ int pk_blk; /* номер блока, который надо прочитать */ int pk_code; /* код запроса */ }; /* request codes (коды запросов) */ #define RQ_READ 0 /* запрос на чтение */ #define CONNECT 1 /* запрос на соединение */ #define SENDPID 2 /* ответ на запрос соединения */ #define DISCONNECT 3 /* разрыв связи */ #define BYE 4 /* завершить сервер */ /* имена FIFO-каналов связи */ #define DNAME "datapipe" #define CNAME "ctrlpipe" /* размер блока информации */ #define PBUFSIZE 512 /* P_client.c --------------------------------------------------------- */ /* * Процесс-клиент, посылающий запросы к серверу. */ #include <stdio.h> #include <signal.h> #include <fcntl.h> #include "P_packet.h" int datapipe, ctrlpipe; int got_sig; int mypid; /* идентификатор процесса-клиента */ int spid; /* идентификатор процесса-сервера */ /* waiting for signal */ #define WAITSIG while( !got_sig ) void handler(nsig){ signal( SIGUSR1, handler ); got_sig ++; } void init(){ extern void die(); /* Ожидать создания каналов связи */ while( (datapipe = open( DNAME, O_RDONLY | O_NDELAY )) < 0 ); while( (ctrlpipe = open( CNAME, O_WRONLY | O_NDELAY )) < 0 ); mypid = getpid(); /* my process identifier */ printf( "Client pid=%d started\n", mypid ); signal( SIGINT, die); signal( SIGQUIT, die); signal( SIGTERM, die); handler(0); } int canRun = 1; void die(nsig){ canRun = 0; } /* подключиться к серверу, запросив его pid */ connect(){ struct packet pk; pk.pk_pid = mypid; pk.pk_code = CONNECT; pk.pk_blk = (-1); got_sig = 0; write( ctrlpipe, &pk, sizeof pk ); /* послать запрос */ /* ожидать сигнала-"толчка" */ WAITSIG; /* прочитать ответ из канала данных */ read( datapipe, &pk, sizeof pk ); /* послать сигнал-подтверждение */ kill( pk.pk_pid, SIGUSR1 ); return pk.pk_pid; } void disconnect(){ struct packet pk; pk.pk_pid = mypid; pk.pk_code = DISCONNECT; pk.pk_blk = (-1); got_sig = 0; write( ctrlpipe, &pk, sizeof pk ); /* send request */ /* wait for reply */ WAITSIG; /* receive reply */ read( datapipe, &pk, sizeof pk ); /* confirm */ kill( pk.pk_pid, SIGUSR1 ); printf( "Disconnected.\n" ); } request( ptr, blk, spid ) char *ptr; int blk; int spid; { struct packet pk; pk.pk_pid = mypid; pk.pk_blk = blk; pk.pk_code = RQ_READ; got_sig = 0; write( ctrlpipe, &pk, sizeof pk ); WAITSIG; read( datapipe, ptr, PBUFSIZE ); kill( spid, SIGUSR1 ); } bye(){ struct packet pk; pk.pk_pid = mypid; pk.pk_code = BYE; pk.pk_blk = (-1); got_sig = 0; write( ctrlpipe, &pk, sizeof pk ); /* send request */ exit(0); } /* client [номер_блока] */ main(argc, argv) char *argv[]; { int blk; char buffer[ PBUFSIZE ]; setbuf( stdout, NULL ); /* make unbuffered */ blk = (argv[1] ? atoi( argv[1] ) : 0); init(); spid = connect(); printf( "Client pid=%d connected to server pid=%d\n", mypid, spid ); /* запрос блока номер -33 соответствует запросу "завершить * работу сервера" */ if( blk == -33 ) bye(); /* в цикле посылать запросы на чтение блока blk */ while( canRun ){ request( buffer, blk, spid ); printf( "\nBEG-------------------------------------\n" ); fwrite( buffer, PBUFSIZE, 1, stdout ); printf( "\nEND-------------------------------------\n" ); } disconnect(); /* отключиться от сервера */ exit(0); } /* P_server.c ---------------------------------------------------------*/ /* * Процесс-сервер, принимающий запросы и выполняющий их. */ #include <stdio.h> #include <signal.h> #include <fcntl.h> #include "P_packet.h" int datapipe, ctrlpipe, datafile, got_sig; char *dataname = "/etc/passwd"; /* waiting for signal */ #define WAITSIG while( !got_sig ) void handler(nsig){ signal( SIGUSR1, handler ); /* reset trap */ got_sig++; } /* завершение работы сервера: уничтожить каналы связи */ void die(nsig){ unlink( CNAME ); unlink( DNAME ); exit(0); /* Если эти файлы были открыты клиентами, * то клиенты не умрут, хотя имена файлов и будут удалены! */ } main(){ struct packet pk; struct packet sendpk; /* сделать стандартный вывод небуферизованным каналом */ setbuf( stdout, NULL ); /* make unbuffered */ /* создать каналы связи */ mknod( DNAME, S_IFIFO | 0666, 0 ); /* create FIFO */ mknod( CNAME, S_IFIFO | 0666, 0 ); /* create FIFO */ /* по этим сигналам будет вызываться функция die() */ signal( SIGINT, die ); signal( SIGQUIT, die ); signal( SIGTERM, die ); /* Открыть управляющий канал связи. O_NDELAY означает, * что файл открывается для "чтения без ожидания", * т.е. если канал пуст (нет заявок), то системный вызов * read() не будет "спать", дожидаясь появления информации, * а просто вернет 0 (прочитано 0 байт). * Этот флаг применим также к чтению с терминала. */ ctrlpipe = open( CNAME, O_RDONLY | O_NDELAY ); if( ctrlpipe < 0 ){ printf( "Can't open %s\n", CNAME ); die(0); } datafile = open( dataname, O_RDONLY ); if( datafile < 0 ){ printf( "Can't open %s\n", dataname ); die(0); } /* заранее формируем пакет для ответов */ sendpk.pk_code = SENDPID; sendpk.pk_pid = getpid(); /* server's pid */ sendpk.pk_blk = (-1); printf( "Server pid=%d\n", getpid()); handler(0); for(;;){ int n; static long i = 0L; /* active spin loop */ printf( "%20ld\r", i++ ); /* опрашивать канал насчет поступления запросов */ while((n = read( ctrlpipe, &pk, sizeof(pk))) > 0 ){ putchar( '\n' ); if( n != sizeof pk ){ printf( "Wrong packet size\n" ); continue; } /* обработать прочитанный запрос */ process( &pk, &sendpk ); } } die(0); } process( pkp, spkp ) struct packet *pkp, *spkp; { char pbuf[ PBUFSIZE ]; /* Запись в FIFO-файл будет произведена только если * он уже открыт для чтения */ datapipe = open( DNAME, O_WRONLY | O_NDELAY ); printf( "REQUEST TYPE_%d from pid=%d blk=%d\n", pkp->pk_code, pkp->pk_pid, pkp->pk_blk ); switch( pkp -> pk_code ){ case CONNECT: /* ответить своим идентификатором процесса */ write( datapipe, spkp, sizeof( struct packet )); break; case RQ_READ: /* ответить блоком информации из файла */ /* read block # pk_blk */ lseek( datafile, pkp -> pk_blk * (long)PBUFSIZE, 0 ); read( datafile, pbuf, PBUFSIZE ); write( datapipe, pbuf, PBUFSIZE ); break; case DISCONNECT: /* подтвердить отключение */ printf( "Client pid=%d finished\n", pkp -> pk_pid ); write ( datapipe, spkp, sizeof( struct packet )); break; case BYE: /* завершиться */ printf( "Server terminated.\n" ); kill( pkp-> pk_pid, SIGKILL ); die(0); default: printf( "Unknown packet type %d\n", pkp -> pk_code ); break; } close( datapipe ); /* "подтолкнуть" отправителя сигналом */ got_sig = 0; kill( pkp -> pk_pid , SIGUSR1 ); printf( "Waiting for reply... " ); /* ждать сигнала-подтверждения от клиента */ WAITSIG; printf( "server continued\n" ); } /* Пример 26 */ /* Общение процессов при помощи общей памяти и семафоров. * Вызов: shms & * shmc a & shmc b & shmc c & */ /* --------------------------- файл shm.h ----------------------- */ #include <stdio.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <sys/sem.h> #include <signal.h> #include <errno.h> extern errno; /* Системный код ошибки */ struct connect { /* Структура почтового ящика */ int pid; int msgnum; int max; char message[128]; /* текст сообщения */ }; #define NSEMS 3 /* число семафоров */ /* Имена семафоров */ #define EMPTY 0 /* 1 - ящик пуст; 0 - содержит письмо */ #define NOTEMPTY 1 /* негатив для EMPTY */ #define ACCESS 2 /* 1 - ящик доступен (закрыт); * 0 - ящик уже открыт кем-то еще */ /* Значения семафоров */ #define YES 1 #define NO 0 /* Операции */ #define OPEN 1 #define CLOSE (-1) #define TEST_NO 0 #ifdef COMMENT Алгоритм одновременного изменения семафоров: semop Дано: аргумент: число семафоров : nsems аргумент: величины изменения : sem_op[i] в ядре: текущие значения семафоров группы sem_id: sem[i] Алгоритм: again: Сохранить значения всех семафоров (для отмены изменений); for(i=0; i<nsems; i++) /* OPEN */ if( sem_op[i] > 0 ){ sem[i] += sem_op[i]; разбудитьЖдущихСобытие( "sem[i]++" ); /* CLOSE */ }else if( sem_op[i] < 0 ){ if((newsm = sem[i] + sem_op[i]) >= 0 ){ sem[i] = newsm; if( sem[i] == 0 ) разбудитьЖдущихСобытие( "sem[i]==0" ); }else{ восстановитьВсеСемафоры; ждатьСобытие( "sem[i]++" ); goto again; } /* TEST0 */ }else{ /* sem_op[i] == 0 */ if( sem[i] != 0 ){ восстановитьВсеСемафоры; ждатьСобытие( "sem[i]==0" ); goto again; } } Алгоритм синхронизации в нашей схеме КЛИЕНТ-СЕРВЕР: |----------------------------------------------------------------| |семафоры: EMPTY ACCESS | |----------------------------------------------------------------| |начальное значение: YES YES | |----------------------------------------------------------------| СЕРВЕР |================================================================| |loop: | |----------------------------------------------------------------| |ждать: NO YES | |сделать: NO(test0) NO(close) | |----------------------------------------------------------------| | прочесть почту; | |----------------------------------------------------------------| |из: NO NO | |сделать: YES(open) YES(open) | |----------------------------------------------------------------| | goto loop; | |================================================================| КЛИЕНТ |================================================================| |loop: | |----------------------------------------------------------------| |ждать: YES YES | |сделать: YES(test!=0) NO(close) | |----------------------------------------------------------------| | записать почту; | |----------------------------------------------------------------| |из: YES NO | |сделать: NO(close) YES(open) | |----------------------------------------------------------------| | goto loop; | |================================================================| К сожалению, операции test!=0 не существует - приходится вводить дополнительный семафор NOTEMPTY, негативный для EMPTY: |----------------------------------------------------------------| |семафоры: EMPTY NOTEMPTY ACCESS | |----------------------------------------------------------------| |начальное значение: YES NO YES | |----------------------------------------------------------------| СЕРВЕР |================================================================| |loop: | |----------------------------------------------------------------| |ждать: NO - YES | |сделать: NO(test0) - NO(close) | |----------------------------------------------------------------| | прочесть почту; | |----------------------------------------------------------------| |из: NO YES NO | |сделать: YES(open) NO(close) YES(open) | |----------------------------------------------------------------| | goto loop; | |================================================================| КЛИЕНТ |================================================================| |loop: | |----------------------------------------------------------------| |ждать: - NO YES | |сделать: - NO(test0) NO(close) | |----------------------------------------------------------------| | записать почту; | |----------------------------------------------------------------| |из: YES NO NO | |сделать: NO(close) YES(open) YES(open) | |----------------------------------------------------------------| | goto loop; | |================================================================| #endif /*COMMENT*/ /* Общая часть сервера и клиента ------------------------------- */ key_t key = 1917; /* Уникальный ключ для доступа */ int shm_id; /* Дескриптор для доступа к общей памяти */ int sem_id; /* Дескриптор для доступа к семафорам */ char name[40]; /* имя программы */ char far *addr; struct connect far *caddr; struct sembuf ops[NSEMS]; /* EMPTY NOTEMPTY ACCESS */ short values[NSEMS] = { YES, NO, YES }; void semtell(msg, name) char *msg, *name; { int i; semctl(sem_id, NSEMS, GETALL, values); printf( "%s %-10s: значения семафоров:", name, msg); for(i=0; i < NSEMS; i++) printf( " %d", values[i]); putchar('\n'); } void inisem(){ register i; for(i=0; i < NSEMS; i++ ) ops[i].sem_flg = 0; } /* --------------------------- файл shms.c ----------------------- */ /* Shared memory server */ #include "shm.h" int npack; /* номер сообщения */ void cleanup(sig){ /* Уничтожить сегмент общей памяти (это нужно делать явно) */ shmctl( shm_id, IPC_RMID, NULL ); /* Уничтожить семафоры */ semctl( sem_id, NSEMS, IPC_RMID, NULL ); if( npack ) printf( "\t** Всего было %d сообщений **\n", npack+1); exit(0); } void main(){ register i; int pid = getpid(); FILE *fout; sprintf( name, "Server-%03d", pid ); for( i = 1; i <= SIGTERM; i++ ) signal( i, cleanup ); /* Создать разделяемый сегмент */ if((shm_id = shmget( key, sizeof(struct connect), 0644 | IPC_CREAT )) < 0 ){ perror( "shmget" ) ; exit(1); } /* Подключить общий сегмент к произвольному адресу */ if((addr = (char far *) shmat( shm_id, NULL, 0 )) == NULL ){ perror( "shmat" ); cleanup(); } caddr = (struct connect far *) addr; /* Создать группу из NSEMS семафоров */ if((sem_id = semget( key, NSEMS, 0644 |IPC_CREAT |IPC_EXCL)) < 0){ if(errno == EEXIST){ printf( "Сервер уже запущен\n");exit(2); } else{ perror( "semget" ); cleanup(); } } /* Загрузить начальные значения семафоров */ semctl( sem_id, NSEMS, SETALL, values ); setbuf(stdout, NULL); inisem(); printf( "Server is up now. Читай файл MESSAGES.\n"); fout = fopen( "MESSAGES", "w"); for(;;npack++){ printf( "%s: ждет почты\n", name ); semtell("Вход", name); ops[0].sem_num = EMPTY; ops[0].sem_op = TEST_NO; ops[1].sem_num = ACCESS; ops[1].sem_op = CLOSE; semop( sem_id, ops, 2 /* сразу два семафора */); printf( "%s: GOT-%02d/%02d от %d \"%s\"\n", name, caddr->msgnum, caddr->max, caddr->pid, caddr->message); fprintf( fout, "#%03d %02d/%02d от %d \"%s\"\n", npack, caddr->msgnum, caddr->max, caddr->pid, caddr->message); if( ! strcmp(caddr->message, "-exit" )){ printf( "%s: завершает работу.\n", name ); cleanup(); } semtell("Выход", name); ops[0].sem_num = EMPTY ; ops[0].sem_op = OPEN; ops[1].sem_num = NOTEMPTY; ops[1].sem_op = CLOSE; ops[2].sem_num = ACCESS ; ops[2].sem_op = OPEN; semop( sem_id, ops, 3 /* сразу три семафора */); } /*NOTREACHED*/ } /* --------------------------- файл shmc.c ----------------------- */ /* Shared memory client */ #include "shm.h" void ignsigs(sig){ register i; for( i = 1; i <= SIGTERM; i++ ) signal( i, ignsigs ); printf( "Клиент игнорирует сигналы,\n\ чтобы не оставлять закрытых семафоров в случае своей смерти.\n" ); } void main(argc, argv) char **argv; { int pid = getpid(); int i, ntimes = 60; if( argc < 2 ){ fprintf( stderr, "Вызов: %s сообщение [числоПовторов]\n", argv[0] ); fprintf( stderr, "сообщение \"-exit\" завершает сервер\n"); fprintf( stderr, "сообщение \"-info\" выдает значения семафоров\n"); exit(1); } if( argc > 2 ) ntimes = atoi(argv[2]); sprintf( name, "Client-%03d", pid); ignsigs(); srand( pid ); /* Получить доступ к разделяемому сегменту */ if((shm_id = shmget( key, sizeof(struct connect), 0644)) < 0 ){ perror( "shmget" ); exit(2); } /* Подключить общий сегмент к произвольному адресу */ if((addr = (char far *) shmat( shm_id, NULL, 0 )) == NULL ){ perror( "shmat" ); exit(3); } caddr = (struct connect far *) addr; /* Получить доступ к семафорам */ if((sem_id = semget( key, NSEMS, 0644)) < 0 ){ perror( "semget" ); exit(4); } setbuf(stdout, NULL); inisem(); if( !strcmp(argv[1], "-info")){ semtell("Информация", name); exit(0); } for( i=0; i < ntimes; i++ ){ printf( "%s: ждет пустого ящика\n", name); semtell("Вход", name); ops[0].sem_num = NOTEMPTY; ops[0].sem_op = TEST_NO; ops[1].sem_num = ACCESS ; ops[1].sem_op = CLOSE; if( semop( sem_id, ops, 2 /* сразу два семафора */) < 0) goto err; caddr->pid = pid; caddr->msgnum = i; caddr->max = ntimes; strncpy( caddr->message, argv[1], sizeof(caddr->message) - 1); printf( "%s: PUT-%02d \"%s\"\n", name, i, argv[1]); semtell("Выход", name); ops[0].sem_num = EMPTY ; ops[0].sem_op = CLOSE; ops[1].sem_num = NOTEMPTY; ops[1].sem_op = OPEN; ops[2].sem_num = ACCESS ; ops[2].sem_op = OPEN; if( semop( sem_id, ops, 3 /* сразу три семафора */) < 0) goto err; if( rand()%2 ) sleep(2); /* пауза */ } shmdt( addr ); /* Отключиться от общего сегмента */ exit(0); err: perror("semop"); exit(5); } /* Пример 27 */ /* Коммуникация процессов при помощи псевдо-терминала. * Данная программа позволяет сохранять полный протокол работы * экранной программы в файл. * Не экранные программы данная версия НЕ трассирует, * поскольку сама работает в "прозрачном" режиме. * * Вариацией данной программы может служить использование * системного вызова select() вместо запуска нескольких процессов. * * Программа также иллюстрирует "дерево" из 5 процессов. * Данная версия написана для UNIX System V. * TRACE__ * \ \ master slave * |экран<======\(Reader)=======!~!<====(целевая ) * / <==\ | ! !====>(программа) * \ | !P! | * | | !T! | * . . . . | | !Y! (Slave)-->Управляет * клавиатура=|===|=>(Writer)=>!_! | \ семафором * | | | | \ * | #####starter################## \ * |...................................| * ftty */ #include <stdio.h> #include <sys/types.h> #include <sys/signal.h> #include <termio.h> #include <sys/stat.h> #include <fcntl.h> extern int exit (); extern char *ttyname (); extern FILE * fopen (); extern errno; #define SEMAPHORE "/tmp/+++" /* семафорный файл */ #define TRACE "./TRACE" /* файл с протоколом */ /* псевдотерминал связи */ /* master - это часть, которая ведет себя как ФАЙЛ и умеет * реагировать на некоторые специальные ioctl()-и */ #define PTY "/dev/ptyp0" /* master */ /* slave - это часть, которая ведет себя как драйвер терминалов */ #define TTYP "/dev/ttyp0" /* slave */ int ptyfd; FILE * ftrace = NULL; /* при прерывании завершить работу процесса "писателя" */ onintr () { closeVisual (); fprintf (stderr, "\rwriter finished\r\n"); exit (0); } /* завершение работы процесса-"читателя" */ bye () { if (ftrace) fclose (ftrace); fprintf (stderr, "\rreader finished\r\n"); exit (0); } int visual = 0; struct termio old, new; /* настроить режимы работы терминала на "прозрачный" режим */ initVisual () { ioctl (0, TCGETA, &old); new = old; new.c_iflag &= ~ICRNL; new.c_lflag &= ~(ECHO | ICANON); new.c_oflag &= ~(TAB3 | ONLCR); new.c_cc[VMIN] = 1; new.c_cc[VTIME] = 0; /* new.c_cc[VINTR] = ctrl('C'); */ new.c_cc[VQUIT] = 0; new.c_cc[VERASE] = 0; new.c_cc[VKILL] = 0; } /* включить прозрачный режим */ openVisual () { if (visual) return; visual = 1; ioctl (0, TCSETAW, &new); } /* выключить прозрачный режим */ closeVisual () { if (!visual) return; visual = 0; ioctl (0, TCSETAW, &old); } struct stat st; main (argc, argv) char **argv; { int r, /* pid процесса-"читателя" */ w; /* pid процесса-"писателя" */ if (argc == 1) { fprintf (stderr, "pty CMD ...\n"); exit (1); } initVisual (); if((ptyfd = open ( PTY , O_RDWR)) < 0){ fprintf(stderr, "Cannot open pty\n"); exit(2); } /* запустить процесс чтения с псевдодисплея */ r = startReader (); /* запустить процесс чтения с клавиатуры */ w = startWriter (); sleep (2); /* запустить протоколируемый процесс */ startSlave (argv + 1, r, w); /* дождаться окончания всех потомков */ while (wait (NULL) > 0); exit (0); } /* запуск протоколируемого процесса */ startSlave (argv, r, w) char **argv; { FILE * ftty; int pid; int tfd; char *tty = ttyname (1); /* полное имя нашего терминала */ if (!(pid = fork ())) { /* PTY SLAVE process */ ftty = fopen (tty, "w"); /* Для выдачи сообщений */ setpgrp (); /* образовать новую группу процессов ; * лишиться управляющего терминала */ /* закрыть стандартные ввод, вывод, вывод ошибок */ close (0); close (1); close (2); /* первый открытый терминал станет управляющим для процесса, * не имеющего управляющего терминала. * Открываем псевдотерминал (slave) в качестве стандартных * ввода, вывода и вывода ошибок */ open ( TTYP, O_RDWR); open ( TTYP, O_RDWR); tfd = open ( TTYP, O_RDWR); if (tfd < 0) { fprintf (ftty, "\rSlave: can't read/write pty\r\n"); kill(r, SIGKILL); kill(w, SIGKILL); exit (1); } /* запускаем целевую программу */ if (!(pid = fork ())) { fprintf (ftty, "\rCreating %s\r\n", SEMAPHORE); fflush (ftty); /* создаем семафорный файл */ close (creat (SEMAPHORE, 0644)); fprintf (ftty, "\rStart %s\r\n", argv[0]); fclose(ftty); /* заменить ответвившийся процесс программой, * указанной в аргументах */ execvp (argv[0], argv); exit (errno); } /* дожидаться окончания целевой программы */ while (wait (NULL) != pid); /* уничтожить семафор, что является признаком завершения * для процессов чтения и записи */ unlink (SEMAPHORE); fprintf (ftty, "\rDied.\r\n"); fflush