Файловые дескрипторы - это целые числа, однозначно идентифицирующие открытые файлы в рамках одной программы.
Как правило, при запуске процесса дескрипторы 0
, 1
и 2
уже заняты стандартным потоком ввода (stdin
), стандартным потоком вывода (stdout
) и стандартным потоком ошибок (stderr
).
Файловые дескрипторы могут быть созданы с помощью операции создания или открытия файла.
Системный вызов 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
.
Код ошибки последней операции хранится в глобальной целочисленной "переменной" errno
(на самом деле, в современных реализациях - это макрос). Значения кодов можно определить из man
-страниц, либо вывести текст ошибки с помощью функции perror
.
В случае создания файла, обязательным параметром является набор POSIX-аттрибутов доступа к файлу. Как правило, они кодируются в восьмиричной системе исчисления в виде 0ugo
, где u
- права доступа для владельца файла, g
- права доступа для всех пользователей группы файла, o
- для остальных.
В восьмеричной записи значения от 0 до 7 соответствуют комбинации трёх бит:
00: ---
01: --x
02: -w-
03: -wx
04: r--
05: r-x
06: rw-
07: rwx
Чтение и запись осуществляются с помощью системных вызовов:
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
, то достигнут конец файла, либо был закрыт канал ввода.
Если файл является обычным, то можно выполнять перемещение текущей позиции в файле.
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
Для кросс-компиляции используется компилятор gcc с целевой системой w64-mingw
. Устанавливается из пакета:
mingw32-gcc
- для Fedoragcc-mingw-w64
- для Ubuntumingw32-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
, которая перемешивается с выводом самой программы.
В системе Windows для файловых дескрипторов используется тип HANDLE
.
Для однобайтных строк используется тип LPCSTR
, для многобайтных - LPCWSTR
.
Функции WinAPI, которые имеют разные варианты поддержки строк, и работают с однобайтными функциями, заканчиваются на букву A
, а функции, которые работают с многобайтными файлами - на букву W
.
Для беззнакового 32-разрядного числа, используемого для флагов - тип DWORD
.
Полный список типов данных в документации от Microsoft.
Файл можно открыть с помощью функции CreateFile.
Чтение и запись - с помощью функций ReadFile и WriteFile.
Навигация по файлу - с помощью функции SetFilePointerEx.