Вперед объявить ФАЙЛ *

Как мне передать объявление FILE * в C? Обычно я делаю это с помощью struct MyType;, но, естественно, это невозможно.

Если поведение стандартов или компиляторов C отличается от C++, это также представляет интерес.

Обновление0

Почему я хочу сделать это в стороне: я спрашиваю, как перенаправить объявление типа non-struct/"typedef'd struct", чтобы я мог объявлять указатели на него. Очевидно, что использование void * и приведение его к исходному файлу немного хакерское.


person Matt Joiner    schedule 07.10.2010    source источник
comment
Почему вы хотите это сделать?   -  person Oliver Charlesworth    schedule 07.10.2010
comment
#include ‹stdio.h›?   -  person Tergiver    schedule 07.10.2010
comment
На самом деле не ясно, чего вы пытаетесь достичь. Не могли бы вы дать более подробную информацию?   -  person swestrup    schedule 07.10.2010
comment
Вам не нужно пересылать объявление FILE*. Просто включите stdio.h.   -  person JeremyP    schedule 07.10.2010
comment
Ваше обновление по-прежнему не объясняет, почему вы хотите это сделать. Почему бы вам не включить stdio.h? Дело не в том, что stdio.h часто меняется и сокращает время добавочной сборки.   -  person jamesdlin    schedule 07.10.2010
comment
@Oli, @jamesdlin: обычно рекомендуется избегать включения заголовков, кроме случаев, когда они действительно необходимы. Включение заголовка для класса не требуется, если достаточно предварительного объявления класса. Сведение к минимуму включения заголовков сводит к минимуму время сборки. Таким образом, попытка избежать включения <stdio.h> там, где это не нужно, просто следует хорошей практике. К сожалению для OP, это невозможно сделать с FILE, потому что это typedef.   -  person Dan Moulding    schedule 07.10.2010
comment
@Dan: Как подразумевал jamsedlin, это должно быть особенно подходящим для предварительно скомпилированных заголовков и т. д., а не для набора плавающих предварительных объявлений (не повторяйтесь и все такое).   -  person Oliver Charlesworth    schedule 07.10.2010
comment
@Oli: Предварительно скомпилированные заголовки решают проблему обходным, нестандартным способом. Разумное использование #include — это прямой способ сократить время компиляции, которое не зависит от нестандартных функций компилятора, зависящих от реализации.   -  person Dan Moulding    schedule 07.10.2010
comment
@Dan: В любом случае, многие вещи, вероятно, будут включать stdio.h, поэтому, по-моему, избегание его в заголовочном файле, который в нем нуждается, имеет сомнительную ценность. Если окажется, что это действительно имеет значение, то разбейте заголовочный файл.   -  person jamesdlin    schedule 07.10.2010
comment
@jamesdlin: Независимо от того, имеет ли какое-либо значение включение stdio.h, будет зависеть от конкретных обстоятельств, в которые никто из нас (за исключением OP) не в курсе. Все, что я говорю, это то, что сведение к минимуму использования #include в целом является хорошей практикой и может быть в данном случае веской причиной для OP изучить эту строку допрос.   -  person Dan Moulding    schedule 07.10.2010
comment
@Dan: Сведение к минимуму использования #include путем создания прототипов для функций, типов и т. д., о которых вы не знаете, является ужасно плохой практикой. Если вам нужен ФАЙЛ по какой-либо причине, это потому, что вы делаете что-то с функциями stdio и должны #include <stdio.h> в любом случае. Не включайте то, чем не пользуетесь, но если используете, включите.   -  person cHao    schedule 08.10.2010
comment
@cHao: Никто не говорит о создании прототипов функций. Это было бы плохой практикой. Но предварительное объявление типов, когда все, что нужно, это неполный тип, является хорошей практикой, и похоже, что это то, что пытался выполнить OP. Представьте себе структуру, определенную в заголовочном файле, членом которой является FILE *. Представьте себе далее, что многим исходным файлам необходимо использовать эту структуру, но только один исходный файл выполняет какой-либо файловый ввод-вывод с использованием FILE *. В этом случае была бы удобна возможность пересылки объявления FILE (а в противном случае #include <stdio.h> было бы не нужно).   -  person Dan Moulding    schedule 08.10.2010
comment
@Dan: Учтите, что если у вас есть много исходных файлов, использующих структуру, один из которых радикально отличается от других, то, вероятно, в вашем дизайне есть что-то довольно странное (даже неправильное). Структуры — это не просто место для хранения информации со всего приложения, даже в C. Если этот FILE * не нужен в 9/10 всего кода, который касается этой структуры, возможно, его не должно быть там в первое место.   -  person cHao    schedule 08.10.2010
comment
@cHao: Это довольно широкое утверждение. Итак, если у меня есть проект из 100 файлов, и в этом проекте у меня есть класс Logger, который имеет закрытый член FILE *, и только Logger.cpp выполняет любой файловый ввод-вывод, используя этот частный FILE *, тогда что-то не так с моим дизайном, потому что другой 99 файлов, все из которых используют Logger, не выполняют файловый ввод-вывод? Надеюсь, вы понимаете мою точку зрения. 99 файлов должны (косвенно) включать <cstdio> (поэтому он компилируется 99 раз), даже если только один файл фактически выполняет какой-либо файловый ввод-вывод.   -  person Dan Moulding    schedule 08.10.2010
comment
@ Дэн: Да. 99 файлов (для которых требуется Logger) косвенно включают <cstdio>. Как и должно быть, поскольку класс содержит FILE *. Вам нужен стандартный тип библиотеки, вы включаете заголовок, который его определяет; вы не наполовину декларацию. Период. Меня не волнует, если это увеличивает время сборки. Не нужно задаваться вопросом, используете ли вы стандартный struct tm или FILE или что-то еще. #include не только определяет тип, но и говорит, что я использую материал, определенный стандартной библиотекой, а не какое-то пользовательское дерьмо с тем же именем (поскольку переопределение типа вызовет ошибку компиляции).   -  person cHao    schedule 08.10.2010
comment
Вдобавок ко всему, файлы заголовков C обычно не требуют много времени для компиляции, поскольку они должны просто содержать объявления для типов, макросов и функций. (В отличие от заголовков C++, здесь нет шаблонов и т. п. для компиляции и кода для генерации.) Таким образом, включение заголовка не должно быть такой большой проблемой.   -  person cHao    schedule 08.10.2010
comment
@cHao: @Dan Molding при деньгах. Структура представляет собой контекст, передаваемый функциям, определенным во многих исходных файлах. Только один исходный файл работает с элементом FILE *. На самом деле структура контекста аналогичным образом объявляется в общедоступном интерфейсе библиотеки, поэтому внутренние компоненты скрыты. Мне не нужно или я не хочу, чтобы другие исходные файлы содержали подпрограммы ввода-вывода. Если бы FILE * не было объявлено заранее, пользовательский тип, содержащий его, был бы таким, что подразумевало бы дополнительное выделение памяти и потенциальные ошибки.   -  person Matt Joiner    schedule 08.10.2010
comment
@cHao: Я думаю, что теперь совершенно очевидно, почему было бы неплохо, если бы вы могли предварительно объявить FILE, и поэтому возник этот вопрос — как я объяснял @Oli и @jamedlin . К сожалению, вы не можете пересылать объявления typedefs. Но я повторю еще раз: использование предварительных объявлений, когда все, что нужно, это неполный тип, является разумной практикой. Включение вместо этого полного определения структуры через включение ее заголовка является ненужным и расточительным (хотя это обычная практика, потому что это проще, чем думать о том, подойдет ли только неполный тип).   -  person Dan Moulding    schedule 08.10.2010
comment
@Dan: И это проще, чем объяснять, почему у вас есть тип, который нигде явно не определен. И это проще, чем вспоминать, имеется ли незаконченный шрифт или настоящий. Извините, но не. Форвардные объявления предназначены для указания API в удобном для использования месте и для решения проблемы циклических зависимостей. Они не для того, чтобы сократить время сборки на секунду.   -  person cHao    schedule 08.10.2010
comment
@cHao: см. bit.ly/7frLLP, bit.ly/9qtWq7, bit.ly/cNLoWh, bit.ly/9FHb7E, bit.ly/bxErrJ и многие другие.   -  person Dan Moulding    schedule 08.10.2010
comment
@Dan: Обратите внимание, что каждый из этих примеров предназначен для C++. Заголовки C++ (особенно те, что содержат шаблоны) могут сильно увеличить время компиляции, и, возможно, их стоит пропустить. Но также обратите внимание, что все эти примеры предварительного объявления предназначены для пользовательских классов. Ни в одном из них не упоминается предварительное объявление любого типа в стандартных библиотеках C или C++.   -  person cHao    schedule 09.10.2010
comment
@cHao: время сборки не является проблемой в моей ситуации. Пожалуйста, сосредоточьтесь на подпрограммах ввода-вывода и беспорядке пространства имен, который мне не нужен в других исходных файлах. Единственное правильное замечание, которое, как я думаю, вы сделали, касается ошибочной интерпретации неполного типа неправильным образом. Если вы сделаете это, у вас будут более серьезные проблемы, о которых нужно беспокоиться. Также я думаю, что во время выполнения все взорвется огненным шаром. Это определенно не проблема в С++.   -  person Matt Joiner    schedule 09.10.2010


Ответы (6)


Вы не можете. В стандарте просто указано, что FILE — это «тип объекта, способный записывать всю информацию, необходимую для управления потоком»; это зависит от реализации, является ли это typedef из struct (имя которого вы все равно не знаете) или чем-то еще.

Единственный переносимый способ объявить FILE — это #include <stdio.h> (или <cstdio> в C++).

person Mike Seymour    schedule 07.10.2010
comment
Если вы абсолютно не можете включить stdio.h, единственная альтернатива — хранить указатели на void вместо FILE *, а затем другим модулям придется преобразовывать их туда и обратно в FILE *, чтобы использовать их. - person R.. GitHub STOP HELPING ICE; 07.10.2010
comment
Я думаю, что FILE обязательно typedef. Это не может быть struct, потому что тогда это будет struct FILE, а не просто FILE. - person Dan Moulding; 07.10.2010
comment
@Dan: да, ты прав, по крайней мере, на C. Мой мозг застрял на C++. - person Mike Seymour; 07.10.2010
comment
Процитируйте соответствующую часть стандарта (для C), в которой предполагается, что FILE может быть чем-то другим или чем-то другим. - person Matt Joiner; 08.10.2010
comment
@Matt: Цитата, которую я дал (из 7.19.1/2), говорит, что FILE — это тип объекта, поэтому это может быть любой тип объекта (арифметический, структура, объединение, массив или указатель). Разработчик библиотеки должен выбрать тип, который лучше всего подходит для них. - person Mike Seymour; 08.10.2010
comment
Учитывая, что FILE является typedef структуры во всех известных мне реализациях, можете ли вы объяснить, почему невозможно передать это объявление? - person Matt Joiner; 08.10.2010
comment
@Matt: Две причины: вы не знаете обо всех текущих и будущих реализациях, и для структуры нет стандартного имени (поскольку в стандарте вообще не указано, что это структура). Таким образом, даже если вы предполагаете, что это будет структура для каждой реализации, которую вы хотите поддерживать, вы не можете предварительно объявить структуру, поскольку не знаете ее имени. - person Mike Seymour; 08.10.2010
comment
@Mike Seymour: Можете ли вы процитировать стандарт по прямому объявлению struct typedefs? - person Matt Joiner; 09.10.2010
comment
@Matt: в стандарте ничего не говорится о предварительном объявлении typedef, потому что такой концепции не существует. Как я сказал (с кавычками) в другом комментарии, единственный способ ввести имя typedef — это объявление typedef, которое должно включать спецификатор типа. Боюсь, вам действительно придется включать заголовочный файл и мириться с дополнительными миллисекундами времени компиляции. - person Mike Seymour; 09.10.2010

Если вы #include <stdio.h>, вы должны получить вместе с ним FILE typedef. Это единственный действительно безопасный и переносимый способ - у вас не может быть typedef без типа для псевдонима, и нет никакой гарантии относительно того, какой тип FILE псевдонимов, поэтому каждый компилятор, libc или что-то еще может иметь другой. Но вам нужно, чтобы тип был правильным, если кто-то действительно захочет #include <stdio.h>, чтобы несогласованные определения не вызвали ошибку.

Редактировать:

Теперь, когда я думаю об этом, я могу придумать еще один способ. Это не typedef, это злой макрос, который работает, перехватывая определение «FILE». Я бы не рекомендовал его только по этой причине. Но это может сработать для того, что вам нужно.

#ifdef USES_REAL_FILE_TYPE
#include <stdio.h>
#else
#define FILE void
#endif

/* declare your struct here */

#ifndef USES_REAL_FILE_TYPE
#undef FILE
#endif

Затем #define USES_REAL_FILE_TYPE перед включением файла в любой код, где вам нужен настоящий FILE *, а остальная часть кода просто увидит указатель как void *.

Я не даю гарантий, что это ничего не испортит. В частности, он сломается в любом случае, когда вы захотите узнать что-нибудь реальное о таком поддельном типе, и весь код, который касается указателя, может нуждаться в этом #define. Но если вы категорически против «ненужных» #includes, это единственный способ получить FILE * без вмешательства в stdio. Вы не сможете перенаправить объявление typedef.

Редактировать2:

Хорошо, я проверил, чтобы убедиться. Не уверен, насколько это стандартно или что с ним можно сделать, но...

typedef FILE;

работает как в Visual C, так и в GCC, но только при компиляции кода C. Может показаться, что в стандарте С++ где-то явно сказано, что у вас не может быть typedef без типа. C, однако, нет.

Тем не менее, похоже, что тип не может быть объявлен заранее, во всяком случае, не в GCC. Если вы попытаетесь typedef int FILE; сразу после этого, выдается ошибка о конфликте определений типов. Однако VS, похоже, позволяет это, если он имеет целочисленный тип. Кажется, typedef X действительно означает typedef int X в VS (и, видимо, в C99). В любом случае, GCC не позволит вам переопределить typedef даже для точно такого же типа.

person cHao    schedule 07.10.2010
comment
Пожалуйста, обоснуйте, почему это единственный портативный способ. - person Matt Joiner; 08.10.2010
comment
Потому что для того, чтобы объявить FILE, вы должны были бы знать, какой это тип за кулисами, чтобы каждый, кто на самом деле делал #include <stdio.h>, не получил ошибку компиляции (потому что два типа не совпадают). Однако дело в том, что фактический тип, стоящий за FILE, не указан ни в одном стандарте, поэтому у каждого компилятора может быть свой тип. - person cHao; 08.10.2010
comment
Что ж, я не могу поставить вам минус, потому что вы правы, но это не тот ответ, который я хотел. Возможно, вы могли бы объяснить, почему невозможно переслать объявление typedef, цитируя стандарт...? - person Matt Joiner; 08.10.2010
comment
@Matt: в стандарте прямо не сказано, что вы не можете; это просто не дает возможности сделать это. 6.7.7 описывает единственный способ ввести имя typedef: в объявлении, спецификатором класса хранения которого является typedef, каждый декларатор определяет идентификатор как имя typedef, обозначающее тип, указанный для идентификатора. 6.7.2/2 говорит, что по крайней мере один спецификатор типа должен быть указан в спецификаторах объявлений в каждом объявлении, поэтому невозможно объявить имя typedef без указания его типа. - person Mike Seymour; 08.10.2010
comment
@Мэтт Джойнер: Обновлено. Это немного некрасиво, но не так некрасиво, как кастинг повсюду. - person cHao; 09.10.2010
comment
@cHao: я считал, что что-то подобное возможно. На самом деле гораздо лучше, если вы ограничите это исходным файлом с IO: поместите typedef void *FILE_forward; в структуру и #define FILE_forward FILE * в исходный файл. Я бы все же предпочел окончательный ответ, который объясняет, почему я не могу объявить typedef (из стандарта). - person Matt Joiner; 09.10.2010
comment
@Matt: снова обновлено. Не спрашивайте меня как и почему, но... :) - person cHao; 09.10.2010

FILE - это typedef вокруг структуры, которую вы не должны слишком много исследовать (например, вы не должны играть с данными, стоящими за дескриптором WinAPI), если только через ее специальную функцию API.

Предварительное объявление?

Упреждающее объявление позволяет объявить указатель (или ссылку на C++) на тип и компилировать это объявление до тех пор, пока символ не используется (например, упреждающее объявление символа в вашем заголовке, а затем включение заголовок, где символ правильно объявлен в источнике, использующем его).

Итак, предварительное объявление включает в себя средства:

  • более быстрая компиляция
  • меньше связи

Chuck Typedef против прямого объявления?

Проблема с typedef заключается в том, что с ними сложно работать, потому что, как вы обнаружили, вы не можете предварительно объявить их.

Таким образом, вы не можете предварительно объявить FILE, и вы не можете предварительно объявить std::string. Таким образом, у вас нет другого выбора, кроме как включить заголовок для их обработки.

(Вот почему я ненавижу шаблон typedef struct { /* ... */ } MyTypedefedType ; из C, вторгающийся в код C++: он бесполезен в C++ и препятствует прямому объявлению.)

Стандартные символы с предварительным объявлением?

Хорошая часть заключается в том, что если символы являются стандартными, включение их заголовка не должно быть слишком болезненным. Связывание не является большой проблемой, и если оно несколько замедлит компиляцию, даже это можно сделать безболезненно с помощью предварительно скомпилированных заголовков.

<iosfwd> : Некоторые думали о тебе!

Стандартная библиотека C++ предлагает заголовок <iosfwd>.

Вместо того, чтобы включать какие-либо (или все) заголовки потоков C++, вы можете включить <iosfwd>, если вам нужно только предварительное объявление.

person paercebal    schedule 09.10.2010

FILE является системно-зависимым typedef. Вы не должны заботиться о том, как конкретная структура определена или даже названа. Но вы всегда можете заглянуть в свой /usr/include/stdio.h файл :)

person Nikolai Fetissov    schedule 07.10.2010
comment
Это кажется лучшим решением. Это или предложение @R.. об использовании пустоты * и выполнении кастинга. К сожалению, использование системных имен структур требует гораздо больше усилий, чем оно того стоит. - person Matt Joiner; 08.10.2010
comment
@Matt: Если вы скопируете объявление из заголовка вашей системы (чего я бы не советовал из соображений переносимости), имейте в виду, что повторяющиеся объявления typedef не разрешены в C, хотя они есть в C++. Это делает ошибкой включение вашего объявления и <stdio.h> в одну и ту же единицу компиляции C. - person Mike Seymour; 08.10.2010
comment
Так что хорошей идеей при реализации библиотеки C является то, чтобы все внутренние типы были структурами. Тогда предварительная декларация будет работать. Разве libpng не реализован таким образом? Поэтому вместо typedef void* handle; написать дескриптор структуры; в заголовке и структуре handle{void* h_impl;}; в источнике - person user877329; 30.05.2012

Как уже отмечалось, не существует переносимого способа для прямого объявления структуры FILE или определения типа.

Однако можно изменить интерфейс собственного объекта, чтобы он полагался на простые целые числа, а затем использовать функцию fileno (также доступную через #include <stdlib.h>).

Подробные действия
0. Найдите текущий интерфейс. Например:
void myprint(FILE* stream, ...);
1. Используйте целочисленный дескриптор файла (fd) вместо FILE*:
void myprint(int stream_fd, ...);
2. Вызовите новый интерфейс с fileno вместо FILE*:
myprint(fileno(stream));

Недостатком, однако, является то, что ваша реализация (myprint в приведенном выше примере) должна быть переписана с использованием файлового дескриптора вместо FILE* для фактических процедур ввода-вывода. Альтернативой переписыванию реализации является простое fdopen в FILE использование данного дескриптора.

void myprint(int stream_fd, ...)
{
  FILE *const stream = fdopen(stream_fd);
  /* your existing implementation follows */
  fclose(stream);
}

Вышеупомянутое, в свою очередь, заставляет вас задуматься о том, где «владеть» ресурсом, чтобы закрыть FILE, когда он больше не нужен. Часто последовательность открытия/закрытия просто прекрасна (как показано выше), однако в более сложных случаях необходимо настроить реализацию (чего мы старались избежать), открыв файл в режиме добавления и т. д.

person hfp    schedule 06.01.2016

FILE* — непрозрачный тип. Таким образом, это должно теоретически работать.

typedef struct FILE_impl_but_including_stdio_h_is_best FILE;
person Maister    schedule 07.10.2010
comment
Вы бы подумали, не так ли. Но это не обязательно непрозрачная структура. Если у вас есть доступ к /usr/include/stdio.h на Macintosh, вы увидите, что он совсем не непрозрачный. - person JeremyP; 07.10.2010
comment
Это может быть так, но опять же, указатель есть указатель (технически не совсем правильно, но эй). stdio заботится только о FILE*. - person Maister; 07.10.2010
comment
Это вызовет ошибку, если вы позже включите stdio.h в любой файл, где виден этот typedef. - person R.. GitHub STOP HELPING ICE; 07.10.2010