Книга: Linux программирование в примерах
4.5. Произвольный доступ: перемещения внутри файла
4.5. Произвольный доступ: перемещения внутри файла
До сих пор мы обсуждали последовательный ввод/вывод, при котором данные читаются или записываются с начала файла и продолжаются до его конца. Часто это все, что требуется программе. Однако, возможно осуществление произвольного ввода/вывода; т.е. читать данные из произвольного положения в файле без необходимости предварительного чтения всего, что находится перед этим местом.
Смещение дескриптора файла является положением внутри открытого файла, начиная с которого будет осуществляться следующая операция чтения или записи. Программа устанавливает смещение с помощью системного вызова lseek()
:
#include <sys/types.h> /* для off_t; POSIX */
#include <unistd.h> /* объявления lseek() и значений whence */
off_t lseek(int fd, off_t offset, int whence);
Тип off_t
(тип смещения) является знаковым целым, представляющим позиции байтов (смещений от начала) внутри файла. На 32-разрядных системах тип представлен обычно как long
. Однако, многие современные системы допускают очень большие файлы, в этом случае off_t
может быть более необычным типом, таким, как C99 int64_t
или какой-нибудь другой расширенный тип. lseek()
принимает три следующих аргумента.
int fd
Дескриптор открытого файла.
off_t offset
Позиция, в которую нужно переместиться. Интерпретация этого значения зависит от параметра whence
. offset
может быть положительным или отрицательным; отрицательные значения перемещают к началу файла; положительные значения перемещают к концу файла.
int whence
Описывает положение в файле, относительно которого отсчитывается offset
. См. табл. 4.4.
Таблица 4.4. Значения whence
для lseek()
Именованная константа | Значение | Комментарий |
---|---|---|
SEEK_SET |
0 | offset абсолютно, т.е. относительно начала файла |
SEEK_CUR |
1 | offset относительно текущей позиции в файле |
SEEK_END |
2 | offset относительно конца файла. |
Большое количество старого кода использует числовые значения, приведенные в табл. 4.4. Однако, любой новый код, который вы пишете, должен использовать символические имена, значение которых более ясно.
Смысл значений и их действие на положение в файле показаны на рис. 4.1. При условии, что файл содержит 3000 байтов и что перед каждым вызовом lseek()
текущим является смещение 2000 байтов, новое положение после каждого вызова будет следующим.
Рис. 4.1. Смещения для lseek()
Отрицательные смещения относительно начала файла бессмысленны; они вызывают ошибку «недействительный параметр».
Возвращаемое значение является новым положением в файле. Поэтому, чтобы получить ваше текущее местоположение в файле, используйте
off_t curpos;
...
curpos = lseek(fd, (off_t)0, SEEK_CUR);
Буква l
в lseek()
означает long
. lseek()
был введен в V7 Unix, когда размеры файлов были увеличены; в V6 был простой системный вызов seek()
. В результате большое количество старой документации (и кода) рассматривает параметр offset как имеющий тип long
, и вместо приведения к типу off_t
довольно часто можно видеть суффикс L в константных значениях смешений:
curpos = lseek(fd, 0L, SEEK_CUR);
На системах с компилятором стандартного С, где lseek()
объявлена с прототипом, такой старый код продолжает работать, поскольку компилятор автоматически преобразует 0L из long
в off_t
, если это различные типы.
Одной интересной и важной особенностью lseek()
является то, что она способна устанавливать смещение за концом файла. Любые данные, которые впоследствии записываются в это место, попадают в файл, но с образованием «интервала» или «дыры» между концом предыдущих данных файла и началом новых данных. Данные в промежутке читаются, как если бы они содержали все нули.
Следующая программа демонстрирует создание дыр. Она записывает три экземпляра struct
в начало, середину и дальний конец файла. Выбранные смешения (строки 16–18, третий элемент каждой структуры) произвольны, но достаточно большие для демонстрации особенности:
1 /* ch04-holes.c --- Демонстрация lseek() и дыр в файлах. */
2
3 #include <stdio.h> /* для fprintf(), stderr, BUFSIZ */
4 #include <errno.h> /* объявление errno */
5 #include <fcntl.h> /* для flags для open() */
6 #include <string.h> /* объявление strerror() */
7 #include <unistd.h> /* для ssize_t */
8 #include <sys/types.h> /* для off_t, etc. */
9 #include <sys/stat.h> /* для mode_t */
10
11 struct person {
12 char name[10]; /* имя */
13 char id[10]; /* идентификатор */
14 off_t pos; /* положение в файле для демонстрации */
15 } people[] = {
16 { "arnold", "123456789", 0 },
17 { "miriam", "987654321", 10240 },
18 { "joe", "192837465", 81920 },
19 };
20
21 int
22 main(int argc, char **argv)
23 {
24 int fd;
25 int i, j;
26
27 if (argc < 2) {
28 fprintf(stderr, "usage: %s filen", argv[0]);
29 return 1;
30 }
31
32 fd = open(argv[1], O_RDWR | O_CREAT | O_TRUNC, 0666);
33 if (fd < 0) {
34 fprintf(stderr, "%s: %s: cannot open for read/write: %sn",
35 argv[0], argv[1], strerror(errno));
36 return 1;
37 }
38
39 j = sizeof(people) / sizeof(people[0]); /* число элементов */
Строки 27–30 гарантируют, что программа была вызвана правильно. Строки 32–37 открывают именованный файл и проверяют успешность открытия.
Вычисление числа элементов j
массива в строке 39 использует отличный переносимый трюк число элементов является размером всего массива, поделенного на размер первого элемента. Красота этого способа в том, что он всегда верен: неважно, сколько элементов вы добавляете в массив или удаляете из него, компилятор это выяснит. Он не требует также завершающей сигнальной метки; т.е. элемента, в котором все поля содержат нули, NULL
или т.п.
Работа осуществляется в цикле (строки 41–55), который отыскивает смещение байтов, приведенное в каждой структуре (строка 42), а затем записывает всю структуру (строка 49):
41 for (i = 0; i < j; i++) {
42 if (lseek(fd, people[i].pos, SEEK_SET) < 0) {
43 fprintf(stderr, "%s: %s: seek error: %sn",
44 argv[0], argv[1], strerror(errno));
45 (void)close(fd);
46 return 1;
47 }
48
49 if (write(fd, &people[i], sizeof(people[i])) != sizeof(people[i])) {
50 fprintf(stderr, "%s: %s: write error: %sn",
51 argv[0], argv[1], strerror(errno));
52 (void)close(fd);
53 return 1;
54 }
55 }
56
57 /* здесь все нормально */
58 (void)close(fd);
59 return 0;
60 }
Вот результаты запуска программы:
$ ch04-holes peoplelist /* Запустить программу */
$ ls -ls peoplelist /* Показать использованные размеры и блоки */
16 -rw-r--r-- 1 arnold devel 81944 Mar 23 17:43 peoplelist
$ echo 81944 / 4096 | bc -l /* Показать блоки, если нет дыр */
20.00585937500000000000
Случайно мы знаем, что каждый дисковый блок файла использует 4096 байтов. (Откуда мы это знаем, обсуждается в разделе 5 4.2 «Получение информации о файле». Пока примите это как данное.) Финальная команда bc указывает, что файлу размером 81944 байтов нужен 21 дисковый блок. Однако, опция -s команды ls, которая сообщает нам, сколько блоков использует файл на самом деле, показывает, что файл использует лишь 16 блоков![48] Отсутствующие блоки в файле являются дырами. Это показано на рис. 4.2.
Рис. 4.2. Дыры в файле
ЗАМЕЧАНИЕ. ch04-holes.c
не осуществляет непосредственный двоичный ввод/вывод. Это хорошо демонстрирует красоту ввода/вывода с произвольным доступом: вы можете рассматривать дисковый файл, как если бы он был очень большим массивом двоичных структур данных.
На практике сохранение данных путем использования двоичного ввода/вывода является решением, которое необходимо тщательно взвесить. Например, что если предположить, что вам нужно переместить данные на систему, использующую отличный порядок байтов для целых? Или другие форматы чисел с плавающей точкой? Или на систему с другими требованиями выравнивания? Игнорирование подобных вопросов может стать слишком дорогостоящим.
- Что делать, если при установке принтера появляется сообщение Невозможно завершение операции. Подсистема печати недоступн...
- Эффективная работа с временными файлами сортировки
- Использование сервера Yaffil внутри процесса
- Единое имя файла параметров InterBase
- Параметры конфигурационного файла InterBase
- 9.4. Права доступа к squid
- 13. Зарабатываем на своих файлах: файлообменники, загружаеми получаем процент за скачивание
- Глава 29 Доступ к канальному уровню
- 10.5. Транзакции и пути доступа меню
- Листинг 15.11. Код для загрузки файла с Web-сервера
- Настройка доступа пользователей к рабочей книге
- 1.4.3. Вентиляторы и температура внутри корпуса