В стандарте POSIX-2001 предусмотрены две основные группы
функций, обслуживающие операции
<fcntl.h> );<stdio.h> ).Применение
FILE, с которой ассоциирован
соответствующий дескриптор
Существенны не только содержимое, но и адрес объекта типа FILE ;
копия подобного объекта не обязательно является корректным
представлением
И FILE с именами, соответственно, stdin, stdout и stderr.
При
Если файл поддерживает запросы на позиционирование (таковы обычные файлы в противоположность, например, символьным специальным, соответствующим терминалам), то после открытия индикатор текущей позиции устанавливается в начало (на нулевой байт) при условии, что файл не открывали на добавление; в этом случае от реализации зависит, будет ли индикатор указывать на начало или конец файла.
В дальнейшем индикатор текущей позиции смещается под воздействием операций чтения, записи и позиционирования, чтобы упростить последовательное продвижение по файлу.
Стандартом C99 [5] предусмотрены байтные и широкие символы.
Соответственно, в стандарте POSIX-2001 введено понятие ориентации
После завершения работы с файлом его необходимо закрыть. При
этом не только разрывается связь между файлами с одной стороны и
дескрипторами и
Стандарт POSIX-2001 предусматривает как синхронный, так и
асинхронный
С одним файлом могут параллельно работать несколько процессов,
выполняющихся на разных хостах (узлах сети) и использующих разные
буфера. Для корректного обслуживания подобной ситуации
Операция синхронизированного
Синхронизированное чтение с целостностью данных считается завершенным, когда образ данных успешно передан запрашивающему процессу. Если в момент запроса операции чтения оставались незавершенные операции записи, пересекающиеся с чтением по данным, сначала должны быть обслужены запросы на запись.
Синхронизированная запись с целостностью данных завершена, когда данные успешно переданы и, кроме того, модифицирована и передана в стабильную память информация о файловой системе, необходимая для последующей выборки данных (такая, например, как размер файла).
Синхронизированная операция с
Как уже указывалось, open() и pipe() (см.
листинг 5.1), а также функции
#include <fcntl.h> int open (const char *path, int oflag, ...); #include <unistd.h> int pipe (int fildes [2]);
#include <stdio.h>
FILE *fopen (const char *restrict path,
const char *restrict mode);
#include <stdio.h>
FILE *fdopen (int fildes,
const char *mode);
#include <stdio.h>
FILE *freopen (const char *restrict path, const
char *restrict mode, FILE *restrict stream);
Функция open() открывает файл с заданным маршрутным именем
(первый аргумент, path ), создавая для него описание – новое и,
следовательно, не разделяемое с другими процессами. Возвращаемый в качестве
результата -1 ).
Второй аргумент, oflag, устанавливает флаги статуса файла и
определяет допустимые виды операций
O_RDONLY
O_WRONLY
O_RDWR
Следующие флаги могут комбинироваться произвольным образом.
O_APPEND
Перед каждой записью устанавливать индикатор текущей позиции на конец файла.
O_CREAT
Если файл существует, данный флаг принимается во внимание
только при наличии описываемого далее флага O_EXCL. Если файла нет, он
создается от имени текущего пользователя. Обратим внимание, что
функция open() имеет переменное число аргументов. При создании файла
предполагается, что в качестве третьего аргумента типа mode_t задается
режим доступа.
O_EXCL
Если установлены флаги O_CREAT и O_EXCL, а файл существует
(хотя бы и в виде символьной ссылки на несуществующий файл), вызов open() завершится неудачей. Проверка существования файла и его
создание представляют собой атомарное действие по отношению к
попыткам других процессов (
O_TRUNC
Если файл существует, является обычным и успешно открывается с
флагами O_RDWR или O_WRONLY, он опустошается (размер устанавливается
равным нулю).
Отметим, что рассмотренная ранее функция creat (path, mode)
по определению эквивалентна вызову open (path, O_WRONLY |
O_CREAT | O_TRUNC, mode).
Следующий флаг относится к асинхронному
O_NONBLOCK
Обычно, если канал открывается только на чтение (или только на
запись), процесс ( O_NONBLOCK
открытие на чтение завершается без задержки, а открытие на запись
заканчивается неудачей, если канал еще не открыт кем-либо на чтение.
При открытии специального файла вызов open() завершается
только после того, как устройство оказывается в состоянии готовности.
Флаг O_NONBLOCK отменяет эту задержку, однако последующее поведе-
ние устройства зависит от реализации.
Следующая группа флагов обслуживает синхронизированный
O_DSYNC
Операции записи с возвращаемым файловым дескриптором должны завершаться с обеспечением целостности данных.
O_SYNC
Операции записи должны завершаться с обеспечением целостности файла.
O_RSYNC
Операции чтения должны завершаться на уровне целостности, заданном флагами O_DSYNC или O_SYNC.
Смысл последнего из стандартизованных флагов будет пояснен при рассмотрении процессов.
O_NOCTTY
Если открывается специальный файл, соответствующий терминалу,
последний не должен становиться управляющим терминалом процесса.
Для создания и открытия канала предназначена функция pipe(). В
массиве fildes она возвращает сразу два дескриптора: fildes [0] служит для чтения, fildes [1] – для записи. Данные читаются в том же
порядке, в каком были записаны.
При успешном завершении pipe() возвращает 0, при неудаче – -1.
Функция fopen() из группы open(), только вместо файлового дескриптора в качестве
результата возвращается указатель на объект, служащий для управления
сформированным NULL ).
Второй аргумент, mode, определяющий допустимые виды операций
"r"
"w"
Опустошить или создать файл, открыв его на запись.
"a"
Открыть или создать файл на запись в конец.
"r+"
"w+"
Опустошить или создать файл, открыв его на изменение.
"a+"
Открыть или создать файл на изменение с записью в конец.
(Стандарт языка C позволяет приписывать к перечисленным
цепочкам символ 'b', который, впрочем, ни на что не влияет.)
Если открытый файл не соответствует интерактивному устройству,
ассоциированный с ним
Функция fdopen() формирует
Второй аргумент, mode, может принимать те же значения, что и для fopen(), но их трактовка по понятным причинам отличается:
существующие файлы не опустошаются, а новые не создаются.
Функция freopen() предназначена для ассоциирования
существующего stream ) с заданным файлом (первый
аргумент, path ) и разрешенными видами операций mode ).
В первую очередь freopen() пытается вытолкнуть буфера stream и закрыть ассоциированный с ним файл. Неудача данного
действия ни на что не влияет.
Затем, аналогично fopen(), открывается заданный файл, только
без формирования нового stream (лишенный,
правда, ориентации) или NULL (в случае неудачи).
Если в качестве первого аргумента функции freopen() задан
пустой указатель, делается попытка изменить виды операций, разрешенные
для stream. (Формально можно считать, что первым аргументом freopen() служит имя файла, ассоциированного с этим
Для закрытия файлов (точнее, файловых дескрипторов или close() и fclose() (см. листинг 5.3).
#include <unistd.h> int close (int fildes); #include <stdio.h> int fclose (FILE *stream);
Функция close() освобождает файловый дескриптор fildes,
который становится доступным для последующего использования при
Когда закрывается последний дескриптор, ссылающийся на
Если число жестких ссылок на файл равно нулю и закрывается последний ассоциированный с ним дескриптор, файл перестает быть доступным, а занимавшееся им пространство освобождается.
Когда закрывается последний дескриптор, ассоциированный с каналом, все оставшиеся непрочитанными данные теряются.
Функция close() возвращает 0 в случае успешного завершения и -1 при неудаче.
Функция fclose() по сути аналогична, только она освобождает 0, признаком неудачи – константа EOF.
Приведем примеры использования описанных функций. Сочетание
флагов O_CREAT и O_EXCL функции open() позволяет организовать
проверку и создание файлов-замков, для которых важен факт
существования в одном экземпляре, а не содержимое (см. листинг 5.4).
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#define LOCK_FILE "my_lock"
/* Функция пытается создать файл-замок */
/* Результат равен 0 в случае успеха, */
/* 1, если файл уже существует, */
/* -1 в случае прочих ошибок */
static int gate (const char *lock_name) {
int fd;
if ((fd = open (lock_name, O_WRONLY | O_CREAT |
O_EXCL, (mode_t) 0)) < 0) {
if (errno == EEXIST) {
return (1);
}
return (-1);
}
return (close (fd));
}
int main (void) {
int res;
if ((res = gate (LOCK_FILE)) > 0) {
perror ("Ошибка при создании файла-замка "
LOCK_FILE);
} else if (res == 1) {
fprintf (stderr, "Файл-замок " LOCK_FILE "
уже существует\n");
}
return (res);
}
Читателю предлагается выполнить приведенную программу дважды.
Следующая программа иллюстрирует перенаправление стандартного
#include <stdio.h>
#define LOGFILE "my_logfile"
int main (void) {
FILE *fp;
printf ("До перенаправления стандартного вывода
в файл " LOGFILE "\n");
if ((fp = freopen (LOGFILE, "a", stdout)) ==
NULL) {
perror ("Не удалось перенаправить стандартный
вывод в файл " LOGFILE);
return (-1);
}
printf ("После перенаправления стандартного
вывода в файл " LOGFILE "\n");
if (fclose (fp) == EOF) {
perror ("Не удалось закрыть файл " LOGFILE);
return (-1);
}
printf ("После закрытия файла " LOGFILE "\n");
return (0);
}
Весьма полезной с практической точки зрения является функция
создания и открытия временных файлов tmpfile() (см. листинг 5.6).
#include <stdio.h> FILE *tmpfile (void);
Временный файл открывается на изменение ( w+ ) и автоматически
удаляется после закрытия всех ссылок на него.
Использование функции tmpfile() предпочтительнее генерации
"временного" имени с помощью функции tmpnam() и последующего
создания файла с этим именем, поскольку в промежутке какой-либо другой
процесс может создать одноименный файл.
Чтение данных из файла выполняют функции read() и
(см. листинг 5.7).
#include <unistd.h> ssize_t read (int fd, void *buf, size_t nbyte); #include <stdio.h> size_t fread (void *restrict buf, size_t size, size_t nitems, FILE *restrict stream);
Функция read() пытается прочитать nbyte байт из файла,
ассоциированного с дескриптором fd, и поместить их в буфер buf.
Для файлов, допускающих позиционирование, read() выполняет
чтение, начиная со значения индикатора текущей позиции,
ассоциированного с дескриптором fd. После завершения операции этот индикатор
увеличивается на количество прочитанных байт. Для устройств, не
поддерживающих позиционирования (таких, например, как терминал),
значение упомянутого индикатора не определено, а чтение выполняется с
текущей позиции устройства.
При успешном завершении read() возвращает количество байт,
реально прочитанных и помещенных в буфер; это значение может оказаться
меньше значения аргумента nbyte, если до конца файла оставалось
меньше, чем nbyte байт. Например, если текущая позиция совпадала с
концом файла, результат будет равен 0. В случае ошибки возвращается -1.
Функция во многом
аналогична read(), но число читаемых байт задается как произведение
размера одного элемента (аргумент size ) на число элементов (аргумент nitems ), а результатом служит количество успешно прочитанных
элементов. В стандарте оговаривается, что элементы читаются побайтно.
Число элементов, успешно прочитанных функцией ,
может быть меньше затребованного, только если достигнут конец
файла или произошла ошибка чтения. В таком случае
устанавливает для feof() и ferror(), соответственно
(см. листинг 5.8), возвращая при установленном индикаторе
ненулевой результат.
#include <stdio.h> int feof (FILE *stream); #include <stdio.h> int ferror (FILE *stream);
Отметим, что использование функции бинарного
ограничивает мобильность приложений, так как результат зависит от размера
элементов и порядка байт, поддерживаемого процессором.
Обратим также внимание на некоторые нюансы синхронного и
асинхронного read(). При попытке чтения
из пустого канала, не открытого кем-либо на запись, результат равен 0
(как O_NONBLOCK возвращается -1 (как признак
ошибки EAGAIN ); при отсутствии флага O_NONBLOCK процесс (
Содержимое символьных ссылок приходится читать особым
образом (хотя бы потому, что обычно функция open() раскрывает их, т. е.
открывает указуемый файл). Для этого служит функция readlink() (см.
листинг 5.9). Она помещает содержимое ссылки с именем link_name в
буфер buf длины buf_size (если буфер мал, остаток содержимого
отбрасывается). Результат равен числу помещенных в буфер байт или -1 в
случае неудачи.
#include <unistd.h>
ssize_t readlink (const char *restrict
link_name, char *restrict buf,
size_t buf_size);
Следующая программа (см. листинг 5.10) переправляет недлинные
сообщения с
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#define C_TERM "/dev/tty"
int main (void) {
char buf [BUFSIZ];
int fd;
ssize_t line_len;
/* Открытие на чтение специального файла, */
/* ассоциированного с управляющим терминалом */
if ((fd = open (C_TERM, O_RDONLY)) < 0) {
perror ("OPEN");
return (-1);
}
/* Ввод с терминала */
printf ("Вводите строки\n");
while ((line_len = read (fd, buf, BUFSIZ - 1))
> 0) {
buf [line_len] = '\0';
printf ("Вы ввели: %s", buf);
}
if (line_len == -1) {
perror ("READ");
close (fd);
return (-1);
}
return (close (fd));
}
В качестве примера мобильного использования функции ,
а также функций feof() и ferror(), рассмотрим программу,
подсчитывающую число символов, слов и строк в файле – аргументе командной
строки (см. листинг 5.11).
/* * * * * * * * * * * * * * * * * * * * * */
/* Подсчет символов, слов и строк в файле */
/* * * * * * * * * * * * * * * * * * * * * */
#include <stdio.h>
int main (int argc, char *argv[]) {
long nwords = 0; /* Счетчик слов */
long nlines = 0; /* Счетчик строк */
long nchars = 0; /* Счетчик символов */
FILE *fp = stdin; /* Если файл не задан, */
/* читается стандартный ввод */
unsigned char buf [BUFSIZ]; /* Буфер для */
/* чтения файла */
unsigned char *p1; /* Указатель на */
/* обрабатываемую часть буфера */
size_t nbytes = 0; /* Количество прочитанных,*/
/* но не обработанных байт */
register int c; /* Обрабатываемый символ */
int inword = 0; /* Признак - находимся ли мы */
/* внутри слова */
if (argc > 2) {
fprintf (stderr, "Использование: %s [файл]\n",
argv [0]);
return (2);
}
if (argc > 1 (fp = fopen (argv [1], "r")) ==
NULL) {
perror ("OPEN");
fprintf (stderr, "%s: Не могу открыть файл
%s\n", argv [0], argv[1]);
return (2);
}
p1 = buf;
for (;;) { /* Посимвольная обработка файла */
if (nbytes == 0) { /* Нужно прочитать новую порцию */
if (feof (fp)) {
/* При предыдущем чтении дошли до конца файла */
break;
}
nbytes = fread (p1 = buf, (size_t) 1,
sizeof (buf), fp);
if (ferror (fp)) {
perror ("READ");
fprintf (stderr, "%s: Ошибка чтения из
файла %s:", argv [0],
argc == 1 ? "стандартного ввода" : argv
[1]);
break;
}
if (nbytes == 0) {
/* В файле не оставалось непрочитанных символов */
break;
}
nchars += nbytes;
}
c = *p1++; /* Обработка одного символа */
nbytes--;
if (c > ' ') {
if (!inword) {
nwords++;
inword = 1;
}
continue;
}
if (c == '\n') {
nlines++;
} else if (c != ' ' c != '\t') {
continue;
}
inword = 0;
}
if (argc > 1) {
fclose (fp);
}
printf ("Файл %s: строк: %ld, слов: %ld,
символов: %ld\n",
argc == 1 ? "стандартного ввода" : argv [1],
nlines, nwords, nchars);
return (0);
}
Читателю предлагается убрать из цикла проверку feof ( fp ) и
оценить, как изменится обработка интерактивного стандартного
Для иллюстрации использования функции readlink() напишем
программу, выдающую на стандартный
#include <unistd.h>
#include <stdio.h>
/* Программа выдает на стандартный вывод */
/* содержимое символьных ссылок - */
/* аргументов командной строки */
int main (int argc, char *argv[]) {
char buf [BUFSIZ];
ssize_t link_len;
int err = 0;
int i;
for (i = 1; i < argc; i++) {
if ((link_len = readlink (argv [i], buf,
sizeof (buf) - 1)) < 0) {
perror ("READLINK");
fprintf (stderr, "%s: Не удалось прочитать содержимое символьной ссылки %s\n", argv [0], argv [i]);
err = -1;
continue;
}
buf [link_len] = '\0';
printf ("Содержимое символьной ссылки %s -> %s\n", argv [i], buf);
}
return (err);
}
write() и fwrite()
(см. листинг 5.13).
#include <unistd.h>
ssize_t write (int fildes, const void *buf,
size_t nbyte);
#include <stdio.h>
size_t fwrite (const void *restrict buf,
size_t size, size_t nitems,
FILE *restrict stream);
Вероятно, лишь несколько моментов, связанных с функциями write() и fwrite(), нуждаются в пояснениях. При записи размер файла
может увеличиться. Если
При записи в канал, если флаг O_NONBLOCK не установлен, процесс
( write() вернет nbyte. При установленном флаге O_NONBLOCK
поведение зависит от значения nbyte и наличия свободного места в канале.
Если nbyte не превосходит константы PIPE_BUF, запишется все или ничего
(в последнем случае результат будет равен -1 ). При попытке записать
порцию данных большего размера запишется сколько можно или ничего.
Приведем несколько примеров. Следующая программа (см. листинг 5.14)
выводит приветствие на
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#define C_TERM "/dev/tty"
char msg [] = "HELLO !!!\n";
int main (void) {
int fd;
/* Открытие на запись специального файла,
ассоциированного с управляющим терминалом */
if ((fd = open (C_TERM, O_WRONLY)) < 0)
{
perror ("OPEN");
return (-1);
}
/* Вывод на терминал */
if (write (fd, msg, sizeof (msg)) != (ssize_t)
sizeof (msg)) {
perror ("WRITE");
return (1);
}
return (close (fd));
}
Программа prnmyself (см. листинг 5.15) выводит свой собственный
исходный текст. При этом применяется следующий прием: данные
фиксированными порциями читаются из файла и выводятся на терминал;
процесс повторяется до тех пор, пока число реально прочитанных байт
совпадает с указанным (до обнаружения конца файла).
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#define SOURCE_FILE "prnmyself.c"
#define C_TERM "/dev/tty"
int main (void) {
unsigned char buf [BUFSIZ];
int fdr, fdw; /* Дескрипторы для чтения и записи */
ssize_t nb;
if (((fdr = open (SOURCE_FILE, O_RDONLY)) < 0) ||
((fdw = open (C_TERM, O_WRONLY)) < 0)) {
perror ("OPEN " SOURCE_FILE " or " C_TERM);
return (1);
}
do {
if ((nb = read (fdr, buf, BUFSIZ)) < 0) {
perror ("READ");
break;
}
if (write (fdw, buf, nb) != nb) {
perror ("WRITE");
break;
}
} while (nb == BUFSIZ);
(void) close (fdw);
(void) close (fdr);
return (0);
}
Для fgetc()
и fputc(), строки рекомендуется вводить, вызывая функцию fgets(),
а выводить с помощью функций fputs() и puts() (см. листинг 5.16).
#include <stdio.h> int fgetc (FILE *stream); #include <stdio.h> int fputc (int c, FILE *stream); #include <stdio.h> char *fgets (char *restrict s, int n, FILE *restrict stream); #include <stdio.h> int fputs (const char *restrict s, FILE *restrict stream); #include <stdio.h> int puts (const char *s);
Описание аналогичных функций для широких символов приведено в листинге 5.17.
#include <stdio.h> #include <wchar.h> wint_t fgetwc (FILE *stream); #include <stdio.h> #include <wchar.h> wint_t fputwc (wchar_t wc, FILE *stream); #include <stdio.h> #include <wchar.h> wchar_t *fgetws (wchar_t *restrict ws, int n, FILE *restrict stream); #include <stdio.h> #include <wchar.h> int fputws (const wchar_t *restrict ws, FILE *restrict stream);
Функция fgetc() пытается прочитать из заданного unsigned char в int и вернуть в качестве
результата. В случае ошибки или при достижении конца файла
возвращается константа EOF.
Функция fgets() читает из заданного s ( n - 1 ) байт или строку, включая символ перевода строки, или
байты, оставшиеся до конца файла, если длина строки или число байт до
конца меньше ( n - 1 ). После прочитанных добавляется нулевой байт.
При нормальном завершении fgets() возвращает s. В случае
ошибки или при достижении конца файла возвращается пустой указатель.
Функция fputc() помещает в c, предварительно
преобразовав его к типу unsigned char. Результатом служит c или EOF.
Функция fputs() выводит в EOF. Функция puts() делает то же для stdout, завершая
Функции для работы с int на wint_t, char на wchar_t и EOF на WEOF.
Программа, показанная в листинге 5.18, иллюстрирует использование
функций fgets() и fputs(). Читателю предлагается сравнить ее с
листингом 5.10.
#include <stdio.h>
#include <limits.h>
/* Программа копирует строки со стандартного ввода на стандартный вывод */
int main (void) {
char line [LINE_MAX];
fputs ("Вводите строки\n", stdout);
while (fgets (line, sizeof (line), stdin) != NULL) {
if ((fputs ("Вы ввели: ", stdout) == EOF) || (fputs (line, stdout) == EOF)) {
break;
}
}
return (ferror (stdin) || ferror (stdout));
}
Использование функций fgetc() и fputc() иллюстрируется программой,
написанной С.В. Самборским (см. листинг 5.19). Она
выполняет раскодировку файлов формата base64, применяемого, например, в
электронной почте.
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <endian.h>
FILE *input=NULL, *output=NULL;
const char str []
="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
unsigned int Prm [256]; /* Таблица перекодировки */
const int WHITE = 100, ERR = 101, END = 102;
static void usage (char argv0 []) {
fprintf (stderr,"Программа раскодирует файлы
формата base64\n");
fprintf (stderr,"Использование:\n%s входной_файл
выходной_файл\n", argv0);
fprintf (stderr,"Файл должен начинаться с
первого символа в кодировке base64.\n");
}
int main (int argc, char *argv []) {
int n;
union {
unsigned long l;
char c[4];
} a;
{
int i;
for (i = 0; i < 256; i++) Prm [i] = ERR;
Prm [' '] = WHITE; Prm ['\t'] = WHITE;
Prm ['\n'] = WHITE; Prm ['\r'] = WHITE;
Prm ['='] = END;
for (i = 0; i < 64; i++) Prm [(int) str [i]] = i;
}
if (argc != 3) {
usage (argv [0]);
return (1);
}
assert (NULL != (input = fopen (argv [1], "r")));
assert (NULL != (output = fopen (argv [2], "w")));
for (a.l = 0, n = 0; ; ) { /* Цикл обработки
входного файла */
int c, b, shift;
assert (EOF != (c = fgetc (input)));
b = Prm [c];
if (WHITE == b) continue;
if (END == b) break;
if (ERR == b) {
fprintf (stderr,"Символ номер %d: %d не
входит в кодировку base64\n", n, c);
return (1);
}
n++;
assert (b < 64);
shift = 6 * (4 - n % 4);
if (shift != 24) b = b << shift;
a.l += b;
if (0 == n % 4) {
#if __BYTE_ORDER == __BIG_ENDIAN
fputc (a.c[1], output); fputc (a.c[2], output); fputc (a.c[3], output);
#elif __BYTE_ORDER == __LITTLE_ENDIAN
fputc (a.c[2], output); fputc (a.c[1],
output); fputc (a.c[0], output);
#elif __BYTE_ORDER == __PDP_ENDIAN
fputc (a.c[0], output); fputc (a.c[3],
output); fputc (a.c[2], output);
#else
#error "Unknown endian"
#endif
a.l = 0;
}
}
{ /* Обработка остатка входного файла */
int tl = (((n - 1) % 4) * 6 + 7) / 8;
if (tl == 3) {
#if __BYTE_ORDER == __BIG_ENDIAN
fputc (a.c[1], output); fputc (a.c[2], output); fputc (a.c[3], output);
#elif __BYTE_ORDER == __LITTLE_ENDIAN
fputc (a.c[2], output); fputc (a.c[1], output);
fputc (a.c[0], output);
#elif __BYTE_ORDER == __PDP_ENDIAN
fputc (a.c[0], output); fputc (a.c[3], output); fputc (a.c[2], output);
#else
#error "Unknown endian"
#endif
}
if (tl == 2) {
#if __BYTE_ORDER == __BIG_ENDIAN
fputc (a.c[1], output); fputc (a.c[2],
output);
#elif __BYTE_ORDER == __LITTLE_ENDIAN
fputc (a.c[2], output); fputc (a.c[1],
output);
#elif __BYTE_ORDER == __PDP_ENDIAN
fputc (a.c[0], output); fputc (a.c[3],
output);
#else
#error "Unknown endian"
#endif
}
if (tl == 1) {
#if __BYTE_ORDER == __BIG_ENDIAN
fputc (a.c[1], output);
#elif __BYTE_ORDER == __LITTLE_ENDIAN
fputc (a.c[2], output);
#elif __BYTE_ORDER == __PDP_ENDIAN
fputc (a.c[0], output);
#else
#error "Unknown endian"
#endif
}
}
fclose (input);
fclose (output);
return (0);
}
Приведенный пример показывает, что написание мобильных
программ даже для сравнительно простых задач требует заметных усилий.
В данном случае пришлось воспользоваться нестандартной
возможностью – включаемым файлом <, содержащим
препроцессорные константы, которые описывают
Отметим также стиль обработки ошибочных ситуаций, основанный
на применении макроса assert. Для авторов программ такой стиль,
безусловно, упрощает жизнь, исходный текст получается более
компактным, однако для пользователей сообщение вида
decode64: decode64.c:39: main: Assertion `((void *)0) != (input = fopen (argv [1], "r"))' failed.
может оказаться менее информативным, чем выдача функции perror().
К числу управляющих операций с файлами мы отнесем прежде
всего позиционирование. Индикатор текущей позиции может быть опрошен
или передвинут при помощи lseek(), а также
функций fseek(), ftell(), ftello(), fgetpos(), fsetpos(), (см. листинг 5.20).
#include <unistd.h>
off_t lseek (int fildes, off_t offset,
int whence);
#include <stdio.h>
int fseek (FILE *stream, long offset,
int whence);
long ftell (FILE *stream);
off_t ftello (FILE *stream);
int fgetpos (FILE *restrict stream,
fpos_t *restrict pos);
int fsetpos (FILE *stream, const fpos_t *pos);
void rewind (FILE *stream);
Функция lseek() устанавливает индикатор текущей позиции
следующим образом. Сначала, в зависимости от значения третьего
аргумента, whence, выбирается точка отсчета: 0, если это значение равно SEEK_SET, текущая позиция для SEEK_CUR и размер файла для SEEK_END. Затем к точке отсчета прибавляется смещение offset (второй аргумент).
Индикатор текущей позиции можно сместить за конец файла (причем его размер не изменится). Если с этой позиции будет произведена запись, в файле образуется дыра, чтение из которой выдает нулевые байты.
Результатом функции lseek() служит новое значение индикатора
текущей позиции, отсчитанное в байтах от начала файла. В случае
ошибки возвращается ( off_t ) ( -1 ), а текущая позиция остается
прежней.
Функция fseek() по сути аналогична, только в случае нормального
завершения возвращается 0. Кроме того, если whence должно равняться SEEK_SET, а
значение offset – нулю или результату вызова функции ftell() для
того же
Функция ftell() возвращает значение индикатора текущей
позиции для заданного (long) (-1) ). Функция ftello() эквивалентна ftell() с точностью до типа результата; в
новых приложениях рекомендуется использовать ftello(), так как эта
функция применима к большим файлам.
Функции fgetpos() и fsetpos() являются парными. Первая из
них заполняет структуру, на которую указывает аргумент pos, а вторая
применяет ее для установки индикатора текущей позиции. Нормальным
результатом служит 0.
Функция , если пренебречь некоторыми тонкостями,
сводится к вызову
(void) fseek (stream, 0L, SEEK_SET).
Приведем несколько примеров. В листинге 5.21 показана установка индикатора текущей позиции в начало и конец файла, а также его (индикатора) приращение.
(void) lseek (fildes, (off_t) 0, SEEK_SET); (void) lseek (fildes, (off_t) 0, SEEK_END); (void) lseek (fildes, inc, SEEK_CUR);
Применение функций
/* * * * * * * * * * * * * * * * * * * * * * * */
/* Набор функций для занесения текстов в файл, */
/* который составляется из двух частей: */
/* таблицы длин и смещений текстов от начала */
/* файла собственно текстов */
/* */
/* В начало файла помещается специальный */
/* элемент таблицы с магическим числом и общим */
/* количеством текстов */
/* * * * * * * * * * * * * * * * * * * * * * * */
#include <stdio.h>
#include <string.h>
/* Магическое число файла с текстами */
#define G_TXT_MAGIC 0x1993
/* Элемент таблицы длин и смещений */
typedef struct {
unsigned int l_txt; /* Длина текста (без */
/* (нулевого байта) */
unsigned long off_txt; /* Смещение текста от */
/* начала файла */
} txt_table_elem;
static FILE *fp = NULL;/* Указатель на поток */
/* файла с текстами */
static unsigned long max_n_txt; /* Общее число */
/* текстов в файле */
/* * * * * * * * * * * * * * * * * * * * * * * */
/* Функция для инициализации набора. */
/* n_txts - максимальное число добавляемых текстов */
/* * * * * * * * * * * * * * * * * * * * * * * */
int g_init_add_txt (const int argc, char *argv [],
const unsigned long n_txts) {
char *path; /* Имя файла, куда нужно поместить
тексты */
int magic; /* Магическое число файла с текстами */
txt_table_elem tte;
unsigned int i;
if (argc != 2) {
fprintf (stderr, "Использование: %s
файл_для_текстов\n", argv [0]);
return (-1);
}
path = argv [1];
/* Аккуратно откроем файл с текстами */
/* Если он уже есть и в нем правильное */
/* магическое число, */
/* будем добавлять тексты. */
/* В противном случае создадим и */
/* инициализируем файл */
if (((fp = fopen (path, "r+")) != NULL) (fread (magic, sizeof (unsigned int), 1, fp) == 1) (magic == G_TXT_MAGIC)) {
/* Перед нами - наш файл */
/* Проверим, не превышает ли заказанная */
/* верхняя граница существующую */
if (fread (max_n_txt, sizeof (unsigned long), 1, fp) != 1) {
fprintf (stderr, "Не удается прочитать
информацию из файла %s\n", path);
return (-1);
}
if (n_txts > max_n_txt) {
fprintf (stderr, "***** Новая верхняя
граница числа сообщений %lu больше
существующей %lu\n", n_txts, max_n_txt);
}
} else {
/* Файла нет или он не наш */
(void) fclose (fp);
if ((fp = fopen (path, "w+")) == NULL) {
fprintf (stderr, "Не удается открыть файл
%s\n", path);
return (-1);
}
tte.l_txt = magic = G_TXT_MAGIC;
tte.off_txt = max_n_txt = n_txts;
if (fwrite (tte, sizeof (txt_table_elem), 1, fp) != 1) {
fprintf (stderr, "Не удается записать
информацию в файл %s\n", path);
return (-1);
}
/* Пропишем нулями индексную таблицу */
/* Заодно конец файла окажется в будущем */
/* начале текстов */
tte.l_txt = 0;
tte.off_txt = 0;
for (i = 0; i < max_n_txt; i++) {
if (fwrite (tte, sizeof (txt_table_elem), 1, fp) != 1) {
fprintf (stderr, "Не удается записать информацию
в файл %s\n", path);
return (-1);
}
}
} /* if - существует ли файл с текстами */
return 0;
}
/* * * * * * * * * * * * * * * * * * * * * * * */
/* Функция для добавления одного текста */
/* * * * * * * * * * * * * * * * * * * * * * * */
int g_add_txt (const unsigned long n_t, const char *txt) {
unsigned int l; /* Длина текста txt */
txt_table_elem tte;
if (n_t >= max_n_txt) {
fprintf (stderr, "Номер текста: %lu должен
быть меньше: %lu\n", n_t, max_n_txt);
return (-1);
}
l = strlen (txt);
tte.l_txt = l;
if (fseek (fp, 0L, SEEK_END)) {
fprintf (stderr, "Ошибка позиционирования при
добавлении текста номер %lu\n", n_t);
return (-1);
}
tte.off_txt = ftell (fp);
if (fseek (fp, (n_t + 1) * sizeof (txt_table_elem), SEEK_SET)) {
fprintf (stderr, "Ошибка позиционирования при
добавлении текста номер %lu\n", n_t);
return (-1);
}
if (fwrite (tte, sizeof (tte), 1, fp) != 1) {
fprintf (stderr, "Ошибка записи при добавлении
текста номер %lu\n", n_t);
return (-1);
}
if (fseek (fp, tte.off_txt, SEEK_SET)) {
fprintf (stderr, "Ошибка позиционирования при
добавлении текста номер %lu\n", n_t);
return (-1);
}
if (fwrite (txt, sizeof (char), l, fp) != l) {
fprintf (stderr, "Ошибка записи при добавлении
текста номер %lu\n", n_t);
return (-1);
}
return 0;
}
/* * * * * * * * * * * * * * * * * * * * * * * */
/* Функция для завершения добавления текстов */
/* * * * * * * * * * * * * * * * * * * * * * * */
int g_term_add_txt () {
return (fclose (fp));
}
/* * * * * * * * * * * * * * * * * * * * * * * */
/* Главная программа, вызывающая определенные */
/* выше функции */
/* * * * * * * * * * * * * * * * * * * * * * * */
#define MAX_TXTS 10240
int main (int argc, char *argv[]) {
if (g_init_add_txt (argc, argv, MAX_TXTS) ||
g_add_txt (0, "Reference to section number %d in %s\n") ||
g_add_txt (1, "Data .init section in %s\n")) {
(void) g_term_add_txt ();
return (-1);
}
return (g_term_add_txt ());
}
Функция fcntl() предназначена для выполнения разнообразных
управляющих операций над
#include <fcntl.h> int fcntl (int fildes, int cmd, ...);
Аргумент fildes задает дескриптор cmd –
управляющую команду, дополнительные данные для которой могут быть
переданы в качестве третьего (необязательного) аргумента arg (обычно
имеющего тип int ). В случае успешного завершения возвращаемое
значение естественным образом зависит от команды; при неудаче всегда
возвращается -1.
Допустимые команды (значения аргумента cmd ) определены в файле
<fcntl.h>. Перечислим сначала наиболее употребительные из них.
F_DUPFD
Дублирование дескриптора arg )
и ассоциировать его с тем же fildes.
F_GETFL
Вернуть флаги статуса и open() ). Для выделения режима доступа из возвращаемого
значения предоставляется маска O_ACCMODE.
F_SETFL
Установить флаги статуса файла в соответствии со значением третьего аргумента arg.
Приведем пример использования описанных команд функции fcntl(). Перенаправить стандартный freopen(),
см. выше листинг 5.5). Кроме того, в ней демонстрируется изменение
набора флагов статуса файла, ассоциированного с дескриптором.
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <assert.h>
#define LOGFILE "my_logfile"
int main (void) {
int fd;
int flags;
assert ((fd = open (LOGFILE, O_WRONLY | O_CREAT
| O_APPEND, S_IRWXU)) > 2);
printf ("До перенаправления стандартного вывода
в файл " LOGFILE "\n");
close (1);
assert (fcntl (fd, F_DUPFD, 1) == 1);
close (fd);
printf ("После перенаправления стандартного
вывода в файл " LOGFILE "\n");
/* Добавим флаг обеспечения целостности файла */
/* при записи */
assert ((flags = fcntl (1, F_GETFL, 0)) != -1);
assert (fcntl (1, F_SETFL, flags | O_SYNC) != -1);
fprintf (stderr, "До перенаправления стандартного
протокола на стандартный вывод\n");
close (2);
assert (fcntl (1, F_DUPFD, 2) == 2);
fprintf (stderr, "После перенаправления
стандартного протокола на стандартный вывод\n");
close (1);
close (2);
return (0);
}
Команды F_GETFD и F_SETFD функции fcntl() предназначены,
соответственно, для опроса и изменения флагов, ассоциированных с
заданным файловым дескриптором (а не с файлом). В стандарте упомянут
один такой флаг – FD_CLOEXEC, предписывающий закрывать
дескриптор при
Особый класс управляющих операций с файлами, имеющих свою
систему понятий, составляют блокировки, хотя и они оформляются как
команды функции fcntl().
Назначение механизма блокировок – дать возможность процессам
(
Блокировка связывается с сегментом (произвольным последовательным участком) файла. Различают блокировку на запись и на чтение. Блокировка на запись носит монопольный характер. Когда сегмент блокирован на запись, никакие другие процессы не имеют возможности заблокировать на чтение или запись этот же или пересекающийся с ним сегмент.
Блокировка на чтение используется для того, чтобы ограничить доступ к сегментам: если сегмент заблокирован на чтение, то другие процессы также могут заблокировать на чтение весь сегмент или его часть, однако никакие сегменты, заблокированные на запись, с ним не пересекаются.
Для установки блокировки на чтение требуется, чтобы файл был открыт как минимум на чтение. Соответственно, для блокировки на запись необходим доступ на запись.
Блокировка сегмента описывается структурой flock, которая,
согласно стандарту, должна содержать по крайней мере следующие поля:
short l_type /* Тип блокировки: */ /* F_RDLCK (на чтение) */ /* F_WRLCK (на запись), */ /* F_UNLCK (снятие блокировки) */ short l_whence/* Точка отсчета для смещения */ /* l_start: SEEK_SET, SEEK_CUR */ /* или SEEK_END */ off_t l_start /* Смещение в байтах начала */ /* блокируемого сегмента */ /* относительно точки отсчета */ off_t l_len /* Размер блокируемого сегмента. */ /* 0 означает блокировку до конца файла. */ pid_t l_pid /* Идентификатор процесса,*/ /* установившего блокировку; */ /* (возвращается по команде F_GETLK, */ /* см. далее) */
Отметим, что блокируемый сегмент может выходить за конец, но не
за начало файла. Длину сегмента разрешается задавать отрицательным
числом, если тип off_t допускает такую возможность; в таком случае
смещение начала блокируемого сегмента относительно точки отсчета
равно l_start+l_len, а смещение конца – l_start-1.
Приведем несколько примеров заполнения структуры flock (см.
листинг 5.25).
#include <fcntl.h>
struct flock lck;
. . .
lck.l_type = F_RDLCK;
/* Блокировка на чтение */
/* всего файла */
lck.l_whence = SEEK_SET;
lck.l_start = 0;
lck.l_len = 0;
Установка блокировки осуществляется управляющими командами F_SETLK и F_SETLKW функции fcntl() (см. листинг 5.26).
if (fcntl (fd, F_SETLK, lck) != -1) ... if (fcntl (fd, F_SETLKW, lck) != -1) ...
Если блокировка не может быть установлена, выполнение команды F_SETLK немедленно завершается, и возвращается -1. Команда F_SETLKW отличается только тем, что в аналогичной ситуации процесс
переходит в состояние ожидания до тех пор, пока нужный сегмент файла
не будет разблокирован.
Для снятия блокировки можно воспользоваться командами F_SETLK или F_SETLKW. Для этого значение поля l_type должно быть
установлено равным F_UNLCK.
Получить характеристики блокировки, мешающей установить новую,
позволяет управляющая команда F_GETLK (новая блокировка задается
структурой, на которую при обращении к fcntl() указывает третий аргумент arg ). Если запрашиваемую блокировку установить нельзя, информация о
первой помещается в ту же структуру; в частности, будет задано значение
поля l_pid – оно идентифицирует процесс, установивший блокировку.
(Естественно, значение поля l_whence будет установлено равным SEEK_SET.)
Если нет помех для создания нужной блокировки, полю l_type присваивается
значение F_UNLCK, а остальные поля в структуре не изменяются.
При закрытии файлового дескриптора все блокировки, установленные в файле текущим процессом, снимаются.
Приведем пример двух программ, первая из которых (назовем ее set_locks ) устанавливает блокировки нескольких сегментов файла и
засыпает на некоторое время (см. листинг 5.27), а вторая ( test_locks,
см. листинг 5.28), выполняющаяся, разумеется, параллельно, в рамках
другого процесса, получает и выводит информацию о заблокированных
сегментах того же файла (см. листинг 5.29).
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <assert.h>
#define LOCKFILE "my_lockfile"
/* Программа устанавливает несколько блокировок */
/* на файл LOCKFILE */
int main (void) {
int fd;
struct flock lck;
assert ((fd = open (LOCKFILE, O_RDWR | O_CREAT |
O_TRUNC, S_IRWXU)) != -1);
/* Установим блокировку на запись на весь файл */
lck.l_type = F_WRLCK;
lck.l_whence = SEEK_SET;
lck.l_start = (off_t) 0;
lck.l_len = (off_t) 0;
if (fcntl (fd, F_SETLK, lck) == -1) {
perror ("FCNTL-F_SETLK-1");
close (fd);
return (-1);
}
/* Сделаем размер файла ненулевым */
if (lseek (fd, (off_t) 1024, SEEK_SET) == -1) {
perror ("LSEEK");
close (fd);
return (-1);
}
if (write (fd, lck, sizeof (lck)) != sizeof (lck)) {
perror ("WRITE");
close (fd);
return (-1);
}
/* Снимем блокировку в середине файла */
lck.l_type = F_UNLCK;
lck.l_whence = SEEK_SET;
lck.l_start = (off_t) 512;
lck.l_len = (off_t) sizeof (lck);
if (fcntl (fd, F_SETLK, lck) == -1) {
perror ("FCNTL-F_SETLK-2");
close (fd);
return (-1);
}
/* Установим блокировку на чтение в конце файла */
lck.l_type = F_RDLCK;
lck.l_whence = SEEK_END;
lck.l_start = (off_t) -sizeof (lck);
lck.l_len = (off_t) sizeof (lck);
if (fcntl (fd, F_SETLK, lck) == -1) {
perror ("FCNTL-F_SETLK-2");
close (fd);
return (-1);
}
sleep (10);
return (close (fd));
}
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <assert.h>
#define LOCKFILE "my_lockfile"
/* Программа выявляет блокировки, установленные */
/* на файл LOCKFILE */
int main (void) {
int fd;
struct flock lck;
assert ((fd = open (LOCKFILE, O_WRONLY)) != -1);
(void) printf ("ид-р проц. тип начало длина\n");
/* Начнем с попытки установить блокировку на */
/* весь файл */
lck.l_whence = SEEK_SET;
lck.l_start = 0;
lck.l_len = 0;
do {
lck.l_type = F_WRLCK;
(void) fcntl (fd, F_GETLK, lck);
if (lck.l_type != F_UNLCK) {
(void) printf ("%9d %3c %7ld %5ld\n",
lck.l_pid,
(lck.l_type == F_WRLCK) ? 'W' : 'R',
lck.l_start, lck.l_len);
/* Если эта блокировка покрывает остаток файла, */
/* нет нужды выявлять другие блокировки */
if (lck.l_len == 0) break;
/* Иначе поищем новую блокировку после найденной */
lck.l_start += lck.l_len;
}
while (lck.l_type != F_UNLCK);
return (close (fd));
}
ид-р проц. тип начало длина 31174 W 0 512 31174 W 528 496 31174 R 1024 16 31174 W 1040 0
Отметим, что блокировка на чтение расщепила блокировку на запись, первоначально покрывавшую весь файл.
Читателю предлагается выполнить командную строку set_locks
set_locks и объяснить полученный результат.
Функции setbuf(), setvbuf( ) и fflush() выполняют
#include <stdio.h> void setbuf (FILE *restrict stream, char *restrict buf); #include <stdio.h> int setvbuf (FILE *restrict stream, char *restrict buf, int type, size_t size); #include <stdio.h> int fflush (FILE *stream);
Функция setvbuf(), которую можно использовать после type:
_IOFBF – полная буферизация;
_IOLBF – построчная буферизация;
_IONBF – отсутствие буферизации.
Функция setvbuf() сама резервирует буфера заданного размера,
но если аргумент buf не является пустым указателем, может использоваться
и пользовательский буфер. В случае нормального завершения
функция возвращает 0.
Функцию setbuf() можно считать частным случаем setvbuf().
С точностью до возвращаемого значения вызов setbuf (stream, NULL)
эквивалентен setvbuf (stream, NULL, _IONBF, BUFSIZ) (отмена буферизации);
если же значение buf не равно NULL, вызов setbuf (stream, buf)
сводится к setvbuf (stream, buf, _IOFBF, BUFSIZ)
(полная буферизация).
Функцию setbuf() чаще всего применяют для отмены
буферизации стандартного setbuf (stdout, NULL) и/или setbuf (stderr, NULL).
Использование функций setbuf() и setvbuf() требует известной
аккуратности. Типичная ошибка – указание в качестве аргумента buf
автоматического массива, определенного внутри блока, и продолжение
работы с
Функция fflush() выталкивает буфера, помещая в файл
предназначенные для записи, но еще не записанные данные. Если в качестве
аргумента stream задан пустой указатель, выталкиваются все буфера всех
Вызов fflush() – необходимый элемент завершения транзакций,
но он полезен и применительно к стандартному
char name [LINE_MAX];
(void) printf ("Введите Ваше имя: ");
(void) fflush (stdout); (void) fgets (name,
sizeof (name), stdin);
В стандарте POSIX-2001 предусмотрены две основные группы
функций, обслуживающие операции
<fcntl.h> );<stdio.h> ).Применение
FILE, с которой ассоциирован
соответствующий дескриптор
Существенны не только содержимое, но и адрес объекта типа FILE ;
копия подобного объекта не обязательно является корректным
представлением
И FILE с именами, соответственно, stdin, stdout и stderr.
При
Если файл поддерживает запросы на позиционирование (таковы обычные файлы в противоположность, например, символьным специальным, соответствующим терминалам), то после открытия индикатор текущей позиции устанавливается в начало (на нулевой байт) при условии, что файл не открывали на добавление; в этом случае от реализации зависит, будет ли индикатор указывать на начало или конец файла.
В дальнейшем индикатор текущей позиции смещается под воздействием операций чтения, записи и позиционирования, чтобы упростить последовательное продвижение по файлу.
Стандартом C99 [5] предусмотрены байтные и широкие символы.
Соответственно, в стандарте POSIX-2001 введено понятие ориентации
После завершения работы с файлом его необходимо закрыть. При
этом не только разрывается связь между файлами с одной стороны и
дескрипторами и
Стандарт POSIX-2001 предусматривает как синхронный, так и
асинхронный
С одним файлом могут параллельно работать несколько процессов,
выполняющихся на разных хостах (узлах сети) и использующих разные
буфера. Для корректного обслуживания подобной ситуации
Операция синхронизированного
Синхронизированное чтение с целостностью данных считается завершенным, когда образ данных успешно передан запрашивающему процессу. Если в момент запроса операции чтения оставались незавершенные операции записи, пересекающиеся с чтением по данным, сначала должны быть обслужены запросы на запись.
Синхронизированная запись с целостностью данных завершена, когда данные успешно переданы и, кроме того, модифицирована и передана в стабильную память информация о файловой системе, необходимая для последующей выборки данных (такая, например, как размер файла).
Синхронизированная операция с
Как уже указывалось, open() и pipe() (см.
листинг 5.1), а также функции
#include <fcntl.h> int open (const char *path, int oflag, ...); #include <unistd.h> int pipe (int fildes [2]);
#include <stdio.h>
FILE *fopen (const char *restrict path,
const char *restrict mode);
#include <stdio.h>
FILE *fdopen (int fildes,
const char *mode);
#include <stdio.h>
FILE *freopen (const char *restrict path, const
char *restrict mode, FILE *restrict stream);
Функция open() открывает файл с заданным маршрутным именем
(первый аргумент, path ), создавая для него описание – новое и,
следовательно, не разделяемое с другими процессами. Возвращаемый в качестве
результата -1 ).
Второй аргумент, oflag, устанавливает флаги статуса файла и
определяет допустимые виды операций
O_RDONLY
O_WRONLY
O_RDWR
Следующие флаги могут комбинироваться произвольным образом.
O_APPEND
Перед каждой записью устанавливать индикатор текущей позиции на конец файла.
O_CREAT
Если файл существует, данный флаг принимается во внимание
только при наличии описываемого далее флага O_EXCL. Если файла нет, он
создается от имени текущего пользователя. Обратим внимание, что
функция open() имеет переменное число аргументов. При создании файла
предполагается, что в качестве третьего аргумента типа mode_t задается
режим доступа.
O_EXCL
Если установлены флаги O_CREAT и O_EXCL, а файл существует
(хотя бы и в виде символьной ссылки на несуществующий файл), вызов open() завершится неудачей. Проверка существования файла и его
создание представляют собой атомарное действие по отношению к
попыткам других процессов (
O_TRUNC
Если файл существует, является обычным и успешно открывается с
флагами O_RDWR или O_WRONLY, он опустошается (размер устанавливается
равным нулю).
Отметим, что рассмотренная ранее функция creat (path, mode)
по определению эквивалентна вызову open (path, O_WRONLY |
O_CREAT | O_TRUNC, mode).
Следующий флаг относится к асинхронному
O_NONBLOCK
Обычно, если канал открывается только на чтение (или только на
запись), процесс ( O_NONBLOCK
открытие на чтение завершается без задержки, а открытие на запись
заканчивается неудачей, если канал еще не открыт кем-либо на чтение.
При открытии специального файла вызов open() завершается
только после того, как устройство оказывается в состоянии готовности.
Флаг O_NONBLOCK отменяет эту задержку, однако последующее поведе-
ние устройства зависит от реализации.
Следующая группа флагов обслуживает синхронизированный
O_DSYNC
Операции записи с возвращаемым файловым дескриптором должны завершаться с обеспечением целостности данных.
O_SYNC
Операции записи должны завершаться с обеспечением целостности файла.
O_RSYNC
Операции чтения должны завершаться на уровне целостности, заданном флагами O_DSYNC или O_SYNC.
Смысл последнего из стандартизованных флагов будет пояснен при рассмотрении процессов.
O_NOCTTY
Если открывается специальный файл, соответствующий терминалу,
последний не должен становиться управляющим терминалом процесса.
Для создания и открытия канала предназначена функция pipe(). В
массиве fildes она возвращает сразу два дескриптора: fildes [0] служит для чтения, fildes [1] – для записи. Данные читаются в том же
порядке, в каком были записаны.
При успешном завершении pipe() возвращает 0, при неудаче – -1.
Функция fopen() из группы open(), только вместо файлового дескриптора в качестве
результата возвращается указатель на объект, служащий для управления
сформированным NULL ).
Второй аргумент, mode, определяющий допустимые виды операций
"r"
"w"
Опустошить или создать файл, открыв его на запись.
"a"
Открыть или создать файл на запись в конец.
"r+"
"w+"
Опустошить или создать файл, открыв его на изменение.
"a+"
Открыть или создать файл на изменение с записью в конец.
(Стандарт языка C позволяет приписывать к перечисленным
цепочкам символ 'b', который, впрочем, ни на что не влияет.)
Если открытый файл не соответствует интерактивному устройству,
ассоциированный с ним
Функция fdopen() формирует
Второй аргумент, mode, может принимать те же значения, что и для fopen(), но их трактовка по понятным причинам отличается:
существующие файлы не опустошаются, а новые не создаются.
Функция freopen() предназначена для ассоциирования
существующего stream ) с заданным файлом (первый
аргумент, path ) и разрешенными видами операций mode ).
В первую очередь freopen() пытается вытолкнуть буфера stream и закрыть ассоциированный с ним файл. Неудача данного
действия ни на что не влияет.
Затем, аналогично fopen(), открывается заданный файл, только
без формирования нового stream (лишенный,
правда, ориентации) или NULL (в случае неудачи).
Если в качестве первого аргумента функции freopen() задан
пустой указатель, делается попытка изменить виды операций, разрешенные
для stream. (Формально можно считать, что первым аргументом freopen() служит имя файла, ассоциированного с этим
Для закрытия файлов (точнее, файловых дескрипторов или close() и fclose() (см. листинг 5.3).
#include <unistd.h> int close (int fildes); #include <stdio.h> int fclose (FILE *stream);
Функция close() освобождает файловый дескриптор fildes,
который становится доступным для последующего использования при
Когда закрывается последний дескриптор, ссылающийся на
Если число жестких ссылок на файл равно нулю и закрывается последний ассоциированный с ним дескриптор, файл перестает быть доступным, а занимавшееся им пространство освобождается.
Когда закрывается последний дескриптор, ассоциированный с каналом, все оставшиеся непрочитанными данные теряются.
Функция close() возвращает 0 в случае успешного завершения и -1 при неудаче.
Функция fclose() по сути аналогична, только она освобождает 0, признаком неудачи – константа EOF.
Приведем примеры использования описанных функций. Сочетание
флагов O_CREAT и O_EXCL функции open() позволяет организовать
проверку и создание файлов-замков, для которых важен факт
существования в одном экземпляре, а не содержимое (см. листинг 5.4).
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#define LOCK_FILE "my_lock"
/* Функция пытается создать файл-замок */
/* Результат равен 0 в случае успеха, */
/* 1, если файл уже существует, */
/* -1 в случае прочих ошибок */
static int gate (const char *lock_name) {
int fd;
if ((fd = open (lock_name, O_WRONLY | O_CREAT |
O_EXCL, (mode_t) 0)) < 0) {
if (errno == EEXIST) {
return (1);
}
return (-1);
}
return (close (fd));
}
int main (void) {
int res;
if ((res = gate (LOCK_FILE)) > 0) {
perror ("Ошибка при создании файла-замка "
LOCK_FILE);
} else if (res == 1) {
fprintf (stderr, "Файл-замок " LOCK_FILE "
уже существует\n");
}
return (res);
}
Читателю предлагается выполнить приведенную программу дважды.
Следующая программа иллюстрирует перенаправление стандартного
#include <stdio.h>
#define LOGFILE "my_logfile"
int main (void) {
FILE *fp;
printf ("До перенаправления стандартного вывода
в файл " LOGFILE "\n");
if ((fp = freopen (LOGFILE, "a", stdout)) ==
NULL) {
perror ("Не удалось перенаправить стандартный
вывод в файл " LOGFILE);
return (-1);
}
printf ("После перенаправления стандартного
вывода в файл " LOGFILE "\n");
if (fclose (fp) == EOF) {
perror ("Не удалось закрыть файл " LOGFILE);
return (-1);
}
printf ("После закрытия файла " LOGFILE "\n");
return (0);
}
Весьма полезной с практической точки зрения является функция
создания и открытия временных файлов tmpfile() (см. листинг 5.6).
#include <stdio.h> FILE *tmpfile (void);
Временный файл открывается на изменение ( w+ ) и автоматически
удаляется после закрытия всех ссылок на него.
Использование функции tmpfile() предпочтительнее генерации
"временного" имени с помощью функции tmpnam() и последующего
создания файла с этим именем, поскольку в промежутке какой-либо другой
процесс может создать одноименный файл.
Чтение данных из файла выполняют функции read() и
(см. листинг 5.7).
#include <unistd.h> ssize_t read (int fd, void *buf, size_t nbyte); #include <stdio.h> size_t fread (void *restrict buf, size_t size, size_t nitems, FILE *restrict stream);
Функция read() пытается прочитать nbyte байт из файла,
ассоциированного с дескриптором fd, и поместить их в буфер buf.
Для файлов, допускающих позиционирование, read() выполняет
чтение, начиная со значения индикатора текущей позиции,
ассоциированного с дескриптором fd. После завершения операции этот индикатор
увеличивается на количество прочитанных байт. Для устройств, не
поддерживающих позиционирования (таких, например, как терминал),
значение упомянутого индикатора не определено, а чтение выполняется с
текущей позиции устройства.
При успешном завершении read() возвращает количество байт,
реально прочитанных и помещенных в буфер; это значение может оказаться
меньше значения аргумента nbyte, если до конца файла оставалось
меньше, чем nbyte байт. Например, если текущая позиция совпадала с
концом файла, результат будет равен 0. В случае ошибки возвращается -1.
Функция во многом
аналогична read(), но число читаемых байт задается как произведение
размера одного элемента (аргумент size ) на число элементов (аргумент nitems ), а результатом служит количество успешно прочитанных
элементов. В стандарте оговаривается, что элементы читаются побайтно.
Число элементов, успешно прочитанных функцией ,
может быть меньше затребованного, только если достигнут конец
файла или произошла ошибка чтения. В таком случае
устанавливает для feof() и ferror(), соответственно
(см. листинг 5.8), возвращая при установленном индикаторе
ненулевой результат.
#include <stdio.h> int feof (FILE *stream); #include <stdio.h> int ferror (FILE *stream);
Отметим, что использование функции бинарного
ограничивает мобильность приложений, так как результат зависит от размера
элементов и порядка байт, поддерживаемого процессором.
Обратим также внимание на некоторые нюансы синхронного и
асинхронного read(). При попытке чтения
из пустого канала, не открытого кем-либо на запись, результат равен 0
(как O_NONBLOCK возвращается -1 (как признак
ошибки EAGAIN ); при отсутствии флага O_NONBLOCK процесс (
Содержимое символьных ссылок приходится читать особым
образом (хотя бы потому, что обычно функция open() раскрывает их, т. е.
открывает указуемый файл). Для этого служит функция readlink() (см.
листинг 5.9). Она помещает содержимое ссылки с именем link_name в
буфер buf длины buf_size (если буфер мал, остаток содержимого
отбрасывается). Результат равен числу помещенных в буфер байт или -1 в
случае неудачи.
#include <unistd.h>
ssize_t readlink (const char *restrict
link_name, char *restrict buf,
size_t buf_size);
Следующая программа (см. листинг 5.10) переправляет недлинные
сообщения с
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#define C_TERM "/dev/tty"
int main (void) {
char buf [BUFSIZ];
int fd;
ssize_t line_len;
/* Открытие на чтение специального файла, */
/* ассоциированного с управляющим терминалом */
if ((fd = open (C_TERM, O_RDONLY)) < 0) {
perror ("OPEN");
return (-1);
}
/* Ввод с терминала */
printf ("Вводите строки\n");
while ((line_len = read (fd, buf, BUFSIZ - 1))
> 0) {
buf [line_len] = '\0';
printf ("Вы ввели: %s", buf);
}
if (line_len == -1) {
perror ("READ");
close (fd);
return (-1);
}
return (close (fd));
}
В качестве примера мобильного использования функции ,
а также функций feof() и ferror(), рассмотрим программу,
подсчитывающую число символов, слов и строк в файле – аргументе командной
строки (см. листинг 5.11).
/* * * * * * * * * * * * * * * * * * * * * */
/* Подсчет символов, слов и строк в файле */
/* * * * * * * * * * * * * * * * * * * * * */
#include <stdio.h>
int main (int argc, char *argv[]) {
long nwords = 0; /* Счетчик слов */
long nlines = 0; /* Счетчик строк */
long nchars = 0; /* Счетчик символов */
FILE *fp = stdin; /* Если файл не задан, */
/* читается стандартный ввод */
unsigned char buf [BUFSIZ]; /* Буфер для */
/* чтения файла */
unsigned char *p1; /* Указатель на */
/* обрабатываемую часть буфера */
size_t nbytes = 0; /* Количество прочитанных,*/
/* но не обработанных байт */
register int c; /* Обрабатываемый символ */
int inword = 0; /* Признак - находимся ли мы */
/* внутри слова */
if (argc > 2) {
fprintf (stderr, "Использование: %s [файл]\n",
argv [0]);
return (2);
}
if (argc > 1 (fp = fopen (argv [1], "r")) ==
NULL) {
perror ("OPEN");
fprintf (stderr, "%s: Не могу открыть файл
%s\n", argv [0], argv[1]);
return (2);
}
p1 = buf;
for (;;) { /* Посимвольная обработка файла */
if (nbytes == 0) { /* Нужно прочитать новую порцию */
if (feof (fp)) {
/* При предыдущем чтении дошли до конца файла */
break;
}
nbytes = fread (p1 = buf, (size_t) 1,
sizeof (buf), fp);
if (ferror (fp)) {
perror ("READ");
fprintf (stderr, "%s: Ошибка чтения из
файла %s:", argv [0],
argc == 1 ? "стандартного ввода" : argv
[1]);
break;
}
if (nbytes == 0) {
/* В файле не оставалось непрочитанных символов */
break;
}
nchars += nbytes;
}
c = *p1++; /* Обработка одного символа */
nbytes--;
if (c > ' ') {
if (!inword) {
nwords++;
inword = 1;
}
continue;
}
if (c == '\n') {
nlines++;
} else if (c != ' ' c != '\t') {
continue;
}
inword = 0;
}
if (argc > 1) {
fclose (fp);
}
printf ("Файл %s: строк: %ld, слов: %ld,
символов: %ld\n",
argc == 1 ? "стандартного ввода" : argv [1],
nlines, nwords, nchars);
return (0);
}
Читателю предлагается убрать из цикла проверку feof ( fp ) и
оценить, как изменится обработка интерактивного стандартного
Для иллюстрации использования функции readlink() напишем
программу, выдающую на стандартный
#include <unistd.h>
#include <stdio.h>
/* Программа выдает на стандартный вывод */
/* содержимое символьных ссылок - */
/* аргументов командной строки */
int main (int argc, char *argv[]) {
char buf [BUFSIZ];
ssize_t link_len;
int err = 0;
int i;
for (i = 1; i < argc; i++) {
if ((link_len = readlink (argv [i], buf,
sizeof (buf) - 1)) < 0) {
perror ("READLINK");
fprintf (stderr, "%s: Не удалось прочитать содержимое символьной ссылки %s\n", argv [0], argv [i]);
err = -1;
continue;
}
buf [link_len] = '\0';
printf ("Содержимое символьной ссылки %s -> %s\n", argv [i], buf);
}
return (err);
}
write() и fwrite()
(см. листинг 5.13).
#include <unistd.h>
ssize_t write (int fildes, const void *buf,
size_t nbyte);
#include <stdio.h>
size_t fwrite (const void *restrict buf,
size_t size, size_t nitems,
FILE *restrict stream);
Вероятно, лишь несколько моментов, связанных с функциями write() и fwrite(), нуждаются в пояснениях. При записи размер файла
может увеличиться. Если
При записи в канал, если флаг O_NONBLOCK не установлен, процесс
( write() вернет nbyte. При установленном флаге O_NONBLOCK
поведение зависит от значения nbyte и наличия свободного места в канале.
Если nbyte не превосходит константы PIPE_BUF, запишется все или ничего
(в последнем случае результат будет равен -1 ). При попытке записать
порцию данных большего размера запишется сколько можно или ничего.
Приведем несколько примеров. Следующая программа (см. листинг 5.14)
выводит приветствие на
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#define C_TERM "/dev/tty"
char msg [] = "HELLO !!!\n";
int main (void) {
int fd;
/* Открытие на запись специального файла,
ассоциированного с управляющим терминалом */
if ((fd = open (C_TERM, O_WRONLY)) < 0)
{
perror ("OPEN");
return (-1);
}
/* Вывод на терминал */
if (write (fd, msg, sizeof (msg)) != (ssize_t)
sizeof (msg)) {
perror ("WRITE");
return (1);
}
return (close (fd));
}
Программа prnmyself (см. листинг 5.15) выводит свой собственный
исходный текст. При этом применяется следующий прием: данные
фиксированными порциями читаются из файла и выводятся на терминал;
процесс повторяется до тех пор, пока число реально прочитанных байт
совпадает с указанным (до обнаружения конца файла).
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#define SOURCE_FILE "prnmyself.c"
#define C_TERM "/dev/tty"
int main (void) {
unsigned char buf [BUFSIZ];
int fdr, fdw; /* Дескрипторы для чтения и записи */
ssize_t nb;
if (((fdr = open (SOURCE_FILE, O_RDONLY)) < 0) ||
((fdw = open (C_TERM, O_WRONLY)) < 0)) {
perror ("OPEN " SOURCE_FILE " or " C_TERM);
return (1);
}
do {
if ((nb = read (fdr, buf, BUFSIZ)) < 0) {
perror ("READ");
break;
}
if (write (fdw, buf, nb) != nb) {
perror ("WRITE");
break;
}
} while (nb == BUFSIZ);
(void) close (fdw);
(void) close (fdr);
return (0);
}
Для fgetc()
и fputc(), строки рекомендуется вводить, вызывая функцию fgets(),
а выводить с помощью функций fputs() и puts() (см. листинг 5.16).
#include <stdio.h> int fgetc (FILE *stream); #include <stdio.h> int fputc (int c, FILE *stream); #include <stdio.h> char *fgets (char *restrict s, int n, FILE *restrict stream); #include <stdio.h> int fputs (const char *restrict s, FILE *restrict stream); #include <stdio.h> int puts (const char *s);
Описание аналогичных функций для широких символов приведено в листинге 5.17.
#include <stdio.h> #include <wchar.h> wint_t fgetwc (FILE *stream); #include <stdio.h> #include <wchar.h> wint_t fputwc (wchar_t wc, FILE *stream); #include <stdio.h> #include <wchar.h> wchar_t *fgetws (wchar_t *restrict ws, int n, FILE *restrict stream); #include <stdio.h> #include <wchar.h> int fputws (const wchar_t *restrict ws, FILE *restrict stream);
Функция fgetc() пытается прочитать из заданного unsigned char в int и вернуть в качестве
результата. В случае ошибки или при достижении конца файла
возвращается константа EOF.
Функция fgets() читает из заданного s ( n - 1 ) байт или строку, включая символ перевода строки, или
байты, оставшиеся до конца файла, если длина строки или число байт до
конца меньше ( n - 1 ). После прочитанных добавляется нулевой байт.
При нормальном завершении fgets() возвращает s. В случае
ошибки или при достижении конца файла возвращается пустой указатель.
Функция fputc() помещает в c, предварительно
преобразовав его к типу unsigned char. Результатом служит c или EOF.
Функция fputs() выводит в EOF. Функция puts() делает то же для stdout, завершая
Функции для работы с int на wint_t, char на wchar_t и EOF на WEOF.
Программа, показанная в листинге 5.18, иллюстрирует использование
функций fgets() и fputs(). Читателю предлагается сравнить ее с
листингом 5.10.
#include <stdio.h>
#include <limits.h>
/* Программа копирует строки со стандартного ввода на стандартный вывод */
int main (void) {
char line [LINE_MAX];
fputs ("Вводите строки\n", stdout);
while (fgets (line, sizeof (line), stdin) != NULL) {
if ((fputs ("Вы ввели: ", stdout) == EOF) || (fputs (line, stdout) == EOF)) {
break;
}
}
return (ferror (stdin) || ferror (stdout));
}
Использование функций fgetc() и fputc() иллюстрируется программой,
написанной С.В. Самборским (см. листинг 5.19). Она
выполняет раскодировку файлов формата base64, применяемого, например, в
электронной почте.
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <endian.h>
FILE *input=NULL, *output=NULL;
const char str []
="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
unsigned int Prm [256]; /* Таблица перекодировки */
const int WHITE = 100, ERR = 101, END = 102;
static void usage (char argv0 []) {
fprintf (stderr,"Программа раскодирует файлы
формата base64\n");
fprintf (stderr,"Использование:\n%s входной_файл
выходной_файл\n", argv0);
fprintf (stderr,"Файл должен начинаться с
первого символа в кодировке base64.\n");
}
int main (int argc, char *argv []) {
int n;
union {
unsigned long l;
char c[4];
} a;
{
int i;
for (i = 0; i < 256; i++) Prm [i] = ERR;
Prm [' '] = WHITE; Prm ['\t'] = WHITE;
Prm ['\n'] = WHITE; Prm ['\r'] = WHITE;
Prm ['='] = END;
for (i = 0; i < 64; i++) Prm [(int) str [i]] = i;
}
if (argc != 3) {
usage (argv [0]);
return (1);
}
assert (NULL != (input = fopen (argv [1], "r")));
assert (NULL != (output = fopen (argv [2], "w")));
for (a.l = 0, n = 0; ; ) { /* Цикл обработки
входного файла */
int c, b, shift;
assert (EOF != (c = fgetc (input)));
b = Prm [c];
if (WHITE == b) continue;
if (END == b) break;
if (ERR == b) {
fprintf (stderr,"Символ номер %d: %d не
входит в кодировку base64\n", n, c);
return (1);
}
n++;
assert (b < 64);
shift = 6 * (4 - n % 4);
if (shift != 24) b = b << shift;
a.l += b;
if (0 == n % 4) {
#if __BYTE_ORDER == __BIG_ENDIAN
fputc (a.c[1], output); fputc (a.c[2], output); fputc (a.c[3], output);
#elif __BYTE_ORDER == __LITTLE_ENDIAN
fputc (a.c[2], output); fputc (a.c[1],
output); fputc (a.c[0], output);
#elif __BYTE_ORDER == __PDP_ENDIAN
fputc (a.c[0], output); fputc (a.c[3],
output); fputc (a.c[2], output);
#else
#error "Unknown endian"
#endif
a.l = 0;
}
}
{ /* Обработка остатка входного файла */
int tl = (((n - 1) % 4) * 6 + 7) / 8;
if (tl == 3) {
#if __BYTE_ORDER == __BIG_ENDIAN
fputc (a.c[1], output); fputc (a.c[2], output); fputc (a.c[3], output);
#elif __BYTE_ORDER == __LITTLE_ENDIAN
fputc (a.c[2], output); fputc (a.c[1], output);
fputc (a.c[0], output);
#elif __BYTE_ORDER == __PDP_ENDIAN
fputc (a.c[0], output); fputc (a.c[3], output); fputc (a.c[2], output);
#else
#error "Unknown endian"
#endif
}
if (tl == 2) {
#if __BYTE_ORDER == __BIG_ENDIAN
fputc (a.c[1], output); fputc (a.c[2],
output);
#elif __BYTE_ORDER == __LITTLE_ENDIAN
fputc (a.c[2], output); fputc (a.c[1],
output);
#elif __BYTE_ORDER == __PDP_ENDIAN
fputc (a.c[0], output); fputc (a.c[3],
output);
#else
#error "Unknown endian"
#endif
}
if (tl == 1) {
#if __BYTE_ORDER == __BIG_ENDIAN
fputc (a.c[1], output);
#elif __BYTE_ORDER == __LITTLE_ENDIAN
fputc (a.c[2], output);
#elif __BYTE_ORDER == __PDP_ENDIAN
fputc (a.c[0], output);
#else
#error "Unknown endian"
#endif
}
}
fclose (input);
fclose (output);
return (0);
}
Приведенный пример показывает, что написание мобильных
программ даже для сравнительно простых задач требует заметных усилий.
В данном случае пришлось воспользоваться нестандартной
возможностью – включаемым файлом <, содержащим
препроцессорные константы, которые описывают
Отметим также стиль обработки ошибочных ситуаций, основанный
на применении макроса assert. Для авторов программ такой стиль,
безусловно, упрощает жизнь, исходный текст получается более
компактным, однако для пользователей сообщение вида
decode64: decode64.c:39: main: Assertion `((void *)0) != (input = fopen (argv [1], "r"))' failed.
может оказаться менее информативным, чем выдача функции perror().
К числу управляющих операций с файлами мы отнесем прежде
всего позиционирование. Индикатор текущей позиции может быть опрошен
или передвинут при помощи lseek(), а также
функций fseek(), ftell(), ftello(), fgetpos(), fsetpos(), (см. листинг 5.20).
#include <unistd.h>
off_t lseek (int fildes, off_t offset,
int whence);
#include <stdio.h>
int fseek (FILE *stream, long offset,
int whence);
long ftell (FILE *stream);
off_t ftello (FILE *stream);
int fgetpos (FILE *restrict stream,
fpos_t *restrict pos);
int fsetpos (FILE *stream, const fpos_t *pos);
void rewind (FILE *stream);
Функция lseek() устанавливает индикатор текущей позиции
следующим образом. Сначала, в зависимости от значения третьего
аргумента, whence, выбирается точка отсчета: 0, если это значение равно SEEK_SET, текущая позиция для SEEK_CUR и размер файла для SEEK_END. Затем к точке отсчета прибавляется смещение offset (второй аргумент).
Индикатор текущей позиции можно сместить за конец файла (причем его размер не изменится). Если с этой позиции будет произведена запись, в файле образуется дыра, чтение из которой выдает нулевые байты.
Результатом функции lseek() служит новое значение индикатора
текущей позиции, отсчитанное в байтах от начала файла. В случае
ошибки возвращается ( off_t ) ( -1 ), а текущая позиция остается
прежней.
Функция fseek() по сути аналогична, только в случае нормального
завершения возвращается 0. Кроме того, если whence должно равняться SEEK_SET, а
значение offset – нулю или результату вызова функции ftell() для
того же
Функция ftell() возвращает значение индикатора текущей
позиции для заданного (long) (-1) ). Функция ftello() эквивалентна ftell() с точностью до типа результата; в
новых приложениях рекомендуется использовать ftello(), так как эта
функция применима к большим файлам.
Функции fgetpos() и fsetpos() являются парными. Первая из
них заполняет структуру, на которую указывает аргумент pos, а вторая
применяет ее для установки индикатора текущей позиции. Нормальным
результатом служит 0.
Функция , если пренебречь некоторыми тонкостями,
сводится к вызову
(void) fseek (stream, 0L, SEEK_SET).
Приведем несколько примеров. В листинге 5.21 показана установка индикатора текущей позиции в начало и конец файла, а также его (индикатора) приращение.
(void) lseek (fildes, (off_t) 0, SEEK_SET); (void) lseek (fildes, (off_t) 0, SEEK_END); (void) lseek (fildes, inc, SEEK_CUR);
Применение функций
/* * * * * * * * * * * * * * * * * * * * * * * */
/* Набор функций для занесения текстов в файл, */
/* который составляется из двух частей: */
/* таблицы длин и смещений текстов от начала */
/* файла собственно текстов */
/* */
/* В начало файла помещается специальный */
/* элемент таблицы с магическим числом и общим */
/* количеством текстов */
/* * * * * * * * * * * * * * * * * * * * * * * */
#include <stdio.h>
#include <string.h>
/* Магическое число файла с текстами */
#define G_TXT_MAGIC 0x1993
/* Элемент таблицы длин и смещений */
typedef struct {
unsigned int l_txt; /* Длина текста (без */
/* (нулевого байта) */
unsigned long off_txt; /* Смещение текста от */
/* начала файла */
} txt_table_elem;
static FILE *fp = NULL;/* Указатель на поток */
/* файла с текстами */
static unsigned long max_n_txt; /* Общее число */
/* текстов в файле */
/* * * * * * * * * * * * * * * * * * * * * * * */
/* Функция для инициализации набора. */
/* n_txts - максимальное число добавляемых текстов */
/* * * * * * * * * * * * * * * * * * * * * * * */
int g_init_add_txt (const int argc, char *argv [],
const unsigned long n_txts) {
char *path; /* Имя файла, куда нужно поместить
тексты */
int magic; /* Магическое число файла с текстами */
txt_table_elem tte;
unsigned int i;
if (argc != 2) {
fprintf (stderr, "Использование: %s
файл_для_текстов\n", argv [0]);
return (-1);
}
path = argv [1];
/* Аккуратно откроем файл с текстами */
/* Если он уже есть и в нем правильное */
/* магическое число, */
/* будем добавлять тексты. */
/* В противном случае создадим и */
/* инициализируем файл */
if (((fp = fopen (path, "r+")) != NULL) (fread (magic, sizeof (unsigned int), 1, fp) == 1) (magic == G_TXT_MAGIC)) {
/* Перед нами - наш файл */
/* Проверим, не превышает ли заказанная */
/* верхняя граница существующую */
if (fread (max_n_txt, sizeof (unsigned long), 1, fp) != 1) {
fprintf (stderr, "Не удается прочитать
информацию из файла %s\n", path);
return (-1);
}
if (n_txts > max_n_txt) {
fprintf (stderr, "***** Новая верхняя
граница числа сообщений %lu больше
существующей %lu\n", n_txts, max_n_txt);
}
} else {
/* Файла нет или он не наш */
(void) fclose (fp);
if ((fp = fopen (path, "w+")) == NULL) {
fprintf (stderr, "Не удается открыть файл
%s\n", path);
return (-1);
}
tte.l_txt = magic = G_TXT_MAGIC;
tte.off_txt = max_n_txt = n_txts;
if (fwrite (tte, sizeof (txt_table_elem), 1, fp) != 1) {
fprintf (stderr, "Не удается записать
информацию в файл %s\n", path);
return (-1);
}
/* Пропишем нулями индексную таблицу */
/* Заодно конец файла окажется в будущем */
/* начале текстов */
tte.l_txt = 0;
tte.off_txt = 0;
for (i = 0; i < max_n_txt; i++) {
if (fwrite (tte, sizeof (txt_table_elem), 1, fp) != 1) {
fprintf (stderr, "Не удается записать информацию
в файл %s\n", path);
return (-1);
}
}
} /* if - существует ли файл с текстами */
return 0;
}
/* * * * * * * * * * * * * * * * * * * * * * * */
/* Функция для добавления одного текста */
/* * * * * * * * * * * * * * * * * * * * * * * */
int g_add_txt (const unsigned long n_t, const char *txt) {
unsigned int l; /* Длина текста txt */
txt_table_elem tte;
if (n_t >= max_n_txt) {
fprintf (stderr, "Номер текста: %lu должен
быть меньше: %lu\n", n_t, max_n_txt);
return (-1);
}
l = strlen (txt);
tte.l_txt = l;
if (fseek (fp, 0L, SEEK_END)) {
fprintf (stderr, "Ошибка позиционирования при
добавлении текста номер %lu\n", n_t);
return (-1);
}
tte.off_txt = ftell (fp);
if (fseek (fp, (n_t + 1) * sizeof (txt_table_elem), SEEK_SET)) {
fprintf (stderr, "Ошибка позиционирования при
добавлении текста номер %lu\n", n_t);
return (-1);
}
if (fwrite (tte, sizeof (tte), 1, fp) != 1) {
fprintf (stderr, "Ошибка записи при добавлении
текста номер %lu\n", n_t);
return (-1);
}
if (fseek (fp, tte.off_txt, SEEK_SET)) {
fprintf (stderr, "Ошибка позиционирования при
добавлении текста номер %lu\n", n_t);
return (-1);
}
if (fwrite (txt, sizeof (char), l, fp) != l) {
fprintf (stderr, "Ошибка записи при добавлении
текста номер %lu\n", n_t);
return (-1);
}
return 0;
}
/* * * * * * * * * * * * * * * * * * * * * * * */
/* Функция для завершения добавления текстов */
/* * * * * * * * * * * * * * * * * * * * * * * */
int g_term_add_txt () {
return (fclose (fp));
}
/* * * * * * * * * * * * * * * * * * * * * * * */
/* Главная программа, вызывающая определенные */
/* выше функции */
/* * * * * * * * * * * * * * * * * * * * * * * */
#define MAX_TXTS 10240
int main (int argc, char *argv[]) {
if (g_init_add_txt (argc, argv, MAX_TXTS) ||
g_add_txt (0, "Reference to section number %d in %s\n") ||
g_add_txt (1, "Data .init section in %s\n")) {
(void) g_term_add_txt ();
return (-1);
}
return (g_term_add_txt ());
}
Функция fcntl() предназначена для выполнения разнообразных
управляющих операций над
#include <fcntl.h> int fcntl (int fildes, int cmd, ...);
Аргумент fildes задает дескриптор cmd –
управляющую команду, дополнительные данные для которой могут быть
переданы в качестве третьего (необязательного) аргумента arg (обычно
имеющего тип int ). В случае успешного завершения возвращаемое
значение естественным образом зависит от команды; при неудаче всегда
возвращается -1.
Допустимые команды (значения аргумента cmd ) определены в файле
<fcntl.h>. Перечислим сначала наиболее употребительные из них.
F_DUPFD
Дублирование дескриптора arg )
и ассоциировать его с тем же fildes.
F_GETFL
Вернуть флаги статуса и open() ). Для выделения режима доступа из возвращаемого
значения предоставляется маска O_ACCMODE.
F_SETFL
Установить флаги статуса файла в соответствии со значением третьего аргумента arg.
Приведем пример использования описанных команд функции fcntl(). Перенаправить стандартный freopen(),
см. выше листинг 5.5). Кроме того, в ней демонстрируется изменение
набора флагов статуса файла, ассоциированного с дескриптором.
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <assert.h>
#define LOGFILE "my_logfile"
int main (void) {
int fd;
int flags;
assert ((fd = open (LOGFILE, O_WRONLY | O_CREAT
| O_APPEND, S_IRWXU)) > 2);
printf ("До перенаправления стандартного вывода
в файл " LOGFILE "\n");
close (1);
assert (fcntl (fd, F_DUPFD, 1) == 1);
close (fd);
printf ("После перенаправления стандартного
вывода в файл " LOGFILE "\n");
/* Добавим флаг обеспечения целостности файла */
/* при записи */
assert ((flags = fcntl (1, F_GETFL, 0)) != -1);
assert (fcntl (1, F_SETFL, flags | O_SYNC) != -1);
fprintf (stderr, "До перенаправления стандартного
протокола на стандартный вывод\n");
close (2);
assert (fcntl (1, F_DUPFD, 2) == 2);
fprintf (stderr, "После перенаправления
стандартного протокола на стандартный вывод\n");
close (1);
close (2);
return (0);
}
Команды F_GETFD и F_SETFD функции fcntl() предназначены,
соответственно, для опроса и изменения флагов, ассоциированных с
заданным файловым дескриптором (а не с файлом). В стандарте упомянут
один такой флаг – FD_CLOEXEC, предписывающий закрывать
дескриптор при
Особый класс управляющих операций с файлами, имеющих свою
систему понятий, составляют блокировки, хотя и они оформляются как
команды функции fcntl().
Назначение механизма блокировок – дать возможность процессам
(
Блокировка связывается с сегментом (произвольным последовательным участком) файла. Различают блокировку на запись и на чтение. Блокировка на запись носит монопольный характер. Когда сегмент блокирован на запись, никакие другие процессы не имеют возможности заблокировать на чтение или запись этот же или пересекающийся с ним сегмент.
Блокировка на чтение используется для того, чтобы ограничить доступ к сегментам: если сегмент заблокирован на чтение, то другие процессы также могут заблокировать на чтение весь сегмент или его часть, однако никакие сегменты, заблокированные на запись, с ним не пересекаются.
Для установки блокировки на чтение требуется, чтобы файл был открыт как минимум на чтение. Соответственно, для блокировки на запись необходим доступ на запись.
Блокировка сегмента описывается структурой flock, которая,
согласно стандарту, должна содержать по крайней мере следующие поля:
short l_type /* Тип блокировки: */ /* F_RDLCK (на чтение) */ /* F_WRLCK (на запись), */ /* F_UNLCK (снятие блокировки) */ short l_whence/* Точка отсчета для смещения */ /* l_start: SEEK_SET, SEEK_CUR */ /* или SEEK_END */ off_t l_start /* Смещение в байтах начала */ /* блокируемого сегмента */ /* относительно точки отсчета */ off_t l_len /* Размер блокируемого сегмента. */ /* 0 означает блокировку до конца файла. */ pid_t l_pid /* Идентификатор процесса,*/ /* установившего блокировку; */ /* (возвращается по команде F_GETLK, */ /* см. далее) */
Отметим, что блокируемый сегмент может выходить за конец, но не
за начало файла. Длину сегмента разрешается задавать отрицательным
числом, если тип off_t допускает такую возможность; в таком случае
смещение начала блокируемого сегмента относительно точки отсчета
равно l_start+l_len, а смещение конца – l_start-1.
Приведем несколько примеров заполнения структуры flock (см.
листинг 5.25).
#include <fcntl.h>
struct flock lck;
. . .
lck.l_type = F_RDLCK;
/* Блокировка на чтение */
/* всего файла */
lck.l_whence = SEEK_SET;
lck.l_start = 0;
lck.l_len = 0;
Установка блокировки осуществляется управляющими командами F_SETLK и F_SETLKW функции fcntl() (см. листинг 5.26).
if (fcntl (fd, F_SETLK, lck) != -1) ... if (fcntl (fd, F_SETLKW, lck) != -1) ...
Если блокировка не может быть установлена, выполнение команды F_SETLK немедленно завершается, и возвращается -1. Команда F_SETLKW отличается только тем, что в аналогичной ситуации процесс
переходит в состояние ожидания до тех пор, пока нужный сегмент файла
не будет разблокирован.
Для снятия блокировки можно воспользоваться командами F_SETLK или F_SETLKW. Для этого значение поля l_type должно быть
установлено равным F_UNLCK.
Получить характеристики блокировки, мешающей установить новую,
позволяет управляющая команда F_GETLK (новая блокировка задается
структурой, на которую при обращении к fcntl() указывает третий аргумент arg ). Если запрашиваемую блокировку установить нельзя, информация о
первой помещается в ту же структуру; в частности, будет задано значение
поля l_pid – оно идентифицирует процесс, установивший блокировку.
(Естественно, значение поля l_whence будет установлено равным SEEK_SET.)
Если нет помех для создания нужной блокировки, полю l_type присваивается
значение F_UNLCK, а остальные поля в структуре не изменяются.
При закрытии файлового дескриптора все блокировки, установленные в файле текущим процессом, снимаются.
Приведем пример двух программ, первая из которых (назовем ее set_locks ) устанавливает блокировки нескольких сегментов файла и
засыпает на некоторое время (см. листинг 5.27), а вторая ( test_locks,
см. листинг 5.28), выполняющаяся, разумеется, параллельно, в рамках
другого процесса, получает и выводит информацию о заблокированных
сегментах того же файла (см. листинг 5.29).
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <assert.h>
#define LOCKFILE "my_lockfile"
/* Программа устанавливает несколько блокировок */
/* на файл LOCKFILE */
int main (void) {
int fd;
struct flock lck;
assert ((fd = open (LOCKFILE, O_RDWR | O_CREAT |
O_TRUNC, S_IRWXU)) != -1);
/* Установим блокировку на запись на весь файл */
lck.l_type = F_WRLCK;
lck.l_whence = SEEK_SET;
lck.l_start = (off_t) 0;
lck.l_len = (off_t) 0;
if (fcntl (fd, F_SETLK, lck) == -1) {
perror ("FCNTL-F_SETLK-1");
close (fd);
return (-1);
}
/* Сделаем размер файла ненулевым */
if (lseek (fd, (off_t) 1024, SEEK_SET) == -1) {
perror ("LSEEK");
close (fd);
return (-1);
}
if (write (fd, lck, sizeof (lck)) != sizeof (lck)) {
perror ("WRITE");
close (fd);
return (-1);
}
/* Снимем блокировку в середине файла */
lck.l_type = F_UNLCK;
lck.l_whence = SEEK_SET;
lck.l_start = (off_t) 512;
lck.l_len = (off_t) sizeof (lck);
if (fcntl (fd, F_SETLK, lck) == -1) {
perror ("FCNTL-F_SETLK-2");
close (fd);
return (-1);
}
/* Установим блокировку на чтение в конце файла */
lck.l_type = F_RDLCK;
lck.l_whence = SEEK_END;
lck.l_start = (off_t) -sizeof (lck);
lck.l_len = (off_t) sizeof (lck);
if (fcntl (fd, F_SETLK, lck) == -1) {
perror ("FCNTL-F_SETLK-2");
close (fd);
return (-1);
}
sleep (10);
return (close (fd));
}
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <assert.h>
#define LOCKFILE "my_lockfile"
/* Программа выявляет блокировки, установленные */
/* на файл LOCKFILE */
int main (void) {
int fd;
struct flock lck;
assert ((fd = open (LOCKFILE, O_WRONLY)) != -1);
(void) printf ("ид-р проц. тип начало длина\n");
/* Начнем с попытки установить блокировку на */
/* весь файл */
lck.l_whence = SEEK_SET;
lck.l_start = 0;
lck.l_len = 0;
do {
lck.l_type = F_WRLCK;
(void) fcntl (fd, F_GETLK, lck);
if (lck.l_type != F_UNLCK) {
(void) printf ("%9d %3c %7ld %5ld\n",
lck.l_pid,
(lck.l_type == F_WRLCK) ? 'W' : 'R',
lck.l_start, lck.l_len);
/* Если эта блокировка покрывает остаток файла, */
/* нет нужды выявлять другие блокировки */
if (lck.l_len == 0) break;
/* Иначе поищем новую блокировку после найденной */
lck.l_start += lck.l_len;
}
while (lck.l_type != F_UNLCK);
return (close (fd));
}
ид-р проц. тип начало длина 31174 W 0 512 31174 W 528 496 31174 R 1024 16 31174 W 1040 0
Отметим, что блокировка на чтение расщепила блокировку на запись, первоначально покрывавшую весь файл.
Читателю предлагается выполнить командную строку set_locks
set_locks и объяснить полученный результат.
Функции setbuf(), setvbuf( ) и fflush() выполняют
#include <stdio.h> void setbuf (FILE *restrict stream, char *restrict buf); #include <stdio.h> int setvbuf (FILE *restrict stream, char *restrict buf, int type, size_t size); #include <stdio.h> int fflush (FILE *stream);
Функция setvbuf(), которую можно использовать после type:
_IOFBF – полная буферизация;
_IOLBF – построчная буферизация;
_IONBF – отсутствие буферизации.
Функция setvbuf() сама резервирует буфера заданного размера,
но если аргумент buf не является пустым указателем, может использоваться
и пользовательский буфер. В случае нормального завершения
функция возвращает 0.
Функцию setbuf() можно считать частным случаем setvbuf().
С точностью до возвращаемого значения вызов setbuf (stream, NULL)
эквивалентен setvbuf (stream, NULL, _IONBF, BUFSIZ) (отмена буферизации);
если же значение buf не равно NULL, вызов setbuf (stream, buf)
сводится к setvbuf (stream, buf, _IOFBF, BUFSIZ)
(полная буферизация).
Функцию setbuf() чаще всего применяют для отмены
буферизации стандартного setbuf (stdout, NULL) и/или setbuf (stderr, NULL).
Использование функций setbuf() и setvbuf() требует известной
аккуратности. Типичная ошибка – указание в качестве аргумента buf
автоматического массива, определенного внутри блока, и продолжение
работы с
Функция fflush() выталкивает буфера, помещая в файл
предназначенные для записи, но еще не записанные данные. Если в качестве
аргумента stream задан пустой указатель, выталкиваются все буфера всех
Вызов fflush() – необходимый элемент завершения транзакций,
но он полезен и применительно к стандартному
char name [LINE_MAX];
(void) printf ("Введите Ваше имя: ");
(void) fflush (stdout); (void) fgets (name,
sizeof (name), stdin);
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.