Опубликован: 15.06.2004 | Доступ: свободный | Студентов: 2557 / 712 | Оценка: 4.35 / 3.96 | Длительность: 27:47:00
ISBN: 978-5-9556-0011-6
Лекция 5:

Файловый ввод/вывод

При записи в канал, если флаг 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));
}
Листинг 5.14. Пример программы, использующей функцию write().

Программа 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);
}
Листинг 5.15. Пример программы, использующей функции read() и write().

Для буферизованного ввода/вывода байт служат функции 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.16. Описание функций fgetc(), fputc(), fgets(), fputs(), puts().

Описание аналогичных функций для широких символов приведено в листинге 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);
Листинг 5.17. Описание функций fgetwc(), fputwc(), fgetws(), fputws().

Функция 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));
}
Листинг 5.18. Пример использования функций fgets() и fputs().

Использование функций 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);
}
Листинг 5.19. Пример использования функций fgetc() и fputc().

Приведенный пример показывает, что написание мобильных программ даже для сравнительно простых задач требует заметных усилий. В данном случае пришлось воспользоваться нестандартной возможностью – включаемым файлом <endian.h>, содержащим препроцессорные константы, которые описывают порядок байт в машинном слове.

Отметим также стиль обработки ошибочных ситуаций, основанный на применении макроса assert. Для авторов программ такой стиль, безусловно, упрощает жизнь, исходный текст получается более компактным, однако для пользователей сообщение вида

decode64: decode64.c:39: main: Assertion 
`((void *)0) != (input = fopen (argv [1], "r"))' 
failed.

может оказаться менее информативным, чем выдача функции perror().

Антон Коновалов
Антон Коновалов

В настоящее время актуальный стандарт - это POSIX 2008 и его дополнение POSIX 1003.13
Планируется ли актуализация материалов данного очень полезного курса?