Функция ошибки открытия файла на с

Теги: Текстовые файлы, fopen, fclose, feof, setbuf, setvbuf, fflush, fgetc, fprintf, fscanf, fgets, буферизированный поток, небуферизированный поток.

Работа с текстовыми файлами

Работа с текстовым файлом похожа работу с консолью: с помощью функций форматированного ввода мы сохраняем данные в файл, с помощью функций форматированного вывода считываем данные из файла. Есть множество нюансов, которые мы позже рассмотрим.
Основные операции, которые необходимо проделать, это

  • 1. Открыть файл, для того, чтобы к нему можно было обращаться. Соответственно, открывать можно для чтения, записи, чтения и записи,
    переписывания или записи в конец файла и т.п. Когда вы открываете файл, может также произойти куча ошибок – файла может не существовать, это может быть
    файл не того типа, у вас может не быть прав на работу с файлом и т.д. Всё это необходимо учитывать.
  • 2. Непосредственно работа с файлом — запись и чтение. Здесь также нужно помнить, что мы работаем не с памятью с произвольным доступом, а с буферизированным потоком, что добавляет свою специфику.
  • 3. Закрыть файл. Так как файл является внешним по отношению к программе ресурсом, то если его не закрыть, то он продолжит висеть в памяти, возможно, даже после
    закрытия программы (например, нельзя будет удалить открытый файл или внести изменения и т.п.). Кроме того, иногда необходимо не закрывать, а «переоткрывать»
    файл для того, чтобы, например, изменить режим доступа.

Кроме того, существует ряд задач, когда нам не нужно обращаться к содержимому файла: переименование, перемещение, копирование и т.д. К сожалению,
в стандарте си нет описания функций для этих нужд. Они, безусловно, имеются для каждой из реализаций компилятора. Считывание содержимого каталога
(папки, директории) – это тоже обращение к файлу, потому что папка сама по себе является файлом с метаинформацией.

Иногда необходимо выполнять некоторые вспомогательные операции: переместиться в нужное место файла, запомнить текущее положение, определить длину файла и т.д.

Для работы с файлом необходим объект FILE. Этот объект хранит идентификатор файлового потока и информацию, которая нужна, чтобы им управлять, включая
указатель на его буфер, индикатор позиции в файле и индикаторы состояния.

Объект FILE сам по себе является структурой, но к его полям не должно быть доступа. Переносимая программа должна работать с файлом как с абстрактным объектом,
позволяющим получить доступ до файлового потока.

Создание и выделение памяти под объект типа FILE осуществляется с помощью функции fopen или tmpfile (есть и другие, но мы остановимся только на этих).

Функция fopen открывает файл. Она получает два аргумента – строку с адресом файла и строку с режимом доступа к файлу. Имя файла может быть как абсолютным, так и относительным.
fopen возвращает указатель на объект FILE, с помощью которого далее можно осуществлять доступ к файлу.

FILE* fopen(const char* filename, const char* mode);

Например, откроем файл и запишем в него Hello World

#include <stdio.h>
#include <conio.h>
#include <stdlib.h>

void main() {
	//С помощью переменной file будем осуществлять доступ к файлу
	FILE *file;
	//Открываем текстовый файл с правами на запись
	file = fopen("C:/c/test.txt", "w+t");
	//Пишем в файл
	fprintf(file, "Hello, World!");
	//Закрываем файл
	fclose(file);
	getch();
}

Функция fopen сама выделяет память под объект, очистка проводится функцией fclose. Закрывать файл обязательно, самостоятельно он не закроется.

Функция fopen может открывать файл в текстовом или бинарном режиме. По умолчанию используется текстовый. Режим доступа может быть следующим

Параметры доступа к файлу.

Тип Описание
r Чтение. Файл должен существовать.
w Запись нового файла. Если файл с таким именем уже существует, то его содержимое будет потеряно.
a Запись в конец файла. Операции позиционирования (fseek, fsetpos, frewind) игнорируются. Файл создаётся, если не существовал.
r+ Чтение и обновление. Можно как читать, так и писать. Файл должен существовать.
w+ Запись и обновление. Создаётся новый файл. Если файл с таким именем уже существует, то его содержимое будет потеряно. Можно как писать, так и читать.
a+ Запись в конец и обновление. Операции позиционирования работают только для чтения, для записи игнорируются. Если файл не существовал, то будет создан новый.

Если необходимо открыть файл в бинарном режиме, то в конец строки добавляется буква b, например
“rb”, “wb”, “ab”, или, для смешанного режима “ab+”, “wb+”, “ab+”. Вместо b можно добавлять букву t, тогда файл будет открываться в текстовом режиме. Это зависит от реализации. В новом стандарте си (2011) буква x означает, что функция fopen должна завершиться с ошибкой, если файл уже существует.
Дополним нашу старую программу: заново откроем файл и считаем, что мы туда записали.

#include <stdio.h>
#include <conio.h>
#include <stdlib.h>

void main() {
	FILE *file;
	char buffer[128];

	file = fopen("C:/c/test.txt", "w");
	fprintf(file, "Hello, World!");
	fclose(file);
	file = fopen("C:/c/test.txt", "r");
	fgets(buffer, 127, file);
	printf("%s", buffer);
	fclose(file);
	getch();
}

Вместо функции fgets можно было использовать fscanf, но нужно помнить, что она может считать строку только до первого пробела.

fscanf(file, "%127s", buffer);

Также, вместо того, чтобы открывать и закрывать файл можно воспользоваться функцией freopen, которая «переоткрывает» файл с новыми правами доступа.

#include <stdio.h>
#include <conio.h>
#include <stdlib.h>

void main() {
	FILE *file;
	char buffer[128];

	file = fopen("C:/c/test.txt", "w");
	fprintf(file, "Hello, World!");
	freopen("C:/c/test.txt", "r", file);
	fgets(buffer, 127, file);
	printf("%s", buffer);
	fclose(file);
	getch();
}

Функции fprintf и fscanf отличаются от printf и scanf только тем, что принимают в качестве первого аргумента указатель на FILE, в который они будут выводить или из которого
они будут читать данные.
Здесь стоит сразу же добавить, что функции printf и scanf могут быть без проблем заменены функциями fprintf и fscanf. В ОС (мы рассматриваем самые распространённые и
адекватные операционные системы) существует три стандартных потока: стандартный поток вывода stdout, стандартный поток ввода stdin и стандартный поток вывода ошибок stderr. Они автоматически открываются во время запуска приложения и связаны с консолью. Пример

#include <stdio.h>
#include <conio.h>
#include <stdlib.h>

void main() {
	int a, b;
	fprintf(stdout, "Enter two numbersn");
	fscanf(stdin, "%d", &a);
	fscanf(stdin, "%d", &b);
	if (b == 0) {
		fprintf(stderr, "Error: divide by zero");
	} else {
		fprintf(stdout, "%.3f", (float) a / (float) b);
	}
	getch();
}

Ошибка открытия файла

Если вызов функции fopen прошёл неудачно, то она возвратит NULL. Ошибки во время работы с файлами встречаются достаточно часто, поэтому каждый раз, когда
мы окрываем файл, необходимо проверять результат работы

#include <stdio.h>
#include <conio.h>
#include <stdlib.h>

#define ERROR_OPEN_FILE -3

void main() {
	FILE *file;
	char buffer[128];

	file = fopen("C:/c/test.txt", "w");
	if (file == NULL) {
		printf("Error opening file");
		getch();
		exit(ERROR_OPEN_FILE);
	}
	fprintf(file, "Hello, World!");
	freopen("C:/c/test.txt", "r", file);
	if (file == NULL) {
		printf("Error opening file");
		getch();
		exit(ERROR_OPEN_FILE);
	}
	fgets(buffer, 127, file);
	printf("%s", buffer);
	fclose(file);
	getch();
}

Проблему вызывает случай, когда открывается сразу несколько файлов: если один из них нельзя открыть, то остальные также должны быть закрыты

...
FILE *inputFile, *outputFile;
	unsigned m, n;
	unsigned i, j;

	inputFile = fopen(INPUT_FILE, READ_ONLY);
	if (inputFile == NULL) {
		printf("Error opening file %s", INPUT_FILE);
		getch();
		exit(3);
	}
	outputFile = fopen(OUTPUT_FILE, WRITE_ONLY);
	if (outputFile == NULL) {
		printf("Error opening file %s", OUTPUT_FILE);
		getch();
		if (inputFile != NULL) {
			fclose(inputFile);
		}
		exit(4);
	}
...

В простых случаях можно действовать влоб, как в предыдущем куске кода. В более сложных случаях используются
методы, подменяющиее RAII из С++: обёртки, или особенности компилятора (cleanup в GCC) и т.п.

Буферизация данных

Как уже говорилось ранее, когда мы выводим данные, они сначала помещаются в буфер. Очистка буфера осуществляется

  • 1) Если он заполнен
  • 2) Если поток закрывается
  • 3) Если мы явно указываем, что необходимо очистить буфер (здесь тоже есть исключения:)).
  • 4) Также очищается, если программа завершилась удачно. Вместе с этим закрываются и все файлы. В случае ошибки выполнения этого может не произойти.

Форсировать выгрузку буфера можно с помощью вызова функции fflush(File *). Рассмотрим два примера – с очисткой и без.

#include <stdio.h>
#include <conio.h>
#include <stdlib.h>

void main() {
	FILE *file;
	char c;

	file = fopen("C:/c/test.txt", "w");
	
	do {
		c = getch();
		fprintf(file, "%c", c);
		fprintf(stdout, "%c", c);
		//fflush(file);
	} while(c != 'q');

	fclose(file);
	getch();
}

Раскомментируйте вызов fflush. Во время выполнения откройте текстовый файл и посмотрите на поведение.

Буфер файла можно назначить самостоятельно, задав свой размер. Делается это при помощи функции

void setbuf (FILE * stream, char * buffer);

которая принимает уже открытый FILE и указатель на новый буфер. Размер нового буфера должен быть не меньше чем BUFSIZ (к примеру, на текущей рабочей станции BUFSIZ равен 512 байт).
Если передать в качестве буфера NULL, то поток станет небуферизированным. Можно также воспользоваться функцией

int setvbuf ( FILE * stream, char * buffer, int mode, size_t size );

которая принимает буфер произвольного размера size. Режим mode может принимать следующие значения

  • _IOFBF — полная буферизация. Данные записываются в файл, когда он заполняется. На считывание, буфер считается заполненным, когда запрашивается операция ввода и буфер пуст.
  • _IOLBF — линейная буферизация. Данные записываются в файл когда он заполняется, либо когда встречается символ новой строки. На считывание, буфер заполняется до символа новой строки, когда запрашивается операция ввода и буфер пуст.
  • _IONBF – без буферизации. В этом случае параметры size и buffer игнорируются.

В случае удачного выполнения функция возвращает 0.

Пример: зададим свой буфер и посмотрим, как осуществляется чтение из файла. Пусть файл короткий (что-нибудь, типа Hello, World!), и считываем мы его посимвольно

#include <conio.h>
#include <stdio.h>
#include <stdlib.h>

void main() {
	FILE *input = NULL;
	char c;
	char buffer[BUFSIZ * 2] = {0};

	input = fopen("D:/c/text.txt", "rt");
	setbuf(input, buffer);

	while (!feof(input)) {
		c = fgetc(input);
		printf("%cn", c);
		printf("%sn", buffer);
		_getch();
	}

	fclose(input);
}

Видно, что данные уже находятся в буфере. Считывание посимвольно производится уже из буфера.

feof

Функция int feof (FILE * stream);
возвращает истину, если конец файла достигнут. Функцию удобно использовать, когда необходимо пройти весь файл от начала до конца.
Пусть есть файл с текстовым содержимым text.txt. Считаем посимвольно файл и выведем на экран.

#include <conio.h>
#include <stdio.h>
#include <stdlib.h>

void main() {
	FILE *input = NULL;
	char c;

	input = fopen("D:/c/text.txt", "rt");
	if (input == NULL) {
		printf("Error opening file");
		_getch();
		exit(0);
	}
	while (!feof(input)) {
		c = fgetc(input);
		fprintf(stdout, "%c", c);
	}

	fclose(input);
	_getch();
}

Всё бы ничего, только функция feof работает неправильно… Это связано с тем, что понятие «конец файла» не определено. При использовании feof
часто возникает ошибка, когда последние считанные данные выводятся два раза. Это связано с тем, что данные записывается в буфер ввода, последнее
считывание происходит с ошибкой и функция возвращает старое считанное значение.

#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
 
void main() {
    FILE *input = NULL;
    char c;
 
    input = fopen("D:/c/text.txt", "rt");
    if (input == NULL) {
        printf("Error opening file");
        _getch();
        exit(0);
    }
    while (!feof(input)) {
        fscanf(input, "%c", &c);
        fprintf(stdout, "%c", c);
    }
 
    fclose(input);
    _getch();
}

Этот пример сработает с ошибкой (скорее всего) и выведет последний символ файла два раза.

Решение – не использовать feof. Например, хранить общее количество записей или использовать тот факт, что функции
fscanf и пр. обычно возвращают число верно считанных и сопоставленных значений.

#include <conio.h>
#include <stdio.h>
#include <stdlib.h>

void main() {
    FILE *input = NULL;
    char c;
 
    input = fopen("D:/c/text.txt", "rt");
    if (input == NULL) {
        printf("Error opening file");
        _getch();
        exit(0);
    }
    while (fscanf(input, "%c", &c) == 1) {
        fprintf(stdout, "%c", c);
    }
 
    fclose(input);
    _getch();
}

Примеры

1. В одном файле записаны два числа — размерности массива. Заполним второй файл массивом случайных чисел.

#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <time.h>

//Имена файлов и права доступа
#define INPUT_FILE  "D:/c/input.txt"
#define OUTPUT_FILE "D:/c/output.txt"
#define READ_ONLY  "r"
#define WRITE_ONLY "w"
//Максимальное значение для размера массива
#define MAX_DIMENSION 100
//Ошибка при открытии файла
#define ERROR_OPEN_FILE -3

void main() {
	FILE *inputFile, *outputFile;
	unsigned m, n;
	unsigned i, j;

	inputFile = fopen(INPUT_FILE, READ_ONLY);
	if (inputFile == NULL) {
		printf("Error opening file %s", INPUT_FILE);
		getch();
		exit(ERROR_OPEN_FILE);
	}
	outputFile = fopen(OUTPUT_FILE, WRITE_ONLY);
	if (outputFile == NULL) {
		printf("Error opening file %s", OUTPUT_FILE);
		getch();
//Если файл для чтения удалось открыть, то его необходимо закрыть
		if (inputFile != NULL) {
			fclose(inputFile);
		}
		exit(ERROR_OPEN_FILE);
	}

	fscanf(inputFile, "%ud %ud", &m, &n);
	if (m > MAX_DIMENSION) {
		m = MAX_DIMENSION;
	}
	if (n > MAX_DIMENSION) {
		n = MAX_DIMENSION;
	}
	srand(time(NULL));
	for (i = 0; i < n; i++) {
		for (j = 0; j < m; j++) {
			fprintf(outputFile, "%8d ", rand());
		}
		fprintf(outputFile, "n");
	}

//Закрываем файлы
	fclose(inputFile);
	fclose(outputFile);
}

2. Пользователь копирует файл, при этом сначала выбирает режим работы: файл может выводиться как на консоль, так и копироваться в новый файл.

#include <stdio.h>
#include <conio.h>
#include <stdlib.h>

#define ERROR_FILE_OPEN -3

void main() {
	FILE *origin = NULL;
	FILE *output = NULL;
	char filename[1024];
	int mode;

	printf("Enter filename: ");
	scanf("%1023s", filename);

	origin = fopen(filename, "r");
	if (origin == NULL) {
		printf("Error opening file %s", filename);
		getch();
		exit(ERROR_FILE_OPEN);
	}

	printf("enter mode: [1 - copy, 2 - print] ");
	scanf("%d", &mode);

	if (mode == 1) {
		printf("Enter filename: ");
		scanf("%1023s", filename);
		output = fopen(filename, "w");
		if (output == NULL) {
			printf("Error opening file %s", filename);
			getch();
			fclose(origin);
			exit(ERROR_FILE_OPEN);
		}
	} else {
		output = stdout;
	}

	while (!feof(origin)) {
		fprintf(output, "%c", fgetc(origin));
	}

	fclose(origin);
	fclose(output);

	getch();
}

3. Пользователь вводит данные с консоли и они записываются в файл до тех пор, пока не будет нажата клавиша esc. Проверьте программу и посмотрите. как она себя ведёт в случае, если вы вводите backspace: что выводится в файл и что выводится на консоль.

#include <stdio.h>
#include <conio.h>
#include <stdlib.h>

#define ERROR_FILE_OPEN -3

void main() {
	FILE *output = NULL;
	char c;

	output = fopen("D:/c/test_output.txt", "w+t");
	if (output == NULL) {
		printf("Error opening file");
		_getch();
		exit(ERROR_FILE_OPEN);
	}

	for (;;) {
		c = _getch();
		if (c == 27) {
			break;
		}
		fputc(c, output);
		fputc(c, stdout);
	}

	fclose(output);
}

4. В файле записаны целые числа. Найти максимальное из них. Воспользуемся тем, что функция fscanf возвращает число верно прочитанных и сопоставленных объектов. Каждый раз должно возвращаться число 1.

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

#define ERROR_FILE_OPEN -3

void main() {
	FILE *input = NULL;
	int num, maxn, hasRead;

	input = fopen("D:/c/input.txt", "r");
	if (input == NULL) {
		printf("Error opening file");
		_getch();
		exit(ERROR_FILE_OPEN);
	}

	maxn = INT_MIN;
	hasRead = 1;
	while (hasRead == 1) {
		hasRead = fscanf(input, "%d", &num);
		if (hasRead != 1) {
			continue;
		}
		if (num > maxn) {
			maxn = num;
		}
	}
	printf("max number = %d", maxn);
	fclose(input);
	_getch();
}

Другое решение считывать числа, пока не дойдём до конца файла.

#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>

#define ERROR_FILE_OPEN -3

void main() {
	FILE *input = NULL;
	int num, maxn, hasRead;

	input = fopen("D:/c/input.txt", "r");
	if (input == NULL) {
		printf("Error opening file");
		_getch();
		exit(ERROR_FILE_OPEN);
	}

	maxn = INT_MIN;
	while (!feof(input)) {
		fscanf(input, "%d", &num);
		if (num > maxn) {
			maxn = num;
		}
	}
	printf("max number = %d", maxn);

	fclose(input);
	_getch();
}

5. В файле записаны слова: русское слово, табуляция, английское слово, в несколько рядов. Пользователь вводит английское слово, необходимо вывести русское.

Файл с переводом выглядит примерно так


солнце sun
карандаш pen
шариковая ручка pencil
дверь door
окно windows
стул chair
кресло armchair

и сохранён в кодировке cp866 (OEM 866). При этом важно: последняя пара cлов также заканчивается переводом строки.

Алгоритм следующий — считываем строку из файла, находим в строке знак табуляции, подменяем знак табуляции нулём, копируем русское слово из буфера, копируем английское слово из буфера, проверяем на равенство.

#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define ERROR_FILE_OPEN -3

void main() {
	FILE *input = NULL;
	char buffer[512];
	char enWord[128];
	char ruWord[128];
	char usrWord[128];
	unsigned index;
	int length;
	int wasFound;

	input = fopen("D:/c/input.txt", "r");
	if (input == NULL) {
		printf("Error opening file");
		_getch();
		exit(ERROR_FILE_OPEN);
	}

	printf("enter word: ");
	fgets(usrWord, 127, stdin);
	wasFound = 0;
	while (!feof(input)) {
		fgets(buffer, 511, input);
		length = strlen(buffer);
		for (index = 0; index < length; index++) {
			if (buffer[index] == 't') {
				buffer[index] = '';
				break;
			}
		}
		strcpy(ruWord, buffer);
		strcpy(enWord, &buffer[index + 1]);
		if (!strcmp(enWord, usrWord)) {
			wasFound = 1;
			break;
		}
	}

	if (wasFound) {
		printf("%s", ruWord);
	} else {
		printf("Word not found");
	}

	fclose(input);
	_getch();
}

6. Подсчитать количество строк в файле. Будем считывать файл посимвольно, считая количество символов ‘n’ до тех пор, пока не встретим символ EOF. EOF – это спецсимвол,
который указывает на то, что ввод закончен и больше нет данных для чтения. Функция возвращает отрицательное значение в случае ошибки.
ЗАМЕЧАНИЕ: EOF имеет тип int, поэтому нужно использовать int для считывания символов. Кроме того, значение EOF не определено стандартом.

#define _CRT_SECURE_NO_WARNINGS

#include <conio.h>
#include <stdio.h>
#include <stdlib.h>

int cntLines(const char *filename) {
	int lines = 0;
	int any;	//any типа int, потому что EOF имеет тип int!
	FILE *f = fopen(filename, "r");
	if (f == NULL) {
		return -1;
	}
	do {
		any = fgetc(f);
		//printf("%c", any);//debug
		if (any == 'n') {
			lines++;
		}
	} while(any != EOF);
	fclose(f);
	return lines;
}

void main() {
	printf("%dn", cntLines("C:/c/file.txt"));
	_getch();
}

Q&A

Всё ещё не понятно? – пиши вопросы на ящик email

Переполнение целых чисел

В языке Си нет никакой нативной обработки ошибок, а значит приходится пользоваться всякими костылями, чтобы эту обработку ошибок сделать. Например, в задании на квадратные уравнения вы пользовались чем-то, похожим на это:

квадратные уравнения

Оказывается, этот подход очень распространен в библиотечном коде проекта GNU. Например, если мы посмотрим на документацию функции `fopen(…)`

документацию функции

то есть, функция `fopen()` вернет:

  • Указатель типа `FILE*`, если программа отработала верно 
  • `NULL`, если во время выполнения возникла ошибка (например, файл который мы пытаемся открыть, не существует). Часть про `errno` пока что игнорируем.

А это значит, что обработка ошибок функции fopen должна происходить примерно вот так:

обработка ошибок функции fopen

Файл `blabla.txt` я специально не создал, а значит, моя программа ожидаемо закончится с сообщением `something went wrong with fopen()!`. Но бывают (почти всегда) ситуации, в которых об ошибке хочется знать чуть больше, чем просто то что она произошла. Тут и вступает в дело переменная `errno`, которая глобальная и определена во всех больших хедерах (в том числе в `stdlib.h` и `stdio.h`, но не в `string.h` — подумайте, почему). Где-то в недрах `errno.h` есть специальный массив строк под названием `sys_errlist`, к которому у вас, как у программиста, строго говоря, нет доступа. Строки в этом массиве — сообщения об ошибках, а `errno`, которая выставилась в результате ошибки — индекс в этом массиве, который указывает на описание ошибки, которая возникла. Существуют специальные функции, которые помогают вам выводить ошибки, например `perror()`. Перепишем наш код с обработкой ошибок через `perror()`

специальные функции

Теперь при ошибке, при открытии файла, вызовется функция `perror()`, она проверит чему равна переменная `errno`(которая, повторюсь, глобальная и хитро определена в заголовке как `stdio.h`, так и `errno.h`), и она найдет нужное сообщение об ошибке, то есть `main: No such file or directory`. В данном случае, я посчитал, что ошибка критическая, поскольку любое продолжение программы приведет к Segmentation fault, поэтому после вывода сообщения об ошибке, программа завершается с кодом errno (что, в принципе, необязательно). Узнать что делает аргумент функции `perror()` остается заданием для читателя. Узнать это можно в man-страницах ;)

Более лаконично этот код записывается так:

код записывается так

а ЕЩЕ более лаконично

более лаконичный код

(Это решение плохо тем, что иногда в случае ошибки функции возвращают `NULL`, а иногда `-1` в случае ошибок, такие штуки ВСЕГДА надо проверять в man-страницах.)

Важные замечания:

  • НЕ стоит проверять значение `errno` в случае успешного выполнения функции, потому что оно, строго говоря, не определено в этом случае (хотя, в большинстве случаев оно будет значения `Success`)
  • Если у вас несколько функций подряд, которые используют errno, то проверять наличие ошибки нужно после КАЖДОЙ из них.

проверка на наличие ошибок кода

Есть ли альтернативы? Ну, пока не придумали. Даже язык Go, по идеологии внук языка Cи, страдает от постоянных

язык Go страдает от ошибок

А этот язык сейчас один из самых популярных! (Хотя в Go2 придумали безумно интересный механизм обработки ошибок, кардинально отличающийся от того, что написано здесь и других прочих объектно-ориентированных exception-handling).

Существует механизм `SJLJ`, пользоваться которым без надобности не рекомендуется.

Берегите себя и пишите безопасный код ;)

It is quite common that errors may occur while reading data from a file in C++ or writing data to a file. For example, an error may arise due to the following:

  • When trying to read a file beyond indicator.
  • When trying to read a file that does not exist.
  • When trying to use a file that has not been opened.
  • When trying to use a file in an inappropriate mode i.e., writing data to a file that has been opened for reading.
  • When writing to a file that is write-protected i.e., trying to write to a read-only file.

Failure to check for errors then the program may behave abnormally therefore an unchecked error may result in premature termination for the program or incorrect output.

Below are some Error handling functions during file operations in C/C++:

ferror():

In C/C++, the library function ferror() is used to check for the error in the stream. Its prototype is written as:

 int ferror (FILE *stream);

The ferror() function checks for any error in the stream. It returns a value zero if no error has occurred and a non-zero value if there is an error. The error indication will last until the file is closed unless it is cleared by the clearerr() function. 

Below is the program to implement the use of ferror():

C

#include <stdio.h>

#include <stdlib.h>

int main()

{

    FILE* fp;

    char feedback[100];

    int i;

    fp = fopen("GeeksForGeeks.TXT", "w");

    if (fp == NULL) {

        printf("n The file could "

               "not be opened");

        exit(1);

    }

    printf("n Provide feedback on "

           "this article: ");

    fgets(feedback, 100, stdin);

    for (i = 0; i < feedback[i]; i++)

        fputc(feedback[i], fp);

    if (ferror(fp)) {

        printf("n Error writing in file");

        exit(1);

    }

    fclose(fp);

}

C++

#include <bits/stdc++.h>

int main()

{

    FILE* fp;

    char feedback[100];

    int i;

    fp = fopen("GeeksForGeeks.TXT", "w");

    if (fp == NULL) {

        printf("n The file could "

               "not be opened");

        exit(1);

    }

    printf("n Provide feedback on "

           "this article: ");

    fgets(feedback, 100, stdin);

    for (i = 0; i < feedback[i]; i++)

        fputc(feedback[i], fp);

    if (ferror(fp)) {

        printf("n Error writing in file");

        exit(1);

    }

    fclose(fp);

}

Output:

Переполнение целых чисел

В языке Си нет никакой нативной обработки ошибок, а значит приходится пользоваться всякими костылями, чтобы эту обработку ошибок сделать. Например, в задании на квадратные уравнения вы пользовались чем-то, похожим на это:

квадратные уравнения

Оказывается, этот подход очень распространен в библиотечном коде проекта GNU. Например, если мы посмотрим на документацию функции `fopen(…)`

документацию функции

то есть, функция `fopen()` вернет:

  • Указатель типа `FILE*`, если программа отработала верно 
  • `NULL`, если во время выполнения возникла ошибка (например, файл который мы пытаемся открыть, не существует). Часть про `errno` пока что игнорируем.

А это значит, что обработка ошибок функции fopen должна происходить примерно вот так:

обработка ошибок функции fopen

Файл `blabla.txt` я специально не создал, а значит, моя программа ожидаемо закончится с сообщением `something went wrong with fopen()!`. Но бывают (почти всегда) ситуации, в которых об ошибке хочется знать чуть больше, чем просто то что она произошла. Тут и вступает в дело переменная `errno`, которая глобальная и определена во всех больших хедерах (в том числе в `stdlib.h` и `stdio.h`, но не в `string.h` — подумайте, почему). Где-то в недрах `errno.h` есть специальный массив строк под названием `sys_errlist`, к которому у вас, как у программиста, строго говоря, нет доступа. Строки в этом массиве — сообщения об ошибках, а `errno`, которая выставилась в результате ошибки — индекс в этом массиве, который указывает на описание ошибки, которая возникла. Существуют специальные функции, которые помогают вам выводить ошибки, например `perror()`. Перепишем наш код с обработкой ошибок через `perror()`

специальные функции

Теперь при ошибке, при открытии файла, вызовется функция `perror()`, она проверит чему равна переменная `errno`(которая, повторюсь, глобальная и хитро определена в заголовке как `stdio.h`, так и `errno.h`), и она найдет нужное сообщение об ошибке, то есть `main: No such file or directory`. В данном случае, я посчитал, что ошибка критическая, поскольку любое продолжение программы приведет к Segmentation fault, поэтому после вывода сообщения об ошибке, программа завершается с кодом errno (что, в принципе, необязательно). Узнать что делает аргумент функции `perror()` остается заданием для читателя. Узнать это можно в man-страницах ;)

Более лаконично этот код записывается так:

код записывается так

а ЕЩЕ более лаконично

более лаконичный код

(Это решение плохо тем, что иногда в случае ошибки функции возвращают `NULL`, а иногда `-1` в случае ошибок, такие штуки ВСЕГДА надо проверять в man-страницах.)

Важные замечания:

  • НЕ стоит проверять значение `errno` в случае успешного выполнения функции, потому что оно, строго говоря, не определено в этом случае (хотя, в большинстве случаев оно будет значения `Success`)
  • Если у вас несколько функций подряд, которые используют errno, то проверять наличие ошибки нужно после КАЖДОЙ из них.

проверка на наличие ошибок кода

Есть ли альтернативы? Ну, пока не придумали. Даже язык Go, по идеологии внук языка Cи, страдает от постоянных

язык Go страдает от ошибок

А этот язык сейчас один из самых популярных! (Хотя в Go2 придумали безумно интересный механизм обработки ошибок, кардинально отличающийся от того, что написано здесь и других прочих объектно-ориентированных exception-handling).

Существует механизм `SJLJ`, пользоваться которым без надобности не рекомендуется.

Берегите себя и пишите безопасный код ;)

It is quite common that errors may occur while reading data from a file in C++ or writing data to a file. For example, an error may arise due to the following:

  • When trying to read a file beyond indicator.
  • When trying to read a file that does not exist.
  • When trying to use a file that has not been opened.
  • When trying to use a file in an inappropriate mode i.e., writing data to a file that has been opened for reading.
  • When writing to a file that is write-protected i.e., trying to write to a read-only file.

Failure to check for errors then the program may behave abnormally therefore an unchecked error may result in premature termination for the program or incorrect output.

Below are some Error handling functions during file operations in C/C++:

ferror():

In C/C++, the library function ferror() is used to check for the error in the stream. Its prototype is written as:

 int ferror (FILE *stream);

The ferror() function checks for any error in the stream. It returns a value zero if no error has occurred and a non-zero value if there is an error. The error indication will last until the file is closed unless it is cleared by the clearerr() function. 

Below is the program to implement the use of ferror():

C

#include <stdio.h>

#include <stdlib.h>

int main()

{

    FILE* fp;

    char feedback[100];

    int i;

    fp = fopen("GeeksForGeeks.TXT", "w");

    if (fp == NULL) {

        printf("n The file could "

               "not be opened");

        exit(1);

    }

    printf("n Provide feedback on "

           "this article: ");

    fgets(feedback, 100, stdin);

    for (i = 0; i < feedback[i]; i++)

        fputc(feedback[i], fp);

    if (ferror(fp)) {

        printf("n Error writing in file");

        exit(1);

    }

    fclose(fp);

}

C++

#include <bits/stdc++.h>

int main()

{

    FILE* fp;

    char feedback[100];

    int i;

    fp = fopen("GeeksForGeeks.TXT", "w");

    if (fp == NULL) {

        printf("n The file could "

               "not be opened");

        exit(1);

    }

    printf("n Provide feedback on "

           "this article: ");

    fgets(feedback, 100, stdin);

    for (i = 0; i < feedback[i]; i++)

        fputc(feedback[i], fp);

    if (ferror(fp)) {

        printf("n Error writing in file");

        exit(1);

    }

    fclose(fp);

}

Output:

Explanation: After executing this code “Provide feedback on this article:“ will be displayed on the screen and after giving some feedback “Process exited after some seconds with return value 0″ will be displayed on the screen.

clearerr():

The function clearerr() is used to clear the end-of-file and error indicators for the stream. Its prototype can be given as:

void clearerr(FILE *stream);

The clearerr() clears the error for the stream pointed by the stream. The function is used because error indicators are not automatically cleared. Once the error indicator for a specific stream is set, operations on the stream continue to return an error value until clearerr(), fseek(), fsetpos(), or rewind() is called.

Below is the program to implement the use of clearerr():

C

#include <errno.h>

#include <stdio.h>

#include <stdlib.h>

int main()

{

    FILE* fp;

    char feedback[100];

    char c;

    fp = fopen("file.txt", "w");

    c = fgetc(fp);

    if (ferror(fp)) {

        printf("Error in reading from"

               " file : file.txtn");

    }

    clearerr(fp);

    if (ferror(fp)) {

        printf("Error in reading from "

               "file : file.txtn");

    }

    fclose(fp);

}

C++

#include <bits/stdc++.h>

int main()

{

    FILE* fp;

    char feedback[100];

    char c;

    fp = fopen("file.txt", "w");

    c = fgetc(fp);

    if (ferror(fp)) {

        printf("Error in reading from"

               " file : file.txtn");

    }

    clearerr(fp);

    if (ferror(fp)) {

        printf("Error in reading from "

               "file : file.txtn");

    }

    fclose(fp);

}

Output:

The function perror() stands for print error. In case of an error, the programmer can determine the type of error that has occurred using the perror() function. When perror() is called, then it displays a message describing the most recent error that occurred during a library function call or system call. Its prototype can be given as:

void perror (char*msg);

  • The perror() takes one argument which points to an optional user-defined message the message is printed first followed by a colon and the implementation-defined message that describes the most recent error.
  • If a call to perror() is made when no error has actually occurred then ‘No error’ will be displayed.
  • The most important thing to remember is that a call to perror() and nothing is done to deal with the error condition, then it is entirely up to the program to take action. For example, the program may prompt the user to do something such as terminate the program.
  • Usually, the program’s activities will be determined by checking the value of errno and the nature of the error.
  • In order to use the external constant errno, you must include the header file ERRNO.H

Below is the program given below illustrates the use of perror(). Here, assume that the file “file.txt” does not exist.

C

#include <errno.h>

#include <stdio.h>

#include <stdlib.h>

int main()

{

    FILE* fp;

    rename("file.txt", "newfile.txt");

    fp = fopen("file.txt", "r");

    if (fp == NULL) {

        perror("Error: ");

        return (-1);

    }

    fclose(fp);

    return (0);

}

C++

#include <bits/stdc++.h>

#include <errno.h>

int main()

{

    FILE* fp;

    rename("file.txt", "newfile.txt");

    fp = fopen("file.txt", "r");

    if (fp == NULL) {

        perror("Error: ");

        return (-1);

    }

    fclose(fp);

    return (0);

}

Output:

Last Updated :
10 Jan, 2022

Like Article

Save Article

Проверка ошибок при выполнении файловых операций

Программы,
представленные до настоящего момента,
предполагали, что во время файловых
операций В/В не происходят ошибки. К
сожалению, это сбывается не всегда.
Например, если вы открываете файл для
ввода, ваши программы должны проверить,
что файл существует. Аналогично, если
ваша программа пишет данные в файл, вам
необходимо убедиться, что операция
прошла успешно (к примеру, отсутствие
места на диске, скорее всего, помешает
записи данных). Чтобы помочь вашим
программам следить за ошибками, вы
можете использовать функцию fail файлового
объекта. Если в процессе файловой
операции ошибок не было, функция возвратит
ложь (0). Однако, если встретилась ошибка,
функция fail возвратит истину. Например,
если программа открывает файл, ей следует
использовать функцию fail, чтобы определить,
произошла ли ошибка, как это показано
ниже:

ifstream
input_file(«FILENAME.DAT»); 

if
(input_file.fail())

   cerr
<< «Ошибка
открытия
FILENAME.EXT» << endl; 

   exit(1); 

}

Таким
образом, программы должны убедиться,
что операции чтения и записи прошли
успешно. Следующая программа TEST_ALL.CPP
использует функцию fail для
проверки различных ошибочных ситуаций:

#include
<iostream.h>

#include
<fstream.h>

void
main(void)

   char
line[256] ; 

   ifstream
input_file(«BOOKINFO.DAT») ; 

   if
(input_file.fail()) cerr << «Ошибка
открытия
BOOKINFO.DAT»<< endl; 

   else

   { 

      while
((! input_file.eof()) && (! input_file.fail()))

      { 

         
input_file.getline(line, sizeof(line)) ; 

         if
(! input_file.fail()) cout << line <<
endl; 

      } 

   } 

}

Закрытие файла, если он больше не нужен

При
завершении вашей программы операционная
система закроет открытые ею файлы.
Однако, как правило, если вашей программе
файл больше не нужен, она должна его
закрыть. Для закрытия файла ваша программа
должна использовать функцию close, как
показано ниже:

input_file.close
();

Когда
вы закрываете файл, все данные, которые
ваша программа писала в этот файл,
сбрасываются на диск, и обновляется
запись каталога для этого файла.

Управление открытием файла

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

ifstream
output_file(«FILENAME.EXT», ios::app);

В
данном случае параметр ios::app указывает режим
открытия файла.
 По
мере усложнения ваших программ они
будут использовать сочетание значений
для режима открытия файла, которые
перечислены в табл. 34.

Таблица
34.
 Значения
режимов открытия.

Режим
открытия

Назначение

ios::app

Открывает
файл в режиме добавления, располагая
файловый указатель в конце файла.

ios::ate

Располагает файловый указатель в
конце файла.

ios::in

Указывает открыть файл для ввода .

ios::nocreate

Если
указанный файл не существует, не
создавать файл и возвратить ошибку.

ios::noreplace

Если файл существует, операция открытия
должна быть прервана и должна возвратить
ошибку.

ios::out

Указывает открыть файл для вывода.

ios::trunc

Сбрасывает (перезаписывает) содержим,
з существующего файла.

Следующая
операция открытия файла открывает файл
для вывода, используя режим ios::noreplace, чтобы
предотвратить перезапись существующего
файла:

ifstream
output_file(«FIlename.EXT», ios::out | ios::noreplace);

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]

  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #

Ввод-вывод и работа с файлами

Последнее обновление: 08.01.2023

Файл в языке Си рассматривается как неструктурированная последовательность байтов. С этой точки зрения в языке программирования C файлом может быть как
собственно файл на жестком диске, так и принтер, дисплей и другие подключаемые устройства ввода-вывода.

Как правило, взаимодействие между приложением и файлами производится посредством обмена блоков байт фиксированной длины (обычно длина представляет степень двойки — 256 или 512 байт).

При чтении из файла данные помещаются в буфер операционной системы, а затем побайтно передаются приложению.

При записи в файл данные накапливаются в буфере, а при заполнении буфера записываются на диск в виде единого блока байт.

Буферы представляют участки памяти, поэтому передача данных между приложением и буфером происходит довольно быстро в отличие от взаимодействия с
физическими устройствами типа принтера.

Файл вместе с предоставляемыми средствами буферизации представляет поток.

Язык программирования Си содержит необходимый функционал для работы с файлами и устройствами ввода-вывода.
Для применения его применения в программе необходимо подключить заголовочный файл stdio.h.

Открытие и закрытие потоков

Чтобы работать с потоком, его необходимо открыть. Для открытия потока применяется функция fopen(), которая имеет следующий прототип:

FILE * fopen(имя_файла, режим_открытия);

Первый параметр представляет имя открываемого файла, а второй задает режим открытия, от которого зависит, как файл может быть обработан.

Функция возвращает указатель на структуру, которая имеет тип FILE, определенный в том
же файле stdio.h. Этот указатель идентифицирует поток в программе и через него мы сможем обращаться к открытому файлу.

При открытии поток связывается со структурой

Режимы открытия

Каждый режим задается в виде набора символов. В частности, мы можем использовать следующие режимы:

  • «w»: текстовый файл открывается для записи. Если файл ранее существовал, то он пересоздается и записывается заново

  • «r»: текстовый файл открывается для чтения

  • «a»: текстовый файл открывается для добавления в него новых данных. Если файл существовал ранее, то данные просто добавляются

  • «w+»: текстовый файл создается для записи/записи. Если файл ранее существовал, то при первой записи после открытия он пересоздается и записывается заново.
    А при последующих записях после открытия данные добавляются в него без перезаписи.

  • «r+»: текстовый файл открывается для чтения/записи. Запись допустима в любом месте файла, кроме конца файла, то есть недопустимо увеличение размеров файла.

  • «a+»: текстовый файл открывается или создается (при его отсутствии) для чтения/записи. В отличие от режима w+ файл при открытии не пересоздается заново,
    а в отличии от режима r+ можно записывать данные в конец файла

  • «wb»: бинарный файл открывается для записи

  • «rb»: бинарный файл открывается для чтения

  • «ab»: бинарный файл открывается для дозаписи

  • «w+b»: бинарный файл создается для записи/чтения

  • «r+b»: бинарный файл открывается для чтения/записи

  • «a+b»: бинарный файл открывается или создается (при его отсутствии) для чтения/дозаписи

Режимы позволяют разграничить доступ к файлу и открыть его только для чтения или только для записи или совместить оба варианта. Кроме того,
на уровне режимов происходит разделение файлов на текстовые и бинарные. И программа будет обрабатывать файлы определенным образом, в зависимости какой
режим будет выбран — для текстовых или бинарных файлов. Неправильно заданный режим может привести к некорректной интерпретации файла.

Закрытие файла

После завершения работы с файлом его следует закрыть. Для этого применяется функция fclose():

int fclose(указатель_на_поток);

Единственный параметр функции представляет ранее полученный при открытии файла указатель на структуру FILE, связанный с файлом.

Функция возвращает число: 0 — в случае успешного выполнения и встроенное значение EOF в случае ошибки.

Например, откроем и закроем файл «data.txt», который расположен в одной и той же папке, что и выполняемая программа:

#include <stdio.h>

int main(void)
{
	FILE * fp = fopen(data.txt", "w");
	fclose(fp);
	return 0;
}

Если файл расположен в каком-то другом месте, то можно указать соответствующий относительный или абсолютный путь. Например, «С:\data.txt» (если файл data.txt
расположен в корне диска C).

Обработка ошибок

В процессе открытия или создания файла мы можем столкнуться с рядом ошибок, например, при открытии в режиме чтения не окажется подобного файла,
недостаточно памяти и т.д. И в случае возникновения ошибки функция fopen() возвращает значение NULL. Мы можем обработать
возникновение ошибки с помощью проверки результата функции:

#include <stdio.h>
 
int main(void)
{
	FILE * fp= fopen("data28.txt", "r");
	if(fp==NULL)
	{
		perror("Error occured while opening data28.txt");
		return 1;
	}
	
	fclose(fp);
	return 0;
}

Для вывода ошибки на консоль применяется встроенная функция perror(). И так как дальнейшие действия в программе в случае ошибки при открытии файла смысла не имеют,
то с помощью вызова return 1; завершаем работу приложения и возвращаем число, отличное от нуля (обычно ненулевое число рассматривается как код ошибки).

И если при попытке открытия файла по указанному пути его не окажется, то консоль выведет следующую ошибку:

Error occured while opening data28.txt: No such file or directory

Возможно, вам также будет интересно:

  • Функция ошибки нейронной сети это
  • Функция ошибки категориальная перекрестная энтропия
  • Функция ошибки для нейронной сети
  • Функция ошибки для линейной регрессии
  • Функция ошибки для задач регрессии

  • Понравилась статья? Поделить с друзьями:
    0 0 голоса
    Рейтинг статьи
    Подписаться
    Уведомить о
    guest

    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии