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
А. Богатырев, 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
8. Экранные библиотеки и работа с видеопамятью.
Терминал в 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
подключающую файл /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 изменяют только ОБРАЗЫ окон в
памяти, а на экране ничего не происходит!). И лишь только ПОСЛЕ того, как вы вызо-