Операції вводу та виводу застосовують системні виклики read і write, доступ до яких з C-програм здійснюється за допомогою двох функцій під назвою read і write. Для обох, першим аргументом є дескриптор файла. Другий аргумент — це символьний масив, вказаний вашою програмою, куди надійдуть дані або звідки їх можна отримати. Третій аргумент — це кількість байтів, що буде передано.
int n_read = read(int fd, char *buf, int n);
int n_written = write(int fd, char *buf, int n);
Кожний виклик поверне відлік числа переданих байтів. При читанні, кількість повернених байтів може виявитися меншою за вказану. Повернення нуль байтів означає кінець файла, а -1 вказує на якусь помилку. При запису, значення, що повертається, дорівнюватиме кількості записаних байтів; якщо значення не дорівнює кількості запитаних байтів, це означатиме помилку.
Будь-яка кількість байтів може бути прочитана або записана за один раз. Найпоширенішим значенням є 1, що означає по одному символу за раз («небуферований ввід або вивід»), а також 1024 або 4096, що відповідає фізичному розмірові блока зовнішнього пристрою. Більші розміри ефективніші, так як це зменшує кількість системних викликів.
Маючи цю інформацію, ми можемо написати просту програму копіювання власного вводу до виводу, еквівалентну програмі копіювання файлів з Розділу 1. Ця програма копіюватиме будь-що до будь-чого, так як ввід і вивід можна перенаправити до будь-якого файла або пристрою.
#include "syscalls.h"
main() /* копіює ввід до виводу */
{
char buf[BUFSIZ];
int n;
while ((n = read(0, buf, BUFSIZ)) > 0)
write(1, buf, n);
return 0;
}
Ми об'єднали прототипи функцій системних викликів у один файл під назвою syscalls.h для включення його в програми в цьому розділі. Проте, ця назва не є стандартною. Параметр BUFSIZтакож визначено в syscalls.h; його значення — це розмір, який підходить для нашої системи. Якщо розмір файла не є кратним BUFSIZ, деякі виклики read можуть повернути меншу кількість байтів для запису write; наступний виклик read поверне нуль, в такому випадку.
Корисно побачити, як можна використати read і write для створення функцій вищого рівня, таких як getchar, putchar тощо. Наприклад, ось версія getchar, яка здійснює небуферований ввід шляхом читання стандартного вводу по одному символові за раз.
#include "syscalls.h"
/* getchar: небуферований ввід по одному знакові */
int getchar(void)
{
char c;
return (read(0, &c, 1) == 1) ? (unsigned char) c : EOF;
}
змінна c повинна бути типу char, оскільки read вимагає знакового покажчику. Зведення c доunsigned char (беззнакового символу) усуває будь-які проблеми зі знаками.
Наступна версія getchar читає ввід великими відрізками, а виводить символи по-одному за раз.
#include "syscalls.h"
/* getchar: проста буферована версія */
int getchar(void)
{
static char buf[BUFSIZ];
static char *bufp = buf;
static int n = 0;
if (n == 0) { /* буфер порожній */
n = read(0, buf, sizeof buf);
bufp = buf;
}
return (--n >= 0) ? (unsigned char) *bufp++ : EOF;
}
Якщо ці версії getchar було би компільовано зі включеним <stdio.h>, необхідно би було#undef (скасувати) назву getchar у випадку, якщо її втілено як макрос.