Например, я могу сделать:
#include <stdio.h>
int do_something(FILE files[static 10])
{
...
}
int main(void)
{
FILE files[10] = {};
do_something(files);
}
В Linux это компилируется с Clang 18.1 и GCC 13.1 без проблем.
TLDR: Нет, это не обязательно должен быть полный тип. Существует несколько соответствующих реализаций, где FILE — непрозрачный тип. Вероятно, дублируются: C Указатель FILE, почему бы просто не использовать тип FILE напрямую?Я позволю другим решить, стоит ли закрывать этот вопрос как дубликат.
@AndrewHenle В комментарии к первому ответу на связанный вопрос говорится: «Примечание: C11 (n1570) 7.21.1 p2 требует, чтобы FILE был (полным) «типом объекта», поэтому файл FILE; действителен». Связанный вопрос не отвечает на мой вопрос, ОП просто хочет знать, зачем нужен указатель.
@Харит Цитата: «тип объекта, способный записывать всю информацию, необходимую для управления потоком». Нет никакого «полного».
@Харит, этот комментарий неверен. Возможно, его автора обмануло описание fpos_t далее в том же абзаце, где указано, что это полный тип объекта.





Я не смог найти гарантии этого в стандарте, но использование FILE не гарантирует работу, даже если гарантированно будет полный тип. 7.23.3.6 говорит в (проекте) ISO/TEC 9899:2023 (E):
Адрес объекта FILE, используемый для управления потоком, может быть значительный; копия объекта FILE не обязательно должна служить вместо оригинал.
Нет, FILE не обязательно должен быть полным типом. Например, стандарт C11 вполне ясен. В §7.21.1 Введение (к разделу §7.21 Ввод/вывод <stdio.h>) говорится:
¶2 Объявлены следующие типы:
size_t(описано в 7.19);FILEкоторый представляет собой тип объекта, способный записывать всю информацию, необходимую для управления потоком, включая индикатор положения файла, указатель на связанный с ним буфер (если таковой имеется), индикатор ошибки, который записывает, произошла ли ошибка чтения/записи, и индикатор конца файла, который фиксирует, достигнут ли конец файла; и
fpos_tкоторый представляет собой полный тип объекта, отличный от типа массива, способный записывать всю информацию, необходимую для однозначного указания каждой позиции в файле.
Обратите внимание, что fpos_t явно является «полным типом объекта», а FILE — это просто «тип объекта».
Разница в формулировках ясно показывает, что FILE не обязательно является полным типом объекта.
Во многих реализациях FILE — это полный тип объекта. Далеко нередки случаи, когда программное обеспечение использует внутреннюю структуру FILE в некоторых системах — Perl — одна из таких программ. (Я не могу представить себе систему, в которой базовый тип не является структурным типом.) А для макроверсий функций, таких как putchar() или getchar(), внутренние компоненты должны быть известны во время компиляции. В наши дни, когда код многопоточный, функции часто являются чистыми функциями, поскольку POSIX требует, чтобы они выполняли эквивалент flockfile() и funlockfile() в потоке файлов. Например, в macOS Sonoma 14.5 препроцессор оставляет эту функцию без изменений:
#include <stdio.h>
extern int get_one(void);
int get_one(void)
{
return getchar();
}
Другими словами, getchar() — это функция, а не макрос. Аналогично, putc(), getc() и putchar() — это просто вызовы функций, а не расширенные в макросы.
Однако <stdio.h> в macOS определяет подробную typedef struct __sFILE { … } FILE; структуру с 19 членами, 4 из которых являются (обнуляемыми) указателями на функции.
POSIX требует функций getc_unlocked(), FILE, getchar_unlocked() и putc_unlocked(). В macOS они развернуты как макросы:
#include <stdio.h>
extern int put_two(void);
int put_two(void)
{
int c = getc_unlocked(stdin);
if (c != EOF)
{
putc_unlocked(c, stdout);
putchar_unlocked(c);
c = getchar_unlocked();
}
return c;
}
предварительно обрабатывается, чтобы стать:
int put_two(void)
{
int c = (--(__stdinp)->_r < 0 ? __srget(__stdinp) : (int)(*(__stdinp)->_p++));
if (c != (-1))
{
__sputc(c, __stdoutp);
__sputc(c, __stdoutp);
c = (--(__stdinp)->_r < 0 ? __srget(__stdinp) : (int)(*(__stdinp)->_p++));
}
return c;
}
Все функции, ссылающиеся на объекты (
putchar_unlocked()), за исключением тех, имена которых заканчиваются наFILE *, должны вести себя так, как если бы они использовали_unlockedиflockfile()внутри себя для получения права собственности на эти (funlockfile()) объекты.
Если я посмотрю на все функции, составляющие API
FILE, я приду к выводу, что это должен быть непрозрачный тип. Я мог бы даже представить себе разные типыFILEразных размеров, например. чтобы разместить за ним разные размеры буфера или совершенно разные реализации.