Стандарт C теперь называется ISO/IEC 9989:2011.
Стандарт C 2011 был опубликован ISO в понедельник 2011-12-19 (или, точнее, уведомление о том, что он был опубликован, было добавлено на веб-сайт комитета 19 числа; стандарт мог быть опубликован как «давно» по состоянию на 08.12.2011). См. объявление на веб-сайте WG14. К сожалению, PDF из ISO стоит 338 швейцарских франков, < s>и от ANSI 387 долларов США.
- Вы можете получить PDF-файл INCITS/ISO/IEC 9899:2012 (C2011) по адресу ANSI за 30 долларов США.
- Вы можете получить PDF-файл для INCITS/ISO/IEC 14882:2012 (C++2011) по адресу ANSI за 30 долларов США.
Основной ответ
Вопрос в том, «Разрешены ли повторные определения типов в C»? Ответ: «Нет, не в стандартах ISO/IEC 9899:1999 или 9899:1990». Причина, вероятно, историческая; первоначальные компиляторы C не позволяли этого, поэтому первоначальные стандартизаторы (которые были уполномочены стандартизировать то, что уже было доступно в компиляторах C) стандартизировали такое поведение.
См. ответ от Als, где стандарт C99 запрещает повторяющиеся определения типов. Стандарт C11 изменил правило в §6.7 ¶3 на:
3 Если идентификатор не имеет связи, должно быть не более одного объявления идентификатора (в описателе или спецификаторе типа) с той же областью действия и в одном и том же пространстве имен, за исключением того, что:
- имя typedef может быть переопределено для обозначения того же типа, что и в настоящее время, при условии, что этот тип не является изменяемым типом;
- теги могут быть повторно объявлены, как указано в 6.7.2.3.
Итак, теперь в C11 есть явный мандат на повторение typedef. Обратите внимание на наличие C11-совместимых компиляторов C.
Для тех, кто все еще использует C99 или более раннюю версию, последующий вопрос, предположительно, звучит так: «Итак, как мне избежать проблем с повторяющимися определениями типов?»
Если вы следуете правилу, согласно которому существует один заголовок, определяющий каждый тип, который необходим более чем в одном исходном файле (но может быть много заголовков, определяющих такие типы, хотя каждый отдельный тип находится только в одном заголовке), и если этот заголовок используется каждый раз, когда этот тип необходим, тогда вы не столкнетесь с конфликтом.
Вы также можете использовать неполные объявления структур, если вам нужны только указатели на типы и вам не нужно выделять фактическую структуру или получать доступ к их членам (непрозрачные типы). Опять же, установите правила о том, какой заголовок объявляет неполный тип, и используйте этот заголовок везде, где требуется тип.
См. также Что такое внешние переменные в C; он говорит о переменных, но с типами можно обращаться примерно так же.
Вопрос из комментариев
Мне очень нужны «неполные объявления структур» из-за отдельных усложнений препроцессора, которые запрещают определенные включения. Итак, вы говорите, что я не должен определять эти предварительные объявления, если они снова определяются полным заголовком?
Более менее. На самом деле мне не приходилось иметь дело с этим (хотя есть части работающих систем, которые очень близки к тому, чтобы беспокоиться об этом), так что это немного предварительно, но я считаю, что это должно работать.
Как правило, заголовок описывает внешние службы, предоставляемые «библиотекой» (один или несколько исходных файлов), достаточно подробно, чтобы пользователи библиотеки могли компилировать ее. Особенно в случае, когда имеется несколько исходных файлов, также может быть внутренний заголовок, определяющий, например, полные типы.
Все заголовки являются (а) автономными и (б) идемпотентными. Это означает, что вы можете (а) включить заголовок, и все другие необходимые заголовки будут включены автоматически, и (б) вы можете включить заголовок несколько раз, не навлекая на себя гнев компилятора. Последнее обычно достигается с помощью защиты заголовков, хотя некоторые предпочитают #pragma once
, но это не переносимо.
Итак, у вас может быть такой публичный заголовок:
общественность.ч
#ifndef PUBLIC_H_INCLUDED
#define PUBLIC_H_INCLUDED
#include <stddef.h> // size_t
typedef struct mine mine;
typedef struct that that;
extern size_t polymath(const mine *x, const that *y, int z);
#endif /* PUBLIC_H_INCLUDED */
Пока так не очень спорно (хотя можно с полным основанием подозревать, что интерфейс, предоставляемый этой библиотекой, очень неполный).
приват.ч
#ifndef PRIVATE_H_INCLUDED
#define PRIVATE_H_INCLUDED
#include "public.h" // Get forward definitions for mine and that types
struct mine { ... };
struct that { ... };
extern mine *m_constructor(int i);
...
#endif /* PRIVATE_H_INCLUDED */
Опять же, не очень спорно. Заголовок public.h
должен быть указан первым; это обеспечивает автоматическую проверку автономности.
Потребительский код
Любой код, которому нужны службы polymath()
, пишет:
#include "public.h"
Это вся информация, необходимая для использования сервиса.
Код провайдера
Любой код в библиотеке, определяющий службы polymath()
, пишет:
#include "private.h"
После этого все работает как обычно.
Другой код провайдера
Если есть другая библиотека (назовем ее multimath()
), которая использует службы polymath()
, то этот код должен включать public.h
, как и любой другой потребитель. Если службы polymath()
являются частью внешнего интерфейса для multimath()
, то общедоступный заголовок multimath.h
будет включать public.h
(извините, здесь я поменял терминологию ближе к концу). Если службы multimath()
полностью скрывают службы polymath()
, то заголовок multimath.h
не будет включать public.h
, но закрытый заголовок multimath()
вполне может это сделать, или отдельные исходные файлы, которым нужны службы polymath()
, могут включать его при необходимости.
Пока вы неукоснительно следуете дисциплине включения правильного заголовка везде, вы не столкнетесь с проблемами двойного определения.
Если впоследствии вы обнаружите, что один из ваших заголовков содержит две группы определений, одну из которых можно использовать без конфликта, а другую, которая может иногда (или всегда) конфликтовать с каким-то новым заголовком (и службами, объявленными в нем), то вам необходимо разделить исходный заголовок на два подзаголовка. Каждый подзаголовок в отдельности следует правилам, разработанным здесь. Исходный заголовок становится тривиальным — защита заголовка и строки для включения двух отдельных файлов. Весь существующий рабочий код остается нетронутым, хотя зависимости меняются (дополнительные файлы, от которых зависит). Новый код теперь может включать соответствующий допустимый подзаголовок, а также использовать новый заголовок, конфликтующий с исходным заголовком.
Конечно, у вас может быть два заголовка, которые просто несовместимы. Для надуманного примера, если есть (плохо спроектированный) заголовок, который объявляет другую версию структуры FILE
(от версии в <stdio.h>
), вы попали в шланг; код может включать либо плохо спроектированный заголовок, либо <stdio.h>
, но не то и другое одновременно. В этом случае плохо спроектированный заголовок следует пересмотреть, чтобы использовать новое имя (возможно, File
, но, возможно, что-то еще). Реально вы можете столкнуться с этой проблемой, если вам нужно объединить код из двух продуктов в один после корпоративного поглощения с некоторыми общими структурами данных, такими как DB_Connection
для соединения с базой данных. В отсутствие функции C++ namespace
вы застряли в упражнении по переименованию одного или обоих участков кода.
person
Jonathan Leffler
schedule
21.12.2011
struct Foo
напрямую, но каким-то образом это противоречит стилю кода. (Это связано с созданием экземпляра этого шаблон хеш-таблицы.) - person Kerrek SB   schedule 21.12.2011please typedef struct Foo Foo;
, чтобы успокоить компилятор. - person R. Martinho Fernandes   schedule 21.12.2011