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

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

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

Update0

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

 Tergiver07 окт. 2010 г., 16:02
#include <stdio.h>?
 Oliver Charlesworth07 окт. 2010 г., 15:57
Зачем тебе это делать?
 jamesdlin07 окт. 2010 г., 19:06
Ваше обновление до сих пор не объясняет, почему вы хотите это сделать. Почему бы вам не включитьstdio.h? Это не так, как если быstdio.h часто меняется и тянет время инкрементной сборки.
 JeremyP07 окт. 2010 г., 16:49
Вам не нужно пересылать объявить ФАЙЛ *. Просто включите stdio.h.
 Oliver Charlesworth07 окт. 2010 г., 21:15
@Dan: Как подразумевает jamsedlin, это то, что должно быть особенно доступно предварительно скомпилированным заголовкам и т. Д., А не куче плавающих предварительных объявлений (не повторяйте себя и все такое).
 cHao08 окт. 2010 г., 15:29
Вдобавок ко всему, заголовочные файлы C обычно не требуют много времени для компиляции, поскольку они должны просто содержать объявления для типов, макросов и функций. (В отличие от заголовков C ++, здесь нет шаблонов и тому подобного для компиляции, и нет кода для генерации.) Поэтому включение заголовка не должно быть такой большой нагрузкой.
 Dan Moulding08 окт. 2010 г., 14:24
@cHao: это довольно широкое утверждение. Так что, если у меня есть проект из 100 файлов, и в этом проекте у меня есть класс Logger, который имеетFILE * член, и только Logger.cpp делает любой файловый ввод-вывод, используя этот закрытыйFILE *что-то не так с моим дизайном, потому что остальные 99 файлов, все из которых используютLogger, не делайте никаких файловых операций ввода-вывода? Я надеюсь, вы понимаете мою точку зрения. 99 файлов должны (косвенно) включать<cstdio> (таким образом, он компилируется 99 раз), хотя только один файл фактически выполняет какой-либо файловый ввод-вывод.
 Dan Moulding08 окт. 2010 г., 20:13
@cHao: я думаю, что теперь совершенно очевидно, почему было бы неплохо, если бы вымог вперед объявитьFILEи является причиной, по которой этот вопрос возник - так же, как я объяснял @Oli и @jamedlin. К сожалению, вы не можете переслать объявитьtypedefs. Но я скажу это снова: используя предварительные декларации,когда все, что нужно, является неполным типомУмная практика. Вместо этого включение полного определения структуры посредством включения ее заголовка является ненужным и расточительным (хотя и обычной практикой, поскольку это проще, чем думать о том, подойдет ли неполный тип).
 Dan Moulding08 окт. 2010 г., 21:36
@cHao: см.bit.ly/7frLLP, bit.ly/9qtWq7, bit.ly/cNLoWh, bit.ly/9FHb7E, bit.ly/bxErrJи бесчисленное множество других.
 cHao08 окт. 2010 г., 02:29
@Dan: минимизация использования#include взбивая свои прототипы для функций, типов и т. д. вы не знаете, это ужасноплохой практика. Если вам нужен файл, длялюбой причина в том, что вы делаете что-то с функциями stdio и должны#include <stdio.h> тем не мение, Не включайте вещи, которые вы не используете, но если вы используете это,включи это.
 cHao08 окт. 2010 г., 22:02
@ Дан: обратите внимание, чтокаждый из этих примеров для C ++. Заголовки C ++ (особенно с шаблонами) могут добавить много времени для компиляции, и, возможно, стоит пропустить их, если это возможно. Но также обратите внимание, что все эти примеры прямого объявления предназначены для пользовательских классов.Ни один из них упоминает о предстоящем объявлении любого типа в стандартных библиотеках C или C ++.
 Dan Moulding08 окт. 2010 г., 03:21
@cHao: никто не говорит о создании функциональных прототипов. Тотбыло бы быть плохой практикой Но предварительное объявление типов, когда все, что нужно, является неполным типом, является хорошей практикой, и это то, что, по-видимому, пытается выполнить ФП. Представьте себе структуру, определенную в заголовочном файле, которая имеетFILE * как член. Представь дальше, чтомного исходные файлы должны использовать эту структуру, но толькоодин исходный файл выполняет любые операции ввода-вывода, используяFILE *, В этом случае возможность переслать объявитьFILE было бы удобно (и#include <stdio.h> иначе было бы ненужно).
 swestrup07 окт. 2010 г., 16:08
Это действительно не ясно, что вы пытаетесь достичь. Не могли бы вы дать более подробную информацию?
 jamesdlin07 окт. 2010 г., 21:31
@ Дэн: много вещей, вероятно, будет включатьstdio.h во всяком случае, так что IMO, избегая этого в заголовочном файле, который нуждается в этом, имеет сомнительную ценность. Если это действительно имеет значение, разбейте заголовочный файл.
 cHao08 окт. 2010 г., 06:00
@Dan: Учтите, что если у вас есть много исходных файлов, использующих структуру, один из которых делает это радикально иначе, чем другие, то, вероятно, в вашем дизайне есть что-то довольно странное (даже неправильное). Структуры - это не просто место для хранения материалов со всего приложения, даже в C. Если этоFILE * не требуется в 9/10 всего кода, который затрагивает эту структуру, возможно, он не должен быть там в первую очередь.
 cHao08 окт. 2010 г., 15:02
@ Дан: Да. 99 файлов (которые нужны Logger) косвенно включают<cstdio>, Как они должны, так как класс содержитFILE *, Вы хотите стандартный тип библиотеки, вы включаете заголовок, который определяет его; Вы не наполовину декларация. Период. Мне все равно, если это увеличивает время сборки. Не нужно удивляться, используете ли вы стандартstruct tm или жеFILE или что угодно.#include он не только определяет тип, но и говорит: «Я использую материал, определенный стандартной библиотекой, а не какую-то пользовательскую хрень с тем же именем» (поскольку переопределение типа вызовет ошибку компиляции).
 Dan Moulding07 окт. 2010 г., 21:38
@jamesdlin: в том числе или нетstdio.h В любом случае, это зависит от конкретных обстоятельств, к которым никто из нас (за исключением ОП) не имеет отношения. Все, что я говорю, - это минимизация использования#include в общем это хорошая практика иможет бытьВ этом случае у действительной причины для ОП исследовать эту линию вопросов.
 cHao08 окт. 2010 г., 20:42
@Dan: И проще, чем объяснять, почему у вас есть тип, который явно нигде не определен. И проще, чем вспоминать ли тыиметь неполный тип или реальная сделка. Извините, но не. Форвардные объявления предназначены для указания API в удобном для использования месте и для решения проблемы циклических зависимостей. Они не для того, чтобы экономить время на сборке.
 Dan Moulding07 окт. 2010 г., 21:07
@Oli, @jamesdlin: как правило, рекомендуется не включать заголовки, кроме случаев, когда они действительно необходимы. Включать заголовок для класса не нужно, если будет делать предварительное объявление класса. Минимизация включения заголовка минимизирует время сборки. Таким образом, пытаясь избежать в том числе<stdio.h> где это не нужно - просто следовать хорошей практике. К сожалению для OP, это невозможно сделать сFILE потому что этоtypedef.
 Dan Moulding07 окт. 2010 г., 21:22
@Oli: Предварительно скомпилированные заголовки решают проблему нестандартным образом. Разумное использование#include это прямой способ сократить время компиляции, которое не зависит от нестандартных, специфичных для реализации функций компилятора.
 Matt Joiner08 окт. 2010 г., 16:13
@CHao: @ Дэн Молдинг на деньги. Структура является контекстом, передаваемым функциям, определенным во многих исходных файлах. Только один исходный файл работает на членеFILE *, На самом деле, структура контекста аналогичным образом объявляется в публичном интерфейсе библиотеки, поэтому внутренние компоненты скрыты. Я не нуждаюсь или не хочу, чтобы другие исходные файлы содержали процедуры ввода-вывода. ЕслиFILE * не было объявлено заранее, определяемый пользователем тип, содержащий его, подразумевал бы дополнительное выделение памяти и возможные ошибки.
 Matt Joiner09 окт. 2010 г., 02:38
@cHao: Время сборки не является проблемой в моей ситуации. Пожалуйста, сосредоточьтесь на процедурах ввода-вывода и беспорядке пространства имен, который мне не нужен в других исходных файлах. Единственное верное замечание, которое, я думаю, вы указали, касается ошибочной интерпретации неполного типа неправильным образом. Если вы сделаете это, у вас возникнут большие проблемы. Также я представляю, что во время выполнения все будет взрываться в огненном шаре. Это определенно не проблема в C ++.

Ответы на вопрос(6)

#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. Но если вы настроены против "ненужных" #include, это единственный способ получитьFILE * не мешая stdio. Вы не сможете переслать объявление typedef.

Edit2:

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

typedef FILE;

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

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

 Matt Joiner09 окт. 2010 г., 02:41
@cHao: я считал, что что-то подобное возможно. На самом деле гораздо приятнее, если вы ограничите исходный файл с помощью ввода-вывода: put typedefvoid *FILE_forward; в структуре, и#define FILE_forward FILE * в исходном файле. Я все еще предпочел бы окончательный ответ, который объясняет, почему я не могу переслать объявление typedef (из стандарта).
 Mike Seymour08 окт. 2010 г., 17:12
@Matt: стандарт прямо не говорит, что вы не можете; это просто не дает никакого способа сделать это. 6.7.7 описывает единственный способ ввести имя typedef: «В объявлении, у которого спецификатор класса хранения равен typedef, каждый декларатор определяет идентификатор как имя typedef, которое обозначает тип, указанный для идентификатора». 6.7.2 / 2 гласит: «В спецификаторах объявлений в каждом объявлении должен быть указан как минимум один спецификатор типа», поэтому невозможно объявить имя typedef без указания его типа.
 cHao09 окт. 2010 г., 04:44
@Matt: обновлено снова. Не спрашивайте меня, как или почему, но ... :)
 cHao08 окт. 2010 г., 22:40
@Matt Joiner: обновлено. Это немного некрасиво, но не так страшно, как кастинг повсюду.
 Matt Joiner08 окт. 2010 г., 16:15
Ну, я не могу отрицать тебя, потому что ты не ошибаешься, но это не тот ответ, который я хотел. Возможно, вы могли бы объяснить, почему невозможно объявить переопределение типа, цитируя стандарт ...?
 Matt Joiner08 окт. 2010 г., 10:17
Пожалуйста, объясните, почему это единственный портативный способ.
 cHao08 окт. 2010 г., 12:44
Потому что для того, чтобы объявитьFILE, вы должны знать, какой это тип за кулисами, чтобы каждый, кто на самом делесделал #include <stdio.h> получить ошибку компиляции (потому что два типа не совпадают). Дело в том, что фактический тип заFILE не указано ни в одном стандарте, поэтому у каждого компилятора может быть свой.

не существует переносимого способа прямого объявления структуры 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 a FILE используя данный дескриптор.

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

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

Решение Вопроса

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

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

 Dan Moulding07 окт. 2010 г., 21:10
Я думаюFILE обязательноtypedef, Это не может бытьstructпотому что тогда это будетstruct FILE, не простоFILE.
 Mike Seymour09 окт. 2010 г., 18:52
@Matt: в стандарте ничего не говорится о декларациях типа вперед, поскольку такой концепции не существует. Как я сказал (с кавычками) в другом комментарии, единственный способ ввести имя typedef - это объявление typedef, которое должно включать спецификатор типа. Боюсь, вам действительно нужно включить заголовочный файл и смириться с дополнительными миллисекундами времени компиляции.
 Matt Joiner08 окт. 2010 г., 16:17
При условииFILE является typedef структуры для всех реализаций, о которых я знаю, можете ли вы объяснить, почему невозможно объявить это вперед?
 Matt Joiner08 окт. 2010 г., 10:19
Пожалуйста, процитируйте соответствующую часть стандарта (для C), которая предполагает, что FILE может быть «чем-то другим» или чем-то еще.
 R..07 окт. 2010 г., 18:03
Если вы абсолютно не можете включитьstdio.hЕдинственная альтернатива - хранить указатели наvoid на местеFILE *, а затем другие модули должны будут конвертировать их назад и вперед вFILE * использовать их.
 Mike Seymour08 окт. 2010 г., 13:07
@Matt: цитата, которую я дал (от 7.19.1 / 2), говорит, чтоFILE является «типом объекта», поэтому это может быть любой тип объекта (арифметика, структура, объединение, массив или указатель). Разработчик библиотеки должен выбрать тип, который лучше всего подходит для них.
 Mike Seymour07 окт. 2010 г., 21:39
@ Дан: да, ты прав, по крайней мере, в Си. Мой мозг застрял в C ++.
 Matt Joiner09 окт. 2010 г., 02:44
@Mike Seymour: Можете ли вы процитировать стандарт по объявлению форвардов struct typedefs?
 Mike Seymour08 окт. 2010 г., 16:27
@Matt: две причины: вам не известны все текущие и будущие реализации, и стандартного имени для структуры не существует (поскольку стандарт вообще не указывает, что это структура). Таким образом, даже если вы предполагаете, что это будет структура во всех реализациях, которые вы хотите поддерживать, вы не можете пересылать объявление структуры, поскольку не знаете ее имени.

это должно теоретически работать.

typedef struct FILE_impl_but_including_stdio_h_is_best FILE;
 Matt Joiner08 окт. 2010 г., 10:21
К сожалению, это не работает, согласно комментарию @R ..
 JeremyP07 окт. 2010 г., 16:48
Вы бы подумали, не так ли? Но это не обязательно непрозрачная структура. Если у вас есть доступ к /usr/include/stdio.h на Macintosh, вы увидите, что он совсем не непрозрачен.
 Maister07 окт. 2010 г., 16:57
Это может быть так, но опять же, указатель является указателем (технически не совсем правильно, но эй). СТДИО заботится только о ФАЙЛЕ *.
 R..07 окт. 2010 г., 18:05
Это приведет к ошибке, если вы позже включитеstdio.h в любом файле, где этот typedef виден.

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

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

Итак, форвард-декларирование включает в себя средства:

более быстрая компиляцияменьше сцепленияцыпленок Typedef против Форвард-декларирования?

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

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

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

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

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

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

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

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

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

 Matt Joiner08 окт. 2010 г., 10:20
Это кажется лучшим решением. Это или предложение @R .. о том, что void * используется, и приведение завершено. К сожалению, использование системных структурных имен требует гораздо больших усилий, чем оно того стоит.
 Mike Seymour08 окт. 2010 г., 17:46
@Matt: если вы скопируете декларацию из системного заголовка (что я бы посоветовал в целях переносимости), помните, что дубликатtypedef объявления не разрешены в C, хотя они есть в C ++. Это делает ошибкой включать ваше заявление и<stdio.h> в том же модуле компиляции Си.
 user87732930 мая 2012 г., 13:38
Поэтому хорошая идея при реализации библиотеки C - позволить всем внутренним типам быть структурой. Тогда предварительная декларация будет работать. Разве libpng не реализован таким образом? Так что вместо typedef void * handle; написать дескриптор структуры; в заголовке и структуре handle {void * h_impl;}; в источнике

Ваш ответ на вопрос