Skip to content

Latest commit

 

History

History
 
 

file_io

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 

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

Файловые дескрипторы

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

Как правило, при запуске процесса дескрипторы 0, 1 и 2 уже заняты стандартным потоком ввода (stdin), стандартным потоком вывода (stdout) и стандартным потоком ошибок (stderr).

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

Системные вызовы open/close

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

int open(const char *path, int oflag, ... /* mode_t mode */);

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

Основные параметры открытия файлов:

  • O_RDONLY - только для чтения;
  • O_WRONLY - только на запись;
  • O_RDWR - чтение и запись;
  • O_APPEND - запись в конец файла;
  • O_TRUNC- обнуление файла при открытии;
  • O_CREAT - создание файла, если не существует;
  • O_EXCL - создание файла только если он не существует.

В случае успеха возвращается неотрицательное число - дескриптор, в случае ошибки - значение -1.

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

Код ошибки последней операции хранится в глобальной целочисленной "переменной" errno (на самом деле, в современных реализациях - это макрос). Значения кодов можно определить из man-страниц, либо вывести текст ошибки с помощью функции perror.

Аттрибуты файлов в POSIX

В случае создания файла, обязательным параметром является набор POSIX-аттрибутов доступа к файлу. Как правило, они кодируются в восьмиричной системе исчисления в виде 0ugo, где u - права доступа для владельца файла, g - права доступа для всех пользователей группы файла, o - для остальных.

В восьмеричной записи значения от 0 до 7 соответствуют комбинации трёх бит:

00: ---
01: --x
02: -w-
03: -wx
04: r--
05: r-x
06: rw-
07: rwx

Чтение и запись в POSIX

Чтение и запись осуществляются с помощью системных вызовов:

ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);

Здесь buf - указатель на буфер данных, а count - максимальное количество байт для чтения/записи.

Как правило, в count указывается размер буфера данных при чтении, или количество данных при записи.

Возвращаемый тип ssize_t - целочисленный, определенный в диапазоне [-1...SSIZE_MAX], где SSIZE_MAX обычно совпадает с SIZE_MAX/2. Значение -1 используется в качестве признака ошибки, неотрицательные значения - количество записанных/прочитанных байт, которое может быть меньше, чем count.

Если системный вызов read вернул значение 0, то достигнут конец файла, либо был закрыт канал ввода.

Навигация по файлу в POSIX

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

off_t lseek(int fd, off_t offset, int whence);

Этот системный вызов предназначен для перемещения текущего указателя на файл.

Третий параметр whence один из трех стандартных способов перемещения:

  • SEEK_SET - указать явным образом позицию в файле;
  • SEEK_CUR - сместить указатель на определенное смещение относительно текущей позиции;
  • SEEK_END - сместить указатель на определенное смещение относительно конца файла.

Системный вызов lseek возвращает текущую позицию в файле, либо значение -1 в случае возникновения ошибки.

Тип off_t является знаковым, и по умолчанию 32-разрядным. Для того, чтобы уметь работать с файлами размером больше 2-х гигабайт, определяется значение переменной препроцессора до подключения заголовочных файлов:

#define _FILE_OFFSET_BITS 64

В этом случае, тип данных off_t становится 64-разрядным. Определить значение переменных препроцессора можно не меняя исходные тексты программы, передав компилятору опцию -DКЛЮЧ=ЗНАЧЕНИЕ:

# Скомпилировать программу с поддержкой больших файлов
gcc -D_FILE_OFFSET_BITS=64 legacy_source.c

Компиляция и запуск Windows-программ из Linux

Для кросс-компиляции используется компилятор gcc с целевой системой w64-mingw. Устанавливается из пакета:

  • mingw32-gcc - для Fedora
  • gcc-mingw-w64 - для Ubuntu
  • mingw32-cross-gcc - для openSUSE.

Скомпилировать программу для Windows можно командой:

$ i686-w64-mingw-gcc -m32 program.c
# На выходе получаем файл a.exe, а не a.out

Обратите внимание, что система Linux, в отличии от Windows различает регистр букв в файловой системе, поэтому нужно использовать стандартные заголовочные файлы WinAPI в нижнем регистре:

#include <windows.h> // правильно
#include <Windows.h> // скомпилируется в Windows, но не в Linux

Запустить полученный файл можно с помощью WINE:

$ WINEDEBUG=-all wine a.exe

Установка переменной окружения WINEDEBUG в значение -all приводит к тому, что в консоль не будет выводится отладочная информация, связанная с подсистемой wine, которая перемешивается с выводом самой программы.

Файловые дескрипторы и другие типы данных в WinAPI

В системе Windows для файловых дескрипторов используется тип HANDLE.

Для однобайтных строк используется тип LPCSTR, для многобайтных - LPCWSTR.

Функции WinAPI, которые имеют разные варианты поддержки строк, и работают с однобайтными функциями, заканчиваются на букву A, а функции, которые работают с многобайтными файлами - на букву W.

Для беззнакового 32-разрядного числа, используемого для флагов - тип DWORD.

Полный список типов данных в документации от Microsoft.

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

Файл можно открыть с помощью функции CreateFile.

Чтение и запись - с помощью функций ReadFile и WriteFile.

Навигация по файлу - с помощью функции SetFilePointerEx.