В листинге А.6 показаны объявления типов данных и функций работы со стеком и унарными числами.
Листинг А.6. (definitions.h) Файл заголовков для файлов
number.c
и
stack.c
#ifndef DEFINITIONS_H
#define DEFINITIONS_H 1
/* Представление числа в виде связного списка. */
struct LinkedListNumber {
struct LinkedListNumber* one_less_;
};
typedef struct LinkedListNumber* number;
/* Реализация стека чисел, представленных в виде
связных списков. Значение 0 соответствует
пустому стеку. */
struct StackElement {
number element_;
struct StackElement* next_;
};
typedef struct StackElement* Stack;
/* Операции над стеком. */
Stack create_stack;
int empty_stack(Stack stack);
number pop_stack Stack* stack);
void push_stack(Stack* stack, number n);
void clear_stack(Stack* stack);
/* Операции над числами */
number make_zero;
void destroy_number(number n);
number add(number n1, number n2);
number subtract(number n1, number n2);
number product(number n1, number n2);
number even(number n);
number odd(number n);
number string_to_number(char* char_number);
unsigned number_to_unsigned_int(number n);
#endif /* DEFINITIONS_H */
Приложение Б
Низкоуровневый ввод-вывод
Программисты, пишущие Linux-программы на языке С. имеют в своем распоряжении два набора функций ввода-вывода. Один из них включен в стандартную библиотеку языка С:
printf
,
fopen
и т.д. [41] Мы предполагаем, что читатели уже знакомы с языком С и знают, как использовать эти функции ввода-вывода, поэтому не будем их подробно описывать.
41
В стандартной библиотеке языка C++ аналогичным целям служат потоки ввода-вывода.
Ядро Linux предоставляет собственные операции ввода-вывода, работающие на более низком уровне. В основном они имеют вид системных вызовов и обеспечивают самый непосредственный доступ к файловой системе. По сути, стандартные библиотечные функции реализованы на их основе. Низкоуровневые вызовы обеспечивают наибольшую эффективность операций ввода-вывода.
Б.1. Чтение и запись данных
Первая функция ввода-вывода, с которой сталкиваются те, кто начинают изучать язык С, называется
printf
. Она форматирует текстовую строку и записывает ее в стандартный выходной поток. Обобщенная ее версия
fprintf
записывает текст
в заданный поток. Поток данных представляется в программе указателем типа
FILE*
. Чтобы получить этот указатель, необходимо открыть файл с помощью функции
fopen
. По завершении работы с файлом его необходимо закрыть с помощью функции
fclose
. Помимо функции
fprintf
существуют также функции
fputc
,
fputs
и
fwrite
, записывающие данные в поток. Функции
fscanf
,
fgetc
,
fgets
и
fread
читают данные из потока.
В низкоуровневых операциях ввода-вывода участвуют не файловые указатели, а дескрипторы. Дескриптор представляет собой целое число, обозначающее конкретный экземпляр файла, открытого в одном процессе. Файл можно открыть для чтения, записи, а также одновременно для чтения и записи. Файловому дескриптору не обязательно соответствует файл: это может быть другой системный компонент, способный передавать или принимать данные (аппаратное устройство, сокет, противоположный конец канала).
Для работы с описанными ниже низкоуровневыми функциями необходимо включить в программу файлы
<fcntl.h>
,
<sys/types.h>
,
<sys/stat.h>
и
<unistd.h>
.
Б.1.1. Открытие файла
Чтобы открыть файл и получить дескриптор для работы с ним, необходимо вызвать функцию
open
. В качестве аргументов она принимает строку с путевым именем файла и флаги, определяющие способ открытия. С помощью функции
open
можно также создать новый файл. Для этого ей нужно передать третий аргумент, определяющий права доступа к файлу.
Если второй аргумент равен
O_RDONLY
, файл открывается только для чтения. При попытке записи в такой файл будет выдана ошибка. Точно так же флаг
O_WRONLY
объявляет файл доступным только для записи. В случае флага
O_RDWR
файл открывается и для чтения. и для записи. Не всякий файл можно открыть в любом из трех режимов. Например, существующие права доступа к файлу могут не позволить конкретному процессу открывать файл для чтения или записи. Файл, находящийся в устройстве, запись в которое невозможна (скажем, компакт-диск), тем более нельзя открыть для записи.
Существуют и другие флаги, определяющие режим открытия файла. Все они могут объединяться с помощью операции побитового ИЛИ. Перечислим наиболее распространенные флаги.
■
O_TRUNC
— приводит к очистке существующего файла. Данные, записываемые в файл, замещают предыдущее содержимое файла.
■
O_APPEND
— приводит к открытию файла в режиме добавления. Данные, записываемые в файл, добавляются в его конец.
■
O_CREAT
— означает создание нового файла. Если указанное имя соответствует несуществующему файлу, он будет создан при условии, что заданный каталог существует и процесс имеет разрешение создавать в нем файлы. Если файл уже существует, он будет открыт. При наличии дополнительного флага
O_EXCL
функция
open
откажется открывать существующий файл.
Когда в функции
open
задан флаг
O_CREAT
, должен присутствовать третий аргумент, определяющий права доступа к создаваемому файлу. О режиме доступа к файлу и битах режима рассказывалось в разделе 10.3, "Права доступа к файлам".
Программа, представленная в листинге Б.1, создает файл, имя которого задано в командной строке. Функции
open
передается флаг
O_EXCL
, поэтому в случае указания существующего файла возникнет ошибка. Владельцу и группе нового файла предоставляются права чтения и записи, остальным пользователям — только право чтения (если для пользователя, которому принадлежит программа, установлено значение
umask
, права доступа к файлу могут оказаться более жесткими).