Файловый ввод-вывод в языке Си
Язык программирования Си поддерживает множество функций стандартных библиотек для файлового ввода и вывода. Эти функции составляют основу заголовочного файла стандартной библиотеки языка Си <stdio.h>
.
Функциональность ввода-вывода языка Си по текущим стандартам реализуется на низком уровне. Язык Си абстрагирует все файловые операции, превращая их в операции с потоками байтов, которые могут быть как «потоками ввода», так и «потоками вывода». В отличие от некоторых ранних языков программирования, язык Си не имеет прямой поддержки произвольного доступа к файлам данных; чтобы считать записанную информацию в середине файла, программисту приходится создавать поток, ищущий в середине файла, а затем последовательно считывать байты из потока.
Потоковая модель файлового ввода-вывода была популяризирована во многом благодаря операционной системе Unix, написанной на языке Си. Большая функциональность современных операционных систем унаследовала потоки от Unix, а многие языки семейства языков программирования Си унаследовали интерфейс файлового ввода-вывода языка Си с небольшими отличиями (например, PHP). Стандартная библиотека C++ отражает потоковую концепцию в своём синтаксисе (смотрите iostream).
Открытие файла при помощи функции fopen
Файл открывается при помощи функции fopen
, которая возвращает информацию потока ввода-вывода, прикреплённого к указанному файлу или другому устройству, с которого идет чтение (или в который идет запись). В случае неудачи функция возвращает нулевой указатель.
Схожая функция freopen
библиотеки Си выполняет аналогичную операцию после первого закрытия любого открытого потока, связанного с её параметрами.
Они объявляются как
FILE *fopen(const char *path, const char *mode);
FILE *freopen(const char *path, const char *mode, FILE *fp);
Функция fopen
по сути представляет собой «обертку» более высокого уровня системного вызова open
операционной системы Unix. Аналогично, fclose
является оберткой системного вызова Unix close
, а сама структура FILE
языка Си зачастую обращается к соответствующему файловому дескриптору Unix. В POSIX-окружении функция fdopen
может использоваться для инициализации структуры FILE
файловым дескриптором. Тем не менее, файловые дескрипторы как исключительно Unix-концепция не представлены в стандарте языка Си.
Параметр mode
(режим) для fopen
и freopen
должен быть строковый и начинаться с одной из следующих последовательностей:
режим | описание | начинает с.. | ||
---|---|---|---|---|
r | rb | открывает для чтения | начала | |
w | wb | открывает для записи (создаёт файл в случае его отсутствия). Удаляет содержимое и перезаписывает файл. | начала | |
a | ab | открывает для добавления (создаёт файл в случае его отсутствия) | конца | |
r+ | rb+ | r+b | открывает для чтения и записи | начала |
w+ | wb+ | w+b | открывает для чтения и записи. Удаляет содержимое и перезаписывает файл. | начала |
a+ | ab+ | a+b | открывает для чтения и записи (создаёт файл в случае его отсутствия) | конца |
Значение «b» зарезервировано для двоичного режима С. Стандарт языка Си описывает два вида файлов — текстовые и двоичные — хотя операционная система не требует их различать (однако, для некоторых компиляторов, например LCC, указание 'b' при работе с бинарным файлом принципиально важно!). Текстовый файл — файл, содержащий текст, разбитый на строки при помощи некоторого разделяющего символа окончания строки или последовательности (в Unix — одиночный символ перевода строки\n
; в Microsoft Windows за символом перевода строки следует знак возврата каретки)\r\n
. При считывании байтов из текстового файла, символы конца строки обычно связываются (заменяются) с переводом строки для упрощения обработки. При записи текстового файла одиночный символ перевода строки перед записью связывается (заменяется) с специфичной для ОС последовательностью символов конца строки. Двоичный файл — файл, из которого байты считываются и выводятся в «сыром» виде без какого-либо связывания (подстановки).
При открытом файле в режиме обновления ('+' в качестве второго или третьего символа аргумента обозначения режима) и ввод и вывод могут выполняться в одном потоке. Тем не менее, запись не может следовать за чтением без промежуточного вызова fflush
или функции позиционирования в файле (fseek
, fsetpos
или rewind
), а чтение не может следовать за записью без промежуточного вызова функции позиционирования в файле.
Режимы записи и добавления пытаются создать файл с заданным именем, если такого файла ещё не существует. Как указывалось выше, если эта операция оканчивается неудачей, fopen
возвращает NULL
.
Закрытие потока при помощи fclose
Функция fclose
принимает один аргумент: указатель на структуру FILE
потока для закрытия.
int fclose(FILE *fp);
Функция возвращает нуль в случае успеха и EOF в случае неудачи. При нормальном завершении программы функция вызывается автоматически для каждого открытого файла.
Чтение из потока
при помощи fgetc
Функция fgetc
применяется для чтения символа из потока.
int fgetc(FILE *fp);
В случае успеха fgetc
возвращает следующий байт или символ из потока (зависит от того, файл «двоичный» или «текстовый», как выше обсуждалось). В противном случае fgetc
возвращает EOF
. (Отдельный тип ошибок можно определить вызовом ferror
или feof
с указателем на файл.)
Стандартный макрос getc
так же определён в <stdio.h>
, успешно работая как fgetc
, кроме одного: будучи макросом, он может обрабатывать свои аргументы более одного раза.
Стандартная функция getchar
так же определена в <stdio.h>
, она не принимает аргументов и эквивалентна getc(stdin)
.
«Ловушка» EOF
Распространённой ошибкой является использование fgetc
, getc
или getchar
для присваивания результата переменной типа char
перед сравнением его с EOF
. Следующий фрагмент кода демонстрирует эту ошибку, а рядом приведён корректный вариант:
Ошибка | Правильно |
---|---|
char c;
while ((c = getchar()) != EOF) {
putchar(c);
}
|
int c;
while ((c = getchar()) != EOF) {
putchar(c);
}
|
Нужно учитывать систему, в которой тип char
, длина которого составляет 8 бит (в частности, архитектура x86), представляет 256 различных значений. getchar
может возвращать любой из 256 возможных символов, а также может возвращать EOF
для обозначения конца файла, значение которого не может совпадать ни с одним из значений char
.
Когда результат getchar
присваивается переменной типа char
, которая может представить лишь 256 различных значений, происходит вынужденная потеря информации — при сжатии 257 значений в 256 «мест» происходит коллизия. Значение EOF
при конвертации в char
становится неотличимым от любого из остальных 256 символов. Если этот символ обнаружен в файле, код, приведённый выше, может принять его за признак конца файла, или, что ещё хуже, если тип char
— беззнаковый, тогда с учётом того, что EOF
— значение отрицательное, оно никогда не сможет стать равным любому беззнаковому char
, и таким образом, пример выше не закончится на метке конца файла, а будет выполняться вечно, повторно печатая символ, получающийся при конвертации EOF
в char
.
В системах, где int
и char
одинакового размера[каких?], даже «правильный» вариант будет работать некорректно из-за сходства EOF
и другого символа. Правильным вариантом обработки подобной ситуации является проверка feof
и ferror
после того, как getchar
вернет EOF
. Если feof
определит, что конец файла ещё не достигнут, а ferror
«сообщит», что ошибок нет, то EOF
, возвращённый getchar
, может считаться текущим символом. Такие дополнительные проверки делаются редко, так как большинство программистов считает, что их код никогда не будет выполняться на подобных системах с «большим char
». Другой способ состоит в использовании проверки при компиляции, что UINT_MAX > UCHAR_MAX
, которая хотя бы предотвратит компиляцию на подобных системах.
при помощи fgets
Функция fgets
применяется для чтения строки из потока. Считывание происходит до тех пор пока не будет достигнут конец строки (hex:0D0A, эквивалентны в листингах \n) или длина строки, в которую происходит считывание.
Предположим, у нас есть файл some_file.txt с текстом
палиндромы А в Енисее - синева. А лама мала. А лис, он умен - крыса сыр к нему носила. (И. Бабицкий)
#include <stdio.h>
#include <string.h>
int main (int argc, char* argv[]) /* argc хранит количество параметров, а argv[] указатели на эти параметры.
Например, если мы запустим исполняемый файл "fgets_example param1 param2", то argc будет равно 3, а argv[] = {"fgets_example", "param1", "param2"}*/
{
FILE *file;
char *fname = "some_file.txt";
char result_string[20]; //Строка в 20 символов
file = fopen(fname,"r");
if(file == NULL)
{
printf("не могу открыть файл '%s'",fname);
return 0;
}
int i=0;
char *real_tail;
while(fgets(result_string,sizeof(result_string),file))
{
real_tail="";
printf("Строка %d:Длина строки - %d:",i++,strlen(result_string));
if(result_string[strlen(result_string)-1] == '\n')//проверяем является ли последний элемент в строке символом её окончания
{
real_tail="\\n";
result_string[strlen(result_string)-1]='\0';
};// эта часть кода добавлена лишь для отображения символа конца строки в консоль без перевода на новую строку
printf("%s%s\n",result_string,real_tail);
}
fclose(file);
return 0;
}
в результате выполнения мы получим
Строка 0:Длина строки - 11:палиндромы\n Строка 1:Длина строки - 19: А в Енисее - си Строка 2:Длина строки - 6:нева.\n Строка 3:Длина строки - 17: А лама мала.\n Строка 4:Длина строки - 19: А лис, он умен Строка 5:Длина строки - 19:- крыса сыр к нему Строка 6:Длина строки - 19:носила. (И. Бабицки Строка 7:Длина строки - 2:й)
Функция strlen определяет длину строки по количеству символов до '\0', например:
printf("%d",strlen("123 \0 123")); //выведет 4
fwrite
В языке программирования Си функции fread
и fwrite
соответственно реализуют файловые операции ввода и вывода. fread
и fwrite
объявлены в <stdio.h>
.
Запись в файл при помощи fwrite
fwrite определяется как
int fwrite ( const char * array, size_t size, size_t count, FILE * stream );
Функция fwrite
записывает блок данных в поток. Таким образом запишется массив элементов array
в текущую позицию в потоке. Для каждого элемента запишется size
байт. Индикатор позиции в потоке изменится на число байт, записанных успешно. Возвращаемое значение будет равно count
в случае успешного завершения записи. В случае ошибки возвращаемое значение будет меньше count
.
Следующая программа открывает файл пример.txt, записывает в него строку символов, а затем его закрывает.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(void)
{
FILE *fp;
size_t count;
char const *str = "привет\n";
fp = fopen("пример.txt", "wb");
if(fp == NULL) {
perror("ошибка открытия пример.txt");
return EXIT_FAILURE;
}
count = fwrite(str, sizeof(char), strlen(str), fp);
printf("Записано %lu байт. fclose(fp) %s.\n", (unsigned long)count, fclose(fp) == 0 ? "успешно" : "с ошибкой");
fclose(fp);
return 0;
}
Запись в поток при помощи fputс
Функция fputc
применяется для записи символа в поток.
int fputc(int c, FILE *fp);
Параметр c
«тихо» конвертируется в unsigned char
перед выводом. Если прошло успешно, то fputc
возвращает записанный символ. Если ошибка, то fputc
возвращает EOF
.
Стандартный макрос putc
также определён в <stdio.h>
, работая в общем случае аналогично fputc
, за исключением того момента, что будучи макросом, он может обрабатывать свои аргументы более одного раза.
Стандартная функция putchar
, также определённая в <stdio.h>
, принимает только первый аргумент, и является эквивалентной putc(c, stdout)
, где c
является упомянутым аргументом.
Пример использования
Нижеследующая программа на языке Си открывает двоичный файл с названием мойфайл, читает пять байт из него, а затем закрывает файл.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char buffer[5] = {0}; /* инициализируем нулями */
int i, rc;
FILE *fp = fopen("мойфайл", "rb");
if (fp == NULL) {
perror("Ошибка при открытии \"мойфайл\"");
return EXIT_FAILURE;
}
for (i = 0; (rc = getc(fp)) != EOF && i < 5; buffer[i++] = rc);
fclose(fp);
if (i == 5) {
puts("Прочитанные байты...");
printf("%x %x %x %x %x\n", buffer[0], buffer[1], buffer[2], buffer[3], buffer[4]);
} else
fputs("Ошибка чтения файла.\n", stderr);
return EXIT_SUCCESS;
}
См. также
Дополнительные источники
- stdio.h on Coding Programmer Page / C Library Reference and Examples (en)
fclose(3)
— страница справки man по библиотечным функциям GNU/Linux (англ.)fgetc(3)
— страница справки man по библиотечным функциям GNU/Linux (англ.)fopen(3)
— страница справки man по библиотечным функциям GNU/Linux (англ.)fputc(3)
— страница справки man по библиотечным функциям GNU/Linux (англ.)- Вопрос 12.1 в вопросах-ответах по Си: использование
char
для хранения возвращаемогоgetc
значения