Лекция 18: Файлы в Си и Файловые системы Linux
"Я многим обязан своим родителям, особенно матери и отцу."
— Грег Норман
Основы файловых систем
Что такое файл?
Файл — это именованная область данных, хранящаяся на носителе информации (жесткий диск, SSD, флешка и т.д.), доступная по уникальному имени. ОС могут рассматривать как файлы и обрабатывать сходным образом и другие ресурсы, такие как устройства (клавиатура, экран, принтер), потоки данных и сетевые ресурсы.
Файловая система (ФС)
Файловая система (ФС) — это:
- Набор правил и структур, описывающих, как данные (файлы и каталоги) организуются и хранятся на носителе.
- Совокупность всех файлов, хранимых в компьютерной системе.
- (В контексте UNIX-подобных систем) Совокупность всех файлов на разделе диска или устройстве вместе с самими устройствами, которые также представлены как файлы.
По сути, ФС определяет порядок и способ именования, хранения и организации информации. Практические реализации файловых систем, например, NTFS
или ext4
, — это технический способ организации информации на определенном типе носителя в соответствии с принятыми правилами.
Структура файловой системы Linux
В отличие от операционных систем семейства Windows, где каждый логический или физический раздел диска имеет свою букву и независимую иерархию (например, C:\
, D:\
), в Linux используется единая иерархическая структура с одним корневым каталогом (/
). Все устройства монтируются в единое дерево каталогов.
Ключевые особенности структуры ФС Linux
- Единый корень (
/
): Вся файловая система начинается с корневого каталога. - Монтирование устройств: Дополнительные разделы диска, USB-флешки, оптические диски и другие носители подключаются (монтируются) к определенным каталогам внутри этого единого дерева, называемым точками монтирования (например,
/media
для съемных носителей или пользовательские точки монтирования). Это делает пути к данным более стабильными, независимо от того, на каком физическом устройстве они расположены. - "Все есть файл": В Linux все, включая дисковые накопители и устройства (клавиатура, экран, принтер и т.д.), представлено в виде файлов. Это ключевое понятие, позволяющее единообразно работать с различными ресурсами через файловые операции.
- Иерархичность: ФС Linux имеет единый корень (
/
). - Неизменность путей: Пути к данным остаются постоянными даже при изменении физической структуры дисков благодаря монтированию.
- Регистрозависимость: Имена файлов и каталогов в Linux чувствительны к регистру символов (например,
Document.txt
иdocument.txt
— это два разных файла).
Типы файлов в Linux (по первому символу в выводе команды ls -l
)
В выводе команды ls -l
первый символ строки показывает тип файла:
Символ | Тип файла | Описание |
---|---|---|
- | Обычный файл | Содержит данные (текст, программы, изображения и т.д.). |
d | Каталог | Специальный файл, содержащий список других файлов и каталогов. |
l | Символическая ссылка | Файл, указывающий на другой файл или каталог по имени. |
c | Символьное устройство | Представляет устройства, работающие с потоками данных (например, терминал). |
b | Блочное устройство | Представляет устройства, работающие с данными блоками (например, жесткий диск). |
p | Именованный канал FIFO | Используется для межпроцессного взаимодействия (первым вошел - первым вышел). |
s | Сокет | Используется для сетевого взаимодействия между процессами. |
Что такое Именованный канал FIFO?
Именованный канал FIFO (Named Pipe FIFO) — это специальный файл в файловой системе Linux, который работает как труба (pipe) для обмена данными между несвязанными процессами.
Простыми словами:
- У него есть имя, как у обычного файла.
- Данные пишутся в один конец и читаются из другого.
- Работает по принципу "первым вошел - первым вышел" (FIFO).
- Используется для связи между разными программами.
Основные каталоги в Linux
Вот краткое описание назначения некоторых важных стандартных каталогов корневой файловой системы Linux:
/
: Корень файловой системы./bin
: Основные исполняемые файлы (бинарники) пользовательских команд, необходимые для работы системы в однопользовательском режиме и доступные всем пользователям./sbin
: Системные исполняемые файлы, предназначенные для системного администрирования (часто доступны только суперпользователю root)./etc
: Конфигурационные файлы системы и установленных программ./dev
: Файлы устройств./proc
: Виртуальная файловая система, содержащая информацию о запущенных процессах и ресурсах системы (создается ядром в оперативной памяти)./sys
: Виртуальная файловая система, предоставляющая интерфейс к устройствам, подключенным к системе, и к ядру./var
: Переменные данные. Содержит файлы, которые часто изменяются в процессе работы системы (логи/var/log
, кеш/var/cache
, очереди печати и т.п.)./tmp
: Временные файлы. Доступен для записи всем пользователям, содержимое часто удаляется при перезагрузке./usr
: Содержит основную часть программного обеспечения пользователя, библиотеки, документацию./home
: Домашние каталоги пользователей. В каждом подкаталоге (например,/home/user1
) хранятся личные файлы и настройки конкретного пользователя./boot
: Файлы, необходимые для загрузки операционной системы (загрузчик, ядро)./lib
//lib64
: Системные библиотеки, необходимые для работы программ из/bin
и/sbin
./opt
: Дополнительное программное обеспечение, устанавливаемое сторонними производителями./mnt
: Традиционное место для временного монтирования файловых систем (например, сетевых дисков, других разделов)./media
: Точка монтирования для съемных носителей (USB-флешек, CD/DVD) при автоматическом монтировании в графической среде./srv
: Данные для сервисов, предоставляемых системой (например, файлы веб-сервера)./run
: Временные файлы, содержащие информацию о запущенных процессах с момента последней загрузки системы.
Имена файлов и папок в Linux
Ограничения и особенности имен файлов и каталогов в Linux:
- Максимальная длина имени файла/каталога обычно составляет 255 символов.
- Символ
/
запрещен в именах, так как он используется как разделитель компонентов пути. - Имя, начинающееся с точки (
.
), считается скрытым и не отображается по умолчанию командойls
. - Имена чувствительны к регистру (
file.txt
≠File.txt
). - Пробелы в именах допускаются, но считаются плохим тоном в профессиональной среде. Использование пробелов и некоторых спецсимволов допустимо, но не рекомендуется для удобства работы в командной строке (требуют экранирования или заключения в кавычки) и для совместимости при копировании на другие файловые системы.
Что такое Inode?
Inode (индексный дескриптор) — это ключевая структура данных в файловых системах Linux (и других UNIX-подобных систем), которая действует как "паспорт" или "карточка" для каждого файла или каталога на диске.
Inode хранит всю метаинформацию об объекте файловой системы, кроме его имени: * Тип файла (обычный файл, каталог, ссылка и т.д.). * Права доступа (кто может читать, писать, выполнять). * Владельца и группу. * Размер файла. * Время создания, последнего доступа и модификации. * И самое главное — указатели на блоки данных на диске, где физически хранится содержимое файла.
Каждому Inode присваивается уникальный номер в рамках конкретной файловой системы.
Имя файла в каталоге — это просто ссылка или "ярлык", который связывает понятное человеку имя с соответствующим номером Inode. Когда вы обращаетесь к файлу по имени, система использует это имя, чтобы найти нужный Inode, а затем по информации из Inode находит сами данные файла на диске.
Несколько жестких ссылок могут указывать на один и тот же Inode, потому что они являются разными именами, ссылающимися на одну и ту же "карточку" с информацией об одних и тех же данных.
Управление файловыми системами на уровне ОС
- /etc/fstab: Этот конфигурационный файл содержит информацию о файловых системах и параметрах их автоматического монтирования (подключения) при загрузке системы. Каждая строка описывает одну файловую систему, указывая устройство (или его UUID), точку монтирования, тип ФС, опции монтирования (например,
defaults
,auto
,noauto
,ro
- только чтение,rw
- чтение-запись) и параметры проверки и резервного копирования. - fsck: Утилита (
file system check
), используемая для проверки и восстановления целостности файловых систем на предмет ошибок и несоответствий. Важно: Применятьfsck
следует только к размонтированным файловым системам (за исключением корневой ФС, которую можно проверять в режиме только для чтения во время загрузки).
Основные команды Linux для работы с файлами
ls
: Вывести список файлов и каталогов. (ls -l
- подробный список)cd [каталог]
: Сменить текущий каталог.pwd
: Показать полный путь к текущему каталогу.mkdir [каталог]
: Создать новый каталог.rm [файл/каталог]
: Удалить файл или пустой каталог. (rm -r
- удалить каталог с содержимым).mv [источник] [назначение]
: Переместить или переименовать файл/каталог.cp [источник] [назначение]
: Скопировать файл/каталог.cat [файл]
: Вывести содержимое файла на экран (или объединить несколько файлов).grep [шаблон] [файл]
: Найти строки, соответствующие шаблону, в файле. (grep -i
- игнорировать регистр).ln [цель] [ссылка]
: Создать жесткую ссылку. (ln -s [цель] [ссылка]
- создать символическую ссылку).touch [файл]
: Создать пустой файл или обновить время последнего доступа/модификации.
Права доступа к файлам в Linux
Linux использует классическую систему прав доступа, основанную на понятиях владельца, группы и остальных пользователей. Права хранятся в специальном поле атрибутов файла.
Структура поля атрибутов файла
Поле атрибутов файла включает информацию о его типе и правах доступа. Права доступа кодируются в 9 битах, разбитых на три группы по 3 бита: * Права для владельца файла (User). * Права для группы, которой принадлежит файл (Group). * Права для всех остальных пользователей системы (Other).
Виды прав и их значение
В каждой из трех групп прав (user
, group
, other
) могут быть установлены следующие права:
Право | Обозначение | Числовое значение (восьмеричное) | Значение для Файла | Значение для Каталога |
---|---|---|---|---|
Чтение | r | 4 | Возможность просматривать содержимое файла. | Возможность просматривать список файлов в каталоге. |
Запись | w | 2 | Возможность изменять содержимое файла. | Возможность создавать, удалять и переименовывать файлы в каталоге. |
Исполнение | x | 1 | Возможность выполнять файл как программу. | Возможность входить в каталог и получать доступ к его файлам/подкаталогам. |
Права могут быть представлены в символьной форме (например, rwxr-xr--
) или в числовой (восьмеричной) форме, где каждое число (от 0 до 7) является суммой значений установленных прав в соответствующей группе (например, rwx
= 4+2+1=7, r-x
= 4+0+1=5). Права для владельца, группы и остальных записываются последовательно (например, 755
соответствует rwxr-xr-x
).
Особые признаки прав доступа
Существуют три особых признака, которые могут быть установлены для файлов или каталогов, влияющие на поведение при выполнении или доступе. Они также кодируются в поле атрибутов.
Признак | Символ (при наличии права x ) | Символ (при отсутствии права x ) | Числовое значение (восьмеричное, в начале) | Применение |
---|---|---|---|---|
Sticky-бит | t (в поле other ) | T (в поле other ) | 1 | Для каталогов: только владелец файла (или суперпользователь root) может удалять файлы из этого каталога, даже если у других есть право на запись в каталог. |
SUID (Set User ID) | s (в поле user ) | S (в поле user ) | 4 | Для исполняемых файлов: позволяет любому пользователю запустить файл с правами владельца этого файла. Потенциально опасно, использовать с осторожностью. |
SGID (Set Group ID) | s (в поле group ) | S (в поле group ) | 2 | Для исполняемых файлов: позволяет любому пользователю запустить файл с правами группы-владельца этого файла. Для каталогов: новые файлы, созданные в этом каталоге, будут иметь ту же группу, что и каталог. Потенциально опасно. |
При установке SUID или SGID символ x
в соответствующей группе прав заменяется на s
. Если исполняемое право не было установлено, вместо s
будет S
. Sticky-бит заменяет x
на t
в группе "остальные". Если исполняемое право не было установлено, вместо t
будет T
. Числовое представление особых признаков ставится перед основными тремя цифрами прав (например, 4755
означает установленный SUID и права 755
).
Работа с файлами в языке Си
В языке программирования Си для работы с файлами и устройствами ввода/вывода используется абстракция потоков данных (stream). Поток — это последовательность байтов, которая течет от источника к получателю. Внешние устройства (клавиатура, экран, принтер) и файлы на диске рассматриваются как потоки.
Потоки данных: Концепция
Поток данных в Си — это абстракция, позволяющая работать с различными источниками/приемниками данных (файлы, устройства) унифицированным способом. Библиотека стандартного ввода-вывода (stdio) управляет этими потоками, предоставляя буферизацию и преобразования данных для удобства программиста.
Пример (концептуальный):
#include <stdio.h>
int main() {
int character;
printf("Введите символ: "); // Вывод в поток stdout
character = getchar(); // Чтение из потока stdin
printf("Вы ввели: "); // Вывод в поток stdout
putchar(character); // Вывод символа в поток stdout
printf("\n");
return 0;
}
getchar()
читает один символ из стандартного потока ввода (stdin
), а printf()
и putchar()
записывают символы в стандартный поток вывода (stdout
). С точки зрения программы, она просто работает с потоками байтов, не "зная", что stdin
обычно привязан к клавиатуре, а stdout
— к экрану. Стандартные потоки
При запуске любой программы на Си автоматически открываются три стандартных потока, связанных с консолью:
stdin
: Стандартный поток ввода (обычно связан с клавиатурой).stdout
: Стандартный поток вывода (обычно связан с экраном для обычного вывода).stderr
: Стандартный поток ошибок (обычно связан с экраном для вывода сообщений об ошибках).
Поток stderr
часто небуферизован для немедленного отображения ошибок. Эти потоки представлены указателями типа FILE*
и объявлены в заголовочном файле stdio.h
. Функции консольного ввода-вывода, такие как scanf
, printf
, getchar
, putchar
, являются высокоуровневыми функциями, работающими со stdin
и stdout
.
Структура FILE
Для управления потоками библиотека stdio.h
использует структуру FILE
. Указатель на эту структуру (FILE *
) используется функциями для работы с файлами и потоками.
Структура FILE
(часто реализуемая как struct _IO_FILE
) содержит всю необходимую информацию о потоке: текущую позицию указателя чтения/записи, состояние буфера, флаги ошибок и конца файла. Программисты работают с файлами исключительно через указатели FILE*
и функции стандартной библиотеки, не обращаясь напрямую к полям структуры FILE
.
Общий алгоритм работы с файлом
Типичный цикл работы с файлом в Си выглядит так:
- Открыть поток (
fopen
). - Выполнить операции (чтение/запись). При необходимости можно изменять текущую позицию в потоке (
fseek
,rewind
). - Закрыть поток (
fclose
). Обязательно! Закрытие файла освобождает системные ресурсы и гарантирует сброс данных из буферов на диск.
Буферизация
Ввод-вывод в Си обычно буферизован. Это означает, что данные не передаются между программой и устройством напрямую при каждом вызове функции чтения/записи. Вместо этого данные сначала накапливаются в специальной области памяти — буфере — и только затем передаются на устройство или считываются с него блоками.
- Как работает: При записи данные сначала попадают в буфер вывода. Когда буфер заполняется, или принудительно (
fflush()
), или при закрытии потока, его содержимое записывается на устройство. При чтении блок данных считывается с устройства в буфер ввода, и затем программа читает данные из этого буфера по запросу. - Преимущество: Буферизация значительно ускоряет операции ввода-вывода, уменьшая количество медленных обращений к устройствам хранения данных.
- Принудительный сброс: Функция
fflush(FILE *stream)
принудительно записывает содержимое буфера вывода на устройство. Размер буфера по умолчанию определяется константойBUFSIZ
(обычно 512, 1024 или 4096 байт). Можно изменить поведение буферизации функциямиsetbuf()
иsetvbuf()
. Для потоков вводаfflush()
не определена стандартно, но может очищать буфер ввода в некоторых реализациях.
Режимы открытия файлов (функция fopen
)
FILE *fopen(const char *filename, const char *mode)
: Открывает файл с заданным именем (filename
) в указанном режиме (mode
) и возвращает указатель на FILE
или NULL
в случае ошибки. Обязательно проверяйте возвращаемое значение на NULL
!
Режим | Описание (текстовый/бинарный) |
---|---|
"r" | Открыть текстовый файл для чтения. Файл должен существовать. |
"w" | Открыть текстовый файл для записи. Создается новый файл или существующий очищается. |
"a" | Открыть текстовый файл для добавления (записи в конец). Создается, если не существует. |
"r+" | Открыть текстовый файл для чтения и записи. Файл должен существовать. |
"w+" | Открыть текстовый файл для чтения и записи. Создается или очищается. |
"a+" | Открыть текстовый файл для чтения и добавления (в конец). Создается, если не существует. |
"rb" | Открыть бинарный файл для чтения. |
"wb" | Открыть бинарный файл для записи. |
"ab" | Открыть biнарный файл для добавления. |
"r+b" | Открыть бинарный файл для чтения и записи. |
"w+b" | Открыть бинарный файл для чтения и записи. |
"a+b" | Открыть бинарный файл для чтения и добавления. |
Пример открытия и закрытия с проверкой:
#include <stdio.h>
#include <stdlib.h> // Для использования perror()
int main() {
FILE *fp;
char filename[] = "my_test_file.txt";
// Попытка открыть файл для чтения
if ((fp = fopen(filename, "r")) == NULL) {
// Если fopen вернул NULL, значит, произошла ошибка (например, файл не найден)
perror("Ошибка открытия файла для чтения"); // Выводит сообщение об ошибке из системы
// return 1; // Завершить программу с кодом ошибки
// Пример продолжения: попытка создать файл для записи
printf("Попробуем создать файл для записи...\n");
if ((fp = fopen(filename, "w")) == NULL) {
perror("Ошибка создания файла для записи");
return 1; // Завершить программу с кодом ошибки
}
printf("Файл успешно создан и открыт для записи.\n");
} else {
printf("Файл успешно открыт для чтения.\n");
}
// Здесь выполняются операции с файлом...
// Закрытие файла
if (fclose(fp) != 0) {
// Если fclose вернул ненулевое значение, произошла ошибка при закрытии
perror("Ошибка закрытия файла");
return 1; // Завершить программу с кодом ошибки
}
printf("Файл успешно закрыт.\n");
return 0; // Успешное завершение программы
}
Закрытие файла (функция fclose
)
int fclose(FILE *stream)
: Закрывает поток, связанный с указанным указателем stream
. Возвращает 0 при успехе, EOF
при ошибке. Обязательно закрывайте открытые файлы для освобождения системных ресурсов и гарантии записи буферизованных данных на диск.
Низкоуровневый файловый ввод-вывод (Файловые дескрипторы)
Параллельно с высокоуровневыми функциями stdio.h
, существует низкоуровневый интерфейс работы с файлами, основанный на системных вызовах и файловых дескрипторах.
- Файловый дескриптор (file descriptor, fd) — это целое число (
int
), которое ядро операционной системы присваивает каждому открытому файлу или устройству для конкретного процесса. Дескрипторы уникальны в пределах одного процесса. - Низкоуровневые функции (системные вызовы):
open()
,read()
,write()
,close()
,lseek()
. Они взаимодействуют напрямую с ядром ОС, минуя буферизацию, предоставляемуюstdio
(хотя ядро само может буферизовать). - Стандартные дескрипторы (автоматически открыты): 0 (
stdin
), 1 (stdout
), 2 (stderr
). Функцияfileno(FILE *stream)
возвращает файловый дескриптор, связанный с потокомFILE*
.
Текстовые и бинарные файлы
Файлы могут обрабатываться как текстовые или бинарные потоки. Режим открытия (t
или b
в строке режима fopen
) определяет, какие преобразования данных будут выполняться библиотекой Си.
Текстовый поток
Последовательность символов, организованная в строки, разделенные символом новой строки (\n
). При работе в текстовом режиме библиотека stdio
может выполнять преобразования символов конца строки (например, \n
в CR+LF в Windows). Чтение также может интерпретировать определенный символ (0x1A в Windows) как маркер конца файла (EOF).
Бинарный поток
Последовательность байтов без каких-либо преобразований. Данные читаются и записываются "как есть" (байт в байт). Используется для данных любых типов и позволяет прямой доступ по байтам.
Функции для работы с файлами в Си (stdio.h
)
- Основные функции:
Функция | Назначение |
---|---|
fopen | Открывает файл. |
fclose | Закрывает файл. |
fread | Читает блоки данных из бинарного потока. |
fwrite | Записывает блоки данных в бинарный поток. |
fgetc | Читает один символ из потока. |
fputc | Записывает один символ в поток. |
fscanf | Читает форматированные данные из потока. |
fprintf | Записывает форматированные данные в поток. |
fseek | Устанавливает позицию указателя в потоке. |
rewind | Устанавливает указатель в начало потока. |
feof | Проверяет флаг конца файла. |
ferror | Проверяет флаг ошибки потока. |
perror | Выводит сообщение об ошибке в stderr. |
- Дополнительные функции:
Функция | Назначение |
---|---|
fgets | Читает строку (до N символов или \n ). |
fputs | Записывает строку (без \0 ). |
fflush | Сбрасывает буфер вывода. |
setbuf | Настраивает буферизацию (откл или заданный буфер). |
setvbuf | Более гибкая настройка буферизации. |
ftell | Возвращает текущую позицию. |
clearerr | Сбрасывает флаги ошибки/конца файла. |
fileno | Возвращает файловый дескриптор (для низкоуровневых операций). |
Пример текстового ввода/вывода:
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *fp;
int num;
fp = fopen("data.txt", "w");
if (fp == NULL) { perror("Write error"); return 1; }
fprintf(fp, "%d", 123); fclose(fp);
fp = fopen("data.txt", "r");
if (fp == NULL) { perror("Read error"); return 1; }
fscanf(fp, "%d", &num); fclose(fp);
printf("Read number: %d\n", num);
return 0;
}
Пример бинарного ввода/вывода:
#include <stdio.h>
#include <stdlib.h>
struct Data { int id; double value; };
int main() {
FILE *fp;
struct Data data_to_write = {1, 3.14};
struct Data read_data;
fp = fopen("binary.bin", "wb");
if (fp == NULL) { perror("Write error"); return 1; }
fwrite(&data_to_write, sizeof(struct Data), 1, fp);
fclose(fp);
fp = fopen("binary.bin", "rb");
if (fp == NULL) { perror("Read error"); return 1; }
fread(&read_data, sizeof(struct Data), 1, fp);
fclose(fp);
printf("Read binary data: id=%d, value=%f\n", read_data.id, read_data.value);
return 0;
}
Позиционирование в файле (fseek
, rewind
, ftell
)
rewind(FILE *stream)
: В начало файла.fseek(FILE *stream, long int offset, int whence)
: Перемещает указатель.offset
- смещение,whence
- начало (SEEK_SET
,SEEK_CUR
,SEEK_END
). Вернет 0 или ошибку.ftell(FILE *stream)
: Текущая позиция (смещение от начала). Вернет -1L при ошибке.
Пример использования fseek
:
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *fp;
fp = fopen("seek_example.txt", "w+");
if (fp == NULL) { perror("Ошибка открытия"); return 1; }
fputs("abcdefghijklmnopqrstuvwxyz", fp); // Запись
fseek(fp, 5, SEEK_SET); // На 5 байт от начала
printf("Позиция (SEEK_SET + 5): %ld\n", ftell(fp)); // 5
int c = fgetc(fp); // Читаем 'f'
printf("Прочитан: %c\n", c); // f
printf("Позиция (после чтения): %ld\n", ftell(fp)); // 6
fseek(fp, -10, SEEK_END); // На 10 байт назад от конца
printf("Позиция (SEEK_END - 10): %ld\n", ftell(fp)); // 16 (26-10)
fclose(fp);
return 0;
}
Проверка состояния потока: конец файла и ошибки
Проверяйте состояние после операций!
feof(FILE *stream)
: Проверяет флаг конца файла (после неудачной попытки чтения). Не использовать как единственное условие цикла чтения, может привести к ошибкам. Лучше проверять результат самой функции чтения.ferror(FILE *stream)
: Проверяет флаг ошибки потока (устанавливается при ошибке, сбрасываетсяclearerr
).perror(const char *str)
: Выводит сообщение об ошибке вstderr
.str
+ системное сообщение поerrno
.
Пример использования feof
и ferror
:
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *fp;
int c;
fp = fopen("example_read.txt", "r");
if (fp == NULL) {
perror("Ошибка открытия example_read.txt"); // Используем perror при ошибке открытия
return 1;
}
printf("Читаем файл:\n");
while ((c = fgetc(fp)) != EOF) { // Корректный цикл: проверка результата fgetc
putchar(c);
}
// После цикла проверяем, почему вышли
if (feof(fp)) {
printf("\nДостигнут конец файла (feof).\n");
} else if (ferror(fp)) {
perror("Ошибка чтения из файла (ferror)"); // Используем perror при ошибке чтения
}
fclose(fp);
return 0;
}
Пример использования perror
при ошибке открытия:
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *fp;
// Попытка открыть файл, который не существует - errno установится
fp = fopen("non_existent_directory/non_existent_file.txt", "r");
if (fp == NULL) {
// perror прочитает errno и выведет системное сообщение
perror("Ошибка открытия файла"); // "Ошибка открытия файла" - наш префикс
return 1;
}
// Если открыт успешно, работаем с файлом...
fclose(fp);
return 0;
}
Все затаились в ожидании нового удара
А я сижу на лекции Иксанова Ильдара