требуется. Для файла было бы использовано макроопределение
_SYS_TYPES_H.
3.20. Любой макрос можно отменить, написав директиву
#undef имяМакро
Пример:
#include
#undef M_UNIX
#undef M_SYSV
main() {
putchar('!');
#undef putchar
#define putchar(c) printf( "Буква '%c'\n", c);
putchar('?');
#if defined(M_UNIX) || defined(M_SYSV)
/* или просто #if M_UNIX */
printf("Это UNIX\n");
#else
printf("Это не UNIX\n");
#endif /* UNIX */
}
Обычно #undef используется именно для переопределения макроса, как putchar в этом
примере (дело в том, что putchar - это макрос из ).
Директива #if, использованная нами, является расширением оператора #ifdef и
подставляет текст если выполнено указанное условие:
А. Богатырев, 1992-95 - 135 - Си в UNIX
#if defined(MACRO) /* равно #ifdef(MACRO) */
#if !defined(MACRO) /* равно #ifndef(MACRO) */
#if VALUE > 15 /* если целая константа
#define VALUE 25
больше 15 (==, !=, <=, ...) */
#if COND1 || COND2 /* если верно любое из условий */
#if COND1 && COND2 /* если верны оба условия */
Директива #if допускает использование в качестве аргумента довольно сложных выраже-
ний, вроде
#if !defined(M1) && (defined(M2) || defined(M3))
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);
}
При компиляции
cc -DDEBUG file.c
в выходном потоке программы будет присутствовать отладочная выдача. При компиляции
без -DDEBUG этой выдачи не будет.
3.22. В языке C++ (развитие языка Си) слова class, delete, friend, new, operator,
overload, template, public, private, protected, this, virtual являются зарезервиро-
ванными (ключевыми). Это может вызвать небольшую проблему при переносе текста прог-
раммы на Си в систему программирования C++, например:
#include
...
int fd_tty = 2; /* stderr */
struct termio old, new;
ioctl (fd_tty, TCGETA, &old);
new = old;
new.c_lflag |= ECHO | ICANON;
ioctl (fd_tty, TCSETAW, &new);
...
Строки, содержащие имя переменной (или функции) new, окажутся неправильными в C++.
Проще всего эта проблема решается переименованием переменной (или функции). Чтобы не
производить правки во всем тексте, достаточно переопределить имя при помощи директивы
define:
А. Богатырев, 1992-95 - 136 - Си в UNIX
#define new new_modes
... старый текст ...
#undef new
При переносе программы на Си в C++ следует также учесть, что в C++ для каждой функции
должен быть задан прототип, прежде чем эта функция будет использована (Си позволяет
опускать прототипы для многих функций, особенно возвращающих значения типов int или
void).
А. Богатырев, 1992-95 - 137 - Си в UNIX
4. Работа с файлами.
Файлы представляют собой области памяти на внешнем носителе (как правило магнит-
ном диске), предназначенные для:
- хранения данных, превосходящих по объему память компьютера (меньше, разумеется,
тоже можно);
- долговременного хранения информации (она сохраняется при выключении машины).
В UNIX и в MS DOS файлы не имеют предопределенной структуры и представляют собой
просто линейные массивы байт. Если вы хотите задать некоторую структуру хранимой
информации - вы должны позаботиться об этом в своей программе сами. Файлы отличаются
от обычных массивов тем, что
- они могут изменять свой размер;
- обращение к элементам этих массивов производится не при помощи операции индекса-
ции [], а при помощи специальных системных вызовов и функций;
- доступ к элементам файла происходит в так называемой "позиции чтения/записи",
которая автоматически продвигается при операциях чтения/записи, т.е. файл прос-
матривается последовательно. Есть, правда, функции для произвольного изменения
этой позиции.
Файлы имеют имена и организованы в иерархическую древовидную структуру из каталогов и
простых файлов. Об этом и о системе именования файлов прочитайте в документации по
UNIX.
4.1. Для работы с каким-либо файлом наша программа должна открыть этот файл - уста-
новить связь между именем файла и некоторой переменной в программе. При открытии
файла в ядре операционной системы выделяется "связующая" структура file "открытый
файл", содержащая:
f_offset:
указатель позиции чтения/записи, который в дальнейшем мы будем обозначать как
RWptr. Это long-число, равное расстоянию в байтах от начала файла до позиции
чтения/записи;
f_flag:
режимы открытия файла: чтение, запись, чтение и запись, некоторые дополнительные
флаги;
f_inode:
расположение файла на диске (в UNIX - в виде ссылки на I-узел файла[*]);
и кое-что еще.
У каждого процесса имеется таблица открытых им файлов - это массив ссылок на
упомянутые "связующие" структуры[**]. При открытии файла в этой таблице ищется
____________________
[*] I-узел (I-node, индексный узел) - своеобразный "паспорт", который есть у каждого
файла (в том числе и каталога). В нем содержатся:
- длина файла long di_size;
- номер владельца файла int di_uid;
- коды доступа и тип файла ushort di_mode;
- время создания и последней модификации
time_t di_ctime, di_mtime;
- начало таблицы блоков файла char di_addr[...];
- количество имен файла short di_nlink;
и.т.п.
Содержимое некоторых полей этого паспорта можно узнать вызовом stat(). Все I-узлы
собраны в единую область в начале файловой системы - так называемый I-файл. Все I-
узлы пронумерованы, начиная с номера 1. Корневой каталог (файл с именем "/") как
правило имеет I-узел номер 2.
[**] У каждого процесса в UNIX также есть свой "паспорт". Часть этого паспорта нахо-
дится в таблице процессов в ядре ОС, а часть - "приклеена" к самому процессу, однако
не доступна из программы непосредственно. Эта вторая часть паспорта носит название
"u-area" или структура user. В нее, в частности, входят таблица открытых процессом
файлов
А. Богатырев, 1992-95 - 138 - Си в UNIX
свободная ячейка, в нее заносится ссылка на структуру "открытый файл" в ядре, и
ИНДЕКС этой ячейки выдается в вашу программу в виде целого числа - так называемого
"дескриптора файла".
При закрытии файла связная структура в ядре уничтожается, ячейка в таблице счи-
тается свободной, т.е. связь программы и файла разрывается.
Дескрипторы являются локальными для каждой программы. Т.е. если две программы
открыли один и тот же файл - дескрипторы этого файла в каждой из них не обязательно
совпадут (хотя и могут). Обратно: одинаковые дескрипторы (номера) в разных програм-
мах не обязательно обозначают один и тот же файл. Следует учесть и еще одну вещь:
несколько или один процессов могут открыть один и тот же файл одновременно несколько
раз. При этом будет создано несколько "связующих" структур (по одной для каждого
открытия); каждая из них будет иметь СВОЙ указатель чтения/записи. Возможна и ситуа-
ция, когда несколько дескрипторов ссылаются к одной структуре - смотри ниже описание
вызова dup2.
fd u_ofile[] struct file
0 ## -------------
1---##---------------->| f_flag |
2 ## | f_count=3 |
3---##---------------->| f_inode---------*
... ## *-------------->| f_offset | |
процесс1 | ------!------ |
| ! V
0 ## | struct file ! struct inode
1 ## | ------------- ! -------------
2---##-* | f_flag | ! | i_count=2 |
3---##--->| f_count=1 | ! | i_addr[]----*
... ## | f_inode----------!-->| ... | | адреса
процесс2 | f_offset | ! ------------- | блоков
-------!----- *=========* | файла
! ! V
0 ! указатели R/W ! i_size-1
@@@@@@@@@@@!@@@@@@@@@@@@@@@@@@@@@!@@@@@@
файл на диске
/* открыть файл */
int fd = open(char имя_файла[], int как_открыть);
... /* какие-то операции с файлом */
close(fd); /* закрыть */
Параметр как_открыть:
#include
O_RDONLY - только для чтения.
O_WRONLY - только для записи.
O_RDWR - для чтения и записи.
O_APPEND - иногда используется вместе с
открытием для записи, "добавление" в файл:
O_WRONLY|O_APPEND, O_RDWR|O_APPEND
Если файл еще не существовал, то его нельзя открыть: open вернет значение (-1),
____________________
struct file *u_ofile[NOFILE];
ссылка на I-узел текущего каталога
struct inode *u_cdir;
а также ссылка на часть паспорта в таблице процессов
struct proc *u_procp;
А. Богатырев, 1992-95 - 139 - Си в UNIX
сигнализирующее об ошибке. В этом случае файл надо создать:
int fd = creat(char имя_файла[], int коды_доступа);
Дескриптор fd будет открыт для записи в этот новый пустой файл. Если же файл уже
существовал, creat опустошает его, т.е. уничтожает его прежнее содержимое и делает
его длину равной 0L байт. Коды_доступа задают права пользователей на доступ к файлу.
Это число задает битовую шкалу из 9и бит, соответствующих строке
биты: 876 543 210
rwx rwx rwx
r - можно читать файл
w - можно записывать в файл
x - можно выполнять программу из этого файла
Первая группа - эта права владельца файла, вторая - членов его группы, третяя - всех
прочих. Эти коды для владельца файла имеют еще и мнемонические имена (используемые в
вызове stat):
#include /* Там определено: */
#define S_IREAD 0400
#define S_IWRITE 0200
#define S_IEXEC 0100
Подробности - в руководствах по системе UNIX. Отметим в частности, что open() может
вернуть код ошибки fd < 0 не только в случае, когда файл не существует
(errno==ENOENT), но и в случае, когда вам не разрешен соответствующий доступ к этому
файлу (errno==EACCES; про переменную кода ошибки errno см. в главе "Взаимодействие с
UNIX").
Вызов creat - это просто разновидность вызова open в форме
fd = open( имя_файла,
O_WRONLY|O_TRUNC|O_CREAT, коды_доступа);
O_TRUNC
означает, что если файл уже существует, то он должен быть опустошен при откры-
тии. Коды доступа и владелец не изменяются.
O_CREAT
означает, что файл должен быть создан, если его не было (без этого флага файл не
создастся, а open вернет fd < 0). Этот флаг требует задания третьего аргумента
коды_доступа[*]. Если файл уже существует - этот флаг не имеет никакого эффекта,
но зато вступает в действие O_TRUNC.
Существует также флаг
O_EXCL
который может использоваться совместно с O_CREAT. Он делает следующее: если
файл уже существует, open вернет код ошибки (errno==EEXIST). Если файл не