Страница:
};
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)))
#define is_alpha(c) (isalpha(c) || (c) == '_')
#define is_alnum(c) (isalnum(c) || (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');
}
7.69. Напишите программу, выделяющую n-ое поле из каждой строки файла. Поля разделя-
ются двоеточиями. Предусмотрите задание символа-разделителя из аргументов программы.
Используйте эту программу для выделения поля "домашний каталог" из файла /etc/passwd.
Для выделения очередного поля можно использовать следующую процедуру:
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 ("прототипы" функций).
f(x, y, s, v)
int x;
char *s;
struct elem *v;
{ ... }
преобразуется в
int f(int x, int y, char *s, struct elem *v)
{ ... }
(обратите внимание, что переменная y и сама функция f описаны по умолчанию как int).
А. Богатырев, 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); // верно.
Собственно под "прототипом" понимают предварительное описание функции в новом стиле -
где вместо тела {...} сразу после заголовка стоит точка с запятой.
long f(long x, long y); /* прототип */
...
long f(long x, long y){ return x+y; } /* реализация */
В прототипе имена аргументов можно опускать:
long f(long, long); /* прототип */
char *strchr(char *, char);
Это предварительное описание помещают где-нибудь в начале программы, до первого
вызова функции. В современном Си прототипы заменяют описания вида
extern long f();
о которых мы говорили раньше. Прототипы предоставляют программисту механизм для
автоматического контроля формата вызова функции. Так, если функция имеет прототип
double f( double );
и вызывается как
double x = f( 12 );
то компилятор автоматически превратит это в
double x = f( (double) 12 );
(поскольку существует приведение типа от int к double); если же написано
f( "привет" );
то компилятор сообщит об ошибке (так как нет преобразования типа (char *) в double).
Прототип принуждает компилятор проверять:
a) соответствие ТИПОВ фактических параметров (при вызове) типам формальных парамет-
ров (в прототипе);
b) соответствие КОЛИЧЕСТВА фактических и формальных параметров;
c) тип возвращаемого функцией значения.
Прототипы обычно помещают в include-файлы. Так в ANSI стандарте Си предусмотрен файл,
подключаемый
#include <stdlib.h>
А. Богатырев, 1992-95 - 345 - Си в UNIX
в котором определены прототипы функций из стандартной библиотеки языка Си. Черезвы-
чайно полезно писать эту директиву include, чтобы компилятор проверял, верно ли вы
вызываете стандартные функции.
Заметим, что если вы определили прототипы каких-то функций, но в своей программе
используете не все из этих функций, то функции, соответствующие "лишним" прототипам,
НЕ будут добавляться к вашей программе из библиотеки. Т.е. прототипы - это указание
компилятору; ни в какие машинные команды они не транслируются. То же самое касается
описаний внешних переменных и функций в виде
extern int x;
extern char *func();
Если вы не используете переменную или функцию с таким именем, то эти строки не имеют
никакого эффекта (как бы вообще отсутствуют).
7.72. Обратная задача: напишите преобразователь из нового стиля в старый.
int f( int x, char *y ){ ... }
переводить в
int f( x, y ) int x; char *y; { ... }
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
Терминал в UNIX с точки зрения программ - это файл. Он представляет собой два
устройства: при записи write() в этот файл осуществляется вывод на экран; при чтении
read()-ом из этого файла - читается информация с клавиатуры.
Современные терминалы в определенном смысле являются устройствами прямого дос-
тупа:
- информация может быть выведена в любое место экрана, а не только последовательно
строка за строкой.
- некоторые терминалы позволяют прочесть содержимое произвольной области экрана в
вашу программу.
Традиционные терминалы являются самостоятельными устройствами, общающимися с компью-
тером через линию связи. Протокол|- общения образует систему команд терминала и может
быть различен для терминалов разных моделей. Поэтому библиотека работы с традиционным
терминалом должна решать следующие проблемы:
- настройка на систему команд данного устройства, чтобы одна и та же программа
работала на разных типах терминалов.
- эмуляция недостающих в системе команд; максимальное использование предоставлен-
ных терминалом возможностей.
- мимнимизация передачи данных через линию связи (для ускорения работы).
- было бы полезно, чтобы библиотека предоставляла пользователю некоторые логичес-
кие абстракции, вроде ОКОН - прямоугольных областей на экране, ведущих себя
подобно маленьким терминалам.
В UNIX эти задачи решает стандартная библиотека curses (а только первую задачу -
более простая библиотека termcap). Для настройки на систему команд конкретного дисп-
лея эти библиотеки считывают описание системы команд, хранящееся в файле
/etc/termcap. Кроме них бывают и другие экранные библиотеки, а также существуют иные
способы работы с экраном (через видеопамять, см. ниже).
В задачах данного раздела вам придется пользоваться библиотекой curses. При ком-
пиляции программ эта библиотека подключается при помощи указания ключа -lcurses, как
в следующем примере:
cc progr.c -Ox -o progr -lcurses -lm
Здесь подключаются две библиотеки: /usr/lib/libcurses.a (работа с экраном) и
/usr/lib/libm.a (математические функции, вроде sin, fabs). Ключи для подключения
библиотек должны быть записаны в команде САМЫМИ ПОСЛЕДНИМИ. Заметим, что стандартная
библиотека языка Си (содержащая системные вызовы, библиотеку stdio (функции printf,
scanf, fread, fseek, ...), разные часто употребляемые функции (strlen, strcat, sleep,
malloc, rand, ...)) /lib/libc.a подключается автоматически и не требует указания
ключа -lc.
В начале своей программы вы должны написать директиву
#include <curses.h>
подключающую файл /usr/include/curses.h, в котором описаны форматы данных, используе-
мых библиотекой curses, некоторые предопределенные константы и.т.п. (это надо, чтобы
ваша программа пользовалась именно этими стандартными соглашениями). Посмотрите в
этот файл!
Когда вы пользуетесь curses-ом, вы НЕ должны пользоваться функциями стандартной
библиотеки stdio для непосредственного вывода на экран; так вы не должны пользоваться
____________________
|- Под протоколом в программировании подразумевают ряд соглашений двух сторон (сер-
вера и клиентов; двух машин в сети (кстати, термин для обозначения машины в сети -
"host" или "site")) о формате (правилах оформления) и смысле данных в передаваемых
друг другу сообщениях. Аналогия из жизни - человеческие речь и язык. Речь всех лю-
дей состоит из одних и тех же звуков и может быть записана одними и теми же буквами
(а данные - байтами). Но если два человека говорят на разных языках - т.е. по-
разному конструируют фразы и интерпретируют звуки - они не поймут друг друга!
А. Богатырев, 1992-95 - 349 - Си в UNIX
функциями printf, putchar. Это происходит потому, что curses хранит в памяти про-
цесса копию содержимого экрана, и если вы выводите что-либо на экран терминала обходя
функции библиотеки curses, то реальное содержимое экрана и позиция курсора на нем
перестают соответствовать хранимым в памяти, и библиотека curses начнет выводить неп-
равильное изображение.
ПРОГРАММА
| |
| CURSES---копия экрана
| printw,addch,move
| |
V V
библиотека STDIO --printf,putchar----> экран
Таким образом, 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 (фон) | foreground (цвет букв)
R - red (красный) G - green (зеленый) B - blue (синий)
blink - мерцание букв (не фона!)
intensity - повышенная яркость
А. Богатырев, 1992-95 - 350 - Си в UNIX
Координатная система на экране: верхний левый угол экрана имеет координаты (0,0), ось
X горизонтальна, ось Y вертикальна и направлена сверху вниз.
Цвет символа получается смешиванием 3х цветов: красного, зеленого и синего
(электронно-лучевая трубка дисплея имеет 3 электронные пушки, отвечающие этим цве-
там). Кроме того, допустимы более яркие цвета. 4 бита задают комбинацию 3х основных
цветов и повышенной яркости. Образуется 2**4=16 цветов:
I R G B номер цвета
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: MK_FP (это должен быть far или huge указатель!). В
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 */
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)))
#define is_alpha(c) (isalpha(c) || (c) == '_')
#define is_alnum(c) (isalnum(c) || (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');
}
7.69. Напишите программу, выделяющую n-ое поле из каждой строки файла. Поля разделя-
ются двоеточиями. Предусмотрите задание символа-разделителя из аргументов программы.
Используйте эту программу для выделения поля "домашний каталог" из файла /etc/passwd.
Для выделения очередного поля можно использовать следующую процедуру:
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 ("прототипы" функций).
f(x, y, s, v)
int x;
char *s;
struct elem *v;
{ ... }
преобразуется в
int f(int x, int y, char *s, struct elem *v)
{ ... }
(обратите внимание, что переменная y и сама функция f описаны по умолчанию как int).
А. Богатырев, 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); // верно.
Собственно под "прототипом" понимают предварительное описание функции в новом стиле -
где вместо тела {...} сразу после заголовка стоит точка с запятой.
long f(long x, long y); /* прототип */
...
long f(long x, long y){ return x+y; } /* реализация */
В прототипе имена аргументов можно опускать:
long f(long, long); /* прототип */
char *strchr(char *, char);
Это предварительное описание помещают где-нибудь в начале программы, до первого
вызова функции. В современном Си прототипы заменяют описания вида
extern long f();
о которых мы говорили раньше. Прототипы предоставляют программисту механизм для
автоматического контроля формата вызова функции. Так, если функция имеет прототип
double f( double );
и вызывается как
double x = f( 12 );
то компилятор автоматически превратит это в
double x = f( (double) 12 );
(поскольку существует приведение типа от int к double); если же написано
f( "привет" );
то компилятор сообщит об ошибке (так как нет преобразования типа (char *) в double).
Прототип принуждает компилятор проверять:
a) соответствие ТИПОВ фактических параметров (при вызове) типам формальных парамет-
ров (в прототипе);
b) соответствие КОЛИЧЕСТВА фактических и формальных параметров;
c) тип возвращаемого функцией значения.
Прототипы обычно помещают в include-файлы. Так в ANSI стандарте Си предусмотрен файл,
подключаемый
#include <stdlib.h>
А. Богатырев, 1992-95 - 345 - Си в UNIX
в котором определены прототипы функций из стандартной библиотеки языка Си. Черезвы-
чайно полезно писать эту директиву include, чтобы компилятор проверял, верно ли вы
вызываете стандартные функции.
Заметим, что если вы определили прототипы каких-то функций, но в своей программе
используете не все из этих функций, то функции, соответствующие "лишним" прототипам,
НЕ будут добавляться к вашей программе из библиотеки. Т.е. прототипы - это указание
компилятору; ни в какие машинные команды они не транслируются. То же самое касается
описаний внешних переменных и функций в виде
extern int x;
extern char *func();
Если вы не используете переменную или функцию с таким именем, то эти строки не имеют
никакого эффекта (как бы вообще отсутствуют).
7.72. Обратная задача: напишите преобразователь из нового стиля в старый.
int f( int x, char *y ){ ... }
переводить в
int f( x, y ) int x; char *y; { ... }
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
Терминал в UNIX с точки зрения программ - это файл. Он представляет собой два
устройства: при записи write() в этот файл осуществляется вывод на экран; при чтении
read()-ом из этого файла - читается информация с клавиатуры.
Современные терминалы в определенном смысле являются устройствами прямого дос-
тупа:
- информация может быть выведена в любое место экрана, а не только последовательно
строка за строкой.
- некоторые терминалы позволяют прочесть содержимое произвольной области экрана в
вашу программу.
Традиционные терминалы являются самостоятельными устройствами, общающимися с компью-
тером через линию связи. Протокол|- общения образует систему команд терминала и может
быть различен для терминалов разных моделей. Поэтому библиотека работы с традиционным
терминалом должна решать следующие проблемы:
- настройка на систему команд данного устройства, чтобы одна и та же программа
работала на разных типах терминалов.
- эмуляция недостающих в системе команд; максимальное использование предоставлен-
ных терминалом возможностей.
- мимнимизация передачи данных через линию связи (для ускорения работы).
- было бы полезно, чтобы библиотека предоставляла пользователю некоторые логичес-
кие абстракции, вроде ОКОН - прямоугольных областей на экране, ведущих себя
подобно маленьким терминалам.
В UNIX эти задачи решает стандартная библиотека curses (а только первую задачу -
более простая библиотека termcap). Для настройки на систему команд конкретного дисп-
лея эти библиотеки считывают описание системы команд, хранящееся в файле
/etc/termcap. Кроме них бывают и другие экранные библиотеки, а также существуют иные
способы работы с экраном (через видеопамять, см. ниже).
В задачах данного раздела вам придется пользоваться библиотекой curses. При ком-
пиляции программ эта библиотека подключается при помощи указания ключа -lcurses, как
в следующем примере:
cc progr.c -Ox -o progr -lcurses -lm
Здесь подключаются две библиотеки: /usr/lib/libcurses.a (работа с экраном) и
/usr/lib/libm.a (математические функции, вроде sin, fabs). Ключи для подключения
библиотек должны быть записаны в команде САМЫМИ ПОСЛЕДНИМИ. Заметим, что стандартная
библиотека языка Си (содержащая системные вызовы, библиотеку stdio (функции printf,
scanf, fread, fseek, ...), разные часто употребляемые функции (strlen, strcat, sleep,
malloc, rand, ...)) /lib/libc.a подключается автоматически и не требует указания
ключа -lc.
В начале своей программы вы должны написать директиву
#include <curses.h>
подключающую файл /usr/include/curses.h, в котором описаны форматы данных, используе-
мых библиотекой curses, некоторые предопределенные константы и.т.п. (это надо, чтобы
ваша программа пользовалась именно этими стандартными соглашениями). Посмотрите в
этот файл!
Когда вы пользуетесь curses-ом, вы НЕ должны пользоваться функциями стандартной
библиотеки stdio для непосредственного вывода на экран; так вы не должны пользоваться
____________________
|- Под протоколом в программировании подразумевают ряд соглашений двух сторон (сер-
вера и клиентов; двух машин в сети (кстати, термин для обозначения машины в сети -
"host" или "site")) о формате (правилах оформления) и смысле данных в передаваемых
друг другу сообщениях. Аналогия из жизни - человеческие речь и язык. Речь всех лю-
дей состоит из одних и тех же звуков и может быть записана одними и теми же буквами
(а данные - байтами). Но если два человека говорят на разных языках - т.е. по-
разному конструируют фразы и интерпретируют звуки - они не поймут друг друга!
А. Богатырев, 1992-95 - 349 - Си в UNIX
функциями printf, putchar. Это происходит потому, что curses хранит в памяти про-
цесса копию содержимого экрана, и если вы выводите что-либо на экран терминала обходя
функции библиотеки curses, то реальное содержимое экрана и позиция курсора на нем
перестают соответствовать хранимым в памяти, и библиотека curses начнет выводить неп-
равильное изображение.
ПРОГРАММА
| |
| CURSES---копия экрана
| printw,addch,move
| |
V V
библиотека STDIO --printf,putchar----> экран
Таким образом, 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 (фон) | foreground (цвет букв)
R - red (красный) G - green (зеленый) B - blue (синий)
blink - мерцание букв (не фона!)
intensity - повышенная яркость
А. Богатырев, 1992-95 - 350 - Си в UNIX
Координатная система на экране: верхний левый угол экрана имеет координаты (0,0), ось
X горизонтальна, ось Y вертикальна и направлена сверху вниз.
Цвет символа получается смешиванием 3х цветов: красного, зеленого и синего
(электронно-лучевая трубка дисплея имеет 3 электронные пушки, отвечающие этим цве-
там). Кроме того, допустимы более яркие цвета. 4 бита задают комбинацию 3х основных
цветов и повышенной яркости. Образуется 2**4=16 цветов:
I R G B номер цвета
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: MK_FP (это должен быть far или huge указатель!). В
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 */