Как на самом деле feof () узнает, когда достигнут конец файла?

Файловая структура FAT32

Устройства внешней памяти в системе FAT32 имеют не байтовую, а блочную адресацию. Запись информации в устройство внешней памяти осуществляется блоками или секторами. Сектор – минимальная адресуемая единица хранения информации на внешних запоминающих устройствах. Как правило, размер сектора фиксирован и составляет 512 байт. Для увеличения адресного пространства устройств внешней памяти сектора объединяют в группы, называемые кластерами. Кластер – объединение нескольких секторов, которое может рассматриваться как самостоятельная единица, обладающая определёнными свойствами. Основным свойством кластера является его размер, измеряемый в количестве секторов или количестве байт. 

Файловая система FAT32 имеет следующую структуру.
Нумерация кластеров, используемых для записи файлов, ведется с 2. Как правило, кластер №2 используется корневым каталогом, а начиная с кластера №3 хранится массив данных. Сектора, используемые для хранения информации, представленной выше корневого каталога, в кластеры не объединяются.
Минимальный размер файла, занимаемый на диске, соответствует 1 кластеру.
Загрузочный сектор начинается следующей информацией:

  • EB 58 90 – безусловный переход и сигнатура;
  • 4D 53 44 4F 53 35 2E 30 MSDOS5.0;
  • 00 02 – количество байт в секторе (обычно 512);
  • 1 байт – количество секторов в кластере;
  • 2 байта – количество резервных секторов.

Кроме того, загрузочный сектор содержит следующую важную информацию:

  • 0x10 (1 байт) – количество таблиц FAT (обычно 2);
  • 0x20 (4 байта) – количество секторов на диске;
  • 0x2С (4 байта) – номер кластера корневого каталога;
  • 0x47 (11 байт) – метка тома;
  • 0x1FE (2 байта) – сигнатура загрузочного сектора (55 AA).

 
Сектор информации файловой системы содержит:

  • 0x00 (4 байта) – сигнатура (52 52 61 41);
  • 0x1E4 (4 байта) – сигнатура (72 72 41 61);
  • 0x1E8 (4 байта) – количество свободных кластеров, -1 если не известно;
  • 0x1EС (4 байта) – номер последнего записанного кластера;
  • 0x1FE (2 байта) – сигнатура (55 AA).

 
Таблица FAT содержит информацию о состоянии каждого кластера на диске. Младшие 2 байт таблицы FAT хранят F8 FF FF 0F FF FF FF FF (что соответствует состоянию кластеров 0 и 1, физически отсутствующих). Далее состояние каждого кластера содержит номер кластера, в котором продолжается текущий файл или следующую информацию:

  • 00 00 00 00 – кластер свободен;
  • FF FF FF 0F – конец текущего файла.

 
Корневой каталог содержит набор 32-битных записей информации о каждом файле, содержащих следующую информацию:

  • 8 байт – имя файла;
  • 3 байта – расширение файла;

 
Корневой каталог содержит набор 32-битных записей информации о каждом файле, содержащих следующую информацию:

  • 8 байт – имя файла;
  • 3 байта – расширение файла;
  • 1 байт – атрибут файла:
  • 1 байт – зарезервирован;
  • 1 байт – время создания (миллисекунды) (число от 0 до 199);
  • 2 байта – время создания (с точностью до 2с):
  • 2 байта – дата создания:
  • 2 байта – дата последнего доступа;
  • 2 байта – старшие 2 байта начального кластера;
  • 2 байта – время последней модификации;
  • 2 байта – дата последней модификации;
  • 2 байта – младшие 2 байта начального кластера;
  • 4 байта – размер файла (в байтах).

 
В случае работы с длинными именами файлов (включая русские имена) кодировка имени файла производится в системе кодировки UTF-16. При этого для кодирования каждого символа отводится 2 байта. При этом имя файла записывается в виде следующей структуры:

  • 1 байт последовательности;
  • 10 байт содержат младшие 5 символов имени файла;
  • 1 байт атрибут;
  • 1 байт резервный;
  • 1 байт – контрольная сумма имени DOS;
  • 12 байт содержат младшие 3 символа имени файла;
  • 2 байта – номер первого кластера;
  • остальные символы длинного имени.

Далее следует запись, включающая имя файла в формате 8.3 в обычном формате.

Открытие файла при помощи функции fopen

Файл открывается при помощи функции , которая возвращает информацию потока ввода-вывода, прикреплённого к указанному файлу или другому устройству, с которого идет чтение (или в который идет запись). В случае неудачи функция возвращает .

Схожая функция библиотеки Си выполняет аналогичную операцию после первого закрытия любого открытого потока, связанного с её параметрами.

Они объявляются как

FILE *fopen(const char *path, const char *mode);
FILE *freopen(const char *path, const char *mode, FILE *fp);

Функция по сути представляет собой «обертку» более высокого уровня системного вызова операционной системы Unix. Аналогично, является оберткой системного вызова Unix , а сама структура языка Си зачастую обращается к соответствующему файловому дескриптору Unix. В POSIX-окружении функция может использоваться для инициализации структуры файловым дескриптором. Тем не менее, файловые дескрипторы как исключительно Unix-концепция не представлены в стандарте языка Си.

Параметр (режим) для и должен быть строковый и начинаться с одной из следующих последовательностей:

режим описание начинает с..
r rb открывает для чтения начала
w wb открывает для записи (создаёт файл в случае его отсутствия). Удаляет содержимое и перезаписывает файл. начала
a ab открывает для добавления (создаёт файл в случае его отсутствия) конца
r+ rb+ r+b открывает для чтения и записи начала
w+ wb+ w+b открывает для чтения и записи. Удаляет содержимое и перезаписывает файл. начала
a+ ab+ a+b открывает для чтения и записи (создаёт файл в случае его отсутствия) конца

Значение «b» зарезервировано для двоичного режима С

Стандарт языка Си описывает два вида файлов — текстовые и двоичные — хотя операционная система не требует их различать (однако, для некоторых компиляторов, например LCC, указание ‘b’ при работе с бинарным файлом принципиально важно!). Текстовый файл — файл, содержащий текст, разбитый на строки при помощи некоторого разделяющего символа окончания строки или последовательности (в Unix — одиночный символ перевода строки; в Microsoft Windows за символом перевода строки следует знак возврата каретки)

При считывании байтов из текстового файла, символы конца строки обычно связываются (заменяются) с переводом строки для упрощения обработки. При записи текстового файла одиночный символ перевода строки перед записью связывается (заменяется) с специфичной для ОС последовательностью символов конца строки. Двоичный файл — файл, из которого байты считываются и выводятся в «сыром» виде без какого-либо связывания (подстановки).

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

Константы

В заголовочном файле определены следующие константы:

Имя Примечания
отрицательное целое число типа , используемое для обозначения конца файла
целое число, равное размеру буфера, используемое функцией
размер массива , достаточного для хранения имени любого файла, который может быть открыт
число файлов, которые могут быть открыты одновременно; как минимум равно 8
сокращение от «input/output fully buffered» (полностью буферируемый ввод-вывод); целое число, которое может быть передано функции для запроса блока буфера ввода и вывода для открытого потока
сокращение от «input/output line buffered» (линейно буферируемый ввод-вывод); целое число, которое может быть передано функции для запроса линии буфера ввода и вывода для открытого потока
сокращение от «input/output not buffered» (не буферируемый ввод-вывод); целое число, которое может быть передано функции для запроса небуферированого ввода и вывода для открытого потока
размер массива , достаточного для хранения временного имени файла, сгенерированного функцией
макрос, расширяющий константу ; то есть константу, представляющую значение указателя, гарантированно указывающего несуществующий адрес объекта в памяти
целое число, которое может быть передано функции для запроса позиционирования относительно текущей позиции в файле
целое число, которое может быть передано функции для запроса позиционирования относительно конца файла
целое число, которое может быть передано функции для запроса позиционирования относительно начала файла
максимальное число уникальных имён файлов, генерируемых функцией ; как минимум 25

RemarksRemarks

Подпрограммы feof (реализованные как функция и как макрос) определяют, был ли передан конец потока .The feof routine (implemented both as a function and as a macro) determines whether the end of stream has been passed. После передачи конца файла операции чтения возвращают индикатор конца файла до закрытия потока или до тех пор, пока не будет закрыт поток или пока небудут вызваны fsetpos, fseekили клеарерр .When the end of file is passed, read operations return an end-of-file indicator until the stream is closed or until rewind, fsetpos, fseek, or clearerr is called against it.

Например, если файл содержит 10 байт и вы читаете 10 байт из файла, feof возвратит 0, так как, даже если указатель файла находится в конце файла, вы не пытались прочитать его после конца.For example, if a file contains 10 bytes and you read 10 bytes from the file, feof will return 0 because, even though the file pointer is at the end of the file, you have not attempted to read beyond the end. Feof возвращает ненулевое значение, только если вы попытаетесь прочитать 11-й байт.Only after you try to read an 11th byte will feof return a nonzero value.

По умолчанию глобальное состояние этой функции ограничивается приложением.By default, this function’s global state is scoped to the application. Чтобы изменить это, см. раздел глобальное состояние в CRT.To change this, see Global state in the CRT.

Решение

Что бы ни делала библиотека C ++, в конце концов она должна читать из файла. Где-то в операционной системе есть фрагмент кода, который в конечном итоге обрабатывает это чтение. Он получает от файловой системы длину файла, сохраненную таким же образом, как файловая система хранит все остальное. Зная длину файла, позицию чтения и количество считываемых байтов, можно определить, что низкоуровневое чтение достигает конца файла.

Когда это определение сделано, оно передается вверх по стеку. В конце концов, он попадает в стандартную библиотеку, которая внутренне записывает, что конец файла достигнут. Когда запрос на чтение в библиотеку пытается пройти этот записанный конец, устанавливается флаг EOF и начнет возвращать истину.

10

Бинарные файлы

Текстовые файлы хранят данные в виде текста (sic!). Это значит, что если, например, мы записываем целое число 12345678 в файл, то
записывается 8 символов, а это 8 байт данных, несмотря на то, что число помещается в целый тип. Кроме того, вывод и ввод данных является форматированным, то
есть каждый раз, когда мы считываем число из файла или записываем в файл происходит трансформация числа в строку или обратно. Это затратные операции, которых можно избежать.

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

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

#define ERROR_FILE_OPEN -3

void main() {
	FILE *output = NULL;
	int number;

	output = fopen("D:/c/output.bin", "wb");
	if (output == NULL) {
		printf("Error opening file");
		getch();
		exit(ERROR_FILE_OPEN);
	}

	scanf("%d", &number);
	fwrite(&number, sizeof(int), 1, output);

	fclose(output);
	_getch();
}

Выполните программу и посмотрите содержимое файла output.bin. Число, которое ввёл пользователь записывается в файл непосредственно в бинарном виде. Можете
открыть файл в любом редакторе, поддерживающем представление в шестнадцатеричном виде (Total Commander, Far) и убедиться в этом.

Запись в файл осуществляется с помощью функции

size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );

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

Запись в бинарный файл объекта похожа на его отображение: берутся данные из оперативной памяти и пишутся как есть. Для считывания используется функция fread

size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );

Функция возвращает число удачно прочитанных элементов, которые помещаются по адресу ptr. Всего считывается count элементов по size байт. Давайте теперь считаем наше число
обратно в переменную.

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

#define ERROR_FILE_OPEN -3

void main() {
	FILE *input = NULL;
	int number;

	input = fopen("D:/c/output.bin", "rb");
	if (input == NULL) {
		printf("Error opening file");
		getch();
		exit(ERROR_FILE_OPEN);
	}

	fread(&number, sizeof(int), 1, input);
	printf("%d", number);

	fclose(input);
	_getch();
}

feof

Функция
возвращает истину, если конец файла достигнут. Функцию удобно использовать, когда необходимо пройти весь файл от начала до конца.
Пусть есть файл с текстовым содержимым 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();
}

AçıklamalarRemarks

Yordamın Fede (hem bir işlev olarak hem de bir makro olarak uygulanan) akışın sonunun geçtiğini belirler.The feof routine (implemented both as a function and as a macro) determines whether the end of stream has been passed. Dosya sonu geçirildiğinde, akış kapatılıncaya kadar veya geri sarma, fsetpos, fseekveya clearerr çağrılana kadar okuma işlemleri bir dosya sonu göstergesi döndürür.When the end of file is passed, read operations return an end-of-file indicator until the stream is closed or until rewind, fsetpos, fseek, or clearerr is called against it.

Örneğin, bir dosya 10 bayt içeriyorsa ve dosyadan 10 bayt okuduğunuzda, dosya işaretçisi dosyanın sonunda olsa bile, son olarak okumayı Denememekle birlikte 0 döndürür.For example, if a file contains 10 bytes and you read 10 bytes from the file, feof will return 0 because, even though the file pointer is at the end of the file, you have not attempted to read beyond the end. Yalnızca bir 11 baytını okumaya çalıştıktan sonra, sıfır dışında bir değer döndürerirsiniz.Only after you try to read an 11th byte will feof return a nonzero value.

Varsayılan olarak, bu işlevin genel durumu uygulamanın kapsamına alınır.By default, this function’s global state is scoped to the application. Bunu değiştirmek için bkz. CRT Içindeki genel durum.To change this, see Global state in the CRT.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Adblock
detector