Подтвердить что ты не робот

Повторяющиеся typedefs - недопустимы в C, но действительны в С++?

Мне нужна стандартная ссылка, почему следующий код запускает предупреждение о соблюдении в C (проверено с помощью gcc -pedantic; "typedef redefinition" ), но отлично подходит для С++ (g++ -pedantic):

typedef struct Foo Foo;
typedef struct Foo Foo;

int main() { return 0; }

Почему я не могу определить typedef несколько раз в C?

(Это имеет практическое значение для структурирования заголовка проекта C).)

4b9b3361

Ответ 1

Почему это компилируется в С++?

Потому что С++ Standard явно так говорит.

Ссылка:

С++ 03 Стандарт 7.1.3 Спецификатор typedef

§7.1.3.2:

В заданной неклассовой области спецификатор typedef может использоваться для переопределения имени любого типа, объявленного в этой области, для обозначения того типа, к которому он уже относится.

[Пример:
typedef struct s {/*... */} s;
typedef int I,
typedef int I,
typedef я I,
-end пример]

Почему это не удается скомпилировать в C?

typedef имена не имеют связей, а стандартные идентификаторы запретов C99 без спецификации привязки имеют более одного объявления с той же областью действия и в том же пространстве имен.

Ссылка:

Стандарт C99: §6.2.2 Связи идентификаторов

§6.2.2/6:

Следующие идентификаторы не имеют связи: идентификатор, объявленный как нечто иное, чем объект или функция; идентификатор, объявленный как параметр функции; область блока идентификатор для объекта, объявленного без спецификатора класса хранения.

Далее §6.7/3:

Если идентификатор не имеет привязки, должно быть не более одного объявления идентификатора (в спецификаторе декларатора или типа) с той же областью и в том же пространстве имен, за исключением тегов как указано в пункте 6.7.2.3.

Ответ 2

Стандарт C теперь соответствует ISO/IEC 9989: 2011

Стандарт 2011 C был опубликован в понедельник 2011-12-19 по ИСО (или, точнее, уведомление о том, что оно было опубликовано, было добавлено на веб-сайт комитета 19-го числа, стандарт может быть опубликован как "давно 'как 2011-12-08). См. Объявление на веб-сайте WG14. К сожалению, PDF из ISO стоит 338 CHF, и ANSI 387 USD.

  • Вы можете получить PDF для INCITS/ISO/IEC 9899: 2012 (C2011) от ANSI за 30 долларов США.
  • Вы можете получить PDF файл для INCITS/ISO/IEC 14882: 2012 (С++ 2011) из ANSI за 30 долларов США.

Основной ответ

Вопрос: "Являются ли повторяющиеся typedefs допустимыми в C"? Ответ "Нет - не соответствует стандартам ISO/IEC 9899: 1999 или 9899: 1990". Причина, вероятно, историческая; исходные компиляторы C этого не допускали, поэтому оригинальные стандартизаторы (которым было поручено стандартизировать то, что уже было доступно в компиляторах C) стандартизировали это поведение.

См. ответ Als, где стандарт C99 запрещает повторные typedef. Стандарт C11 изменил правило в §6.7 ¶3 на:

3 Если идентификатор не имеет связи, должно быть не более одного объявления идентификатора (в спецификаторе декларатора или типа) с той же областью действия и в том же пространстве имен, кроме что:

  • имя typedef может быть переопределено для обозначения того же типа, что и в настоящее время, при условии, что тип не является измененным типом;Теги
  • могут быть обновлены, как указано в 6.7.2.3.

Итак, теперь есть явный мандат повторного typedef в C11. Бросьте доступность C-совместимых компиляторов C.


Для тех, кто все еще использует C99 или ранее, последующий вопрос тогда, предположительно "Итак, как мне избежать проблем с повторяющимися typedefs?"

Если вы следуете правилу, что есть один заголовок, который определяет каждый тип, который требуется в нескольких исходных файлах (но может быть много заголовков, определяющих такие типы, каждый отдельный тип находится только в одном заголовке), и если этот заголовок используется всякий раз, когда этот тип нужен, то вы не столкнетесь с конфликтом.

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

См. также Что такое внешние переменные в C; он говорит о переменных, но типы можно рассматривать несколько аналогично.


Вопрос из комментариев

Мне очень нужны "неполные декларации структуры" из-за отдельных препроцессорных осложнений, которые запрещают определенные включения. Итак, вы говорите, что я не должен набирать эти форвардные объявления, если они снова набраны полным заголовком?

Более или менее. Мне действительно не приходилось иметь дело с этим (хотя есть части систем на работе, которые очень близки к тому, чтобы беспокоиться об этом), поэтому это немного предварительное, но я считаю, что это должно сработать.

Как правило, заголовок описывает внешние службы, предоставляемые "библиотекой" (одним или несколькими исходными файлами) достаточно подробно, чтобы пользователи библиотеки могли скомпилировать их. Особенно в случае, когда имеется несколько исходных файлов, может быть также внутренний заголовок, который определяет, например, полные типы.

Все заголовки являются (а) автономными и (б) идемпотентными. Это означает, что вы можете (a) включить заголовок, и все необходимые другие заголовки автоматически включаются, и (b) вы можете включать заголовок несколько раз, не вызывая гнев компилятора. Последнее обычно достигается с помощью защиты заголовков, хотя некоторые предпочитают #pragma once - но это не переносимо.

Итак, вы можете создать общий заголовок следующим образом:

public.h

#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 */

До сих пор это не очень противоречиво (хотя можно законно подозревать, что интерфейс, предоставляемый этой библиотекой, очень неполный).

private.h

#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 для подключения к базе данных. В отсутствие функции С++ namespace вы застряли с упражнением переименования для одного или обоих серий кода.

Ответ 3

Вы можете сделать это на С++ из-за 7.1.3/3 и /4.

Вы не можете сделать это на C99, потому что он не имеет эквивалентного специального случая в 6.7.7, поэтому повторное объявление имени typedef следует тем же правилам, что и повторное объявление любого другого идентификатора. В частности, 6.2.2/6 (typedefs не имеют привязки) и 6.7/3 (идентификаторы без привязки могут быть объявлены только один раз с той же областью).

Помните typedef - спецификатор класса хранения в C99, тогда как в С++ это спецификатор decl. Другая грамматика заставляет меня подозревать, что авторы С++ решили приложить больше усилий, чтобы сделать typedefs "другим видом декларации", и поэтому вполне могли бы потратить больше времени и текста на специальные правила для них. Кроме того, я не знаю, какова была мотивация авторов (отсутствие) C99.

[Редактировать: см. ответ Йоханнеса для C1x. Я не придерживаюсь этого вообще, поэтому мне, вероятно, следует перестать использовать "C" для обозначения "C99", потому что я, вероятно, даже не заметлю, когда они ратифицируют и публикуют. Это достаточно плохо, как есть: "C" должен означать "C99", но на практике означает "C99, если вам повезло, но если вам нужно поддерживать MSVC, то C89".]

[Изменить снова: и действительно, он был опубликован и теперь является C11. Вут.]

Ответ 4

В спецификации c нет ничего, что говорит, почему это неверно. Спецификация - неправильное место, чтобы прояснить это. FWIW разрешено в C1x (в соответствии с ответом, который я получил на один из моих последних вопросов).

Я полагаю, что эта функция c1x поддерживает преобразование макросов в typedefs (первые разрешены для повторения, если они идентичны).

Ответ 5

Многие люди ответили, ссылаясь на стандарты, но никто не сказал, ПОЧЕМУ стандарты для C и С++ здесь отличаются. Ну, я считаю, причина допуска повторных typedefs в С++ заключалась в том, что С++ неявно объявляет структуры и классы как типы. В С++ допустимо следующее:

struct foo { int a; int b; };
foo f;

В C нужно написать:

struct foo { int a; int b; };
typedef struct foo foo;
foo f;

Существует много C-кода, который объявляет структуры как типы. Если такой код переносится на С++, typedefs дублируются, потому что язык С++ добавляет свои собственные неявные typedef. Таким образом, чтобы избежать проблем с программистами, чтобы удалить те, которые больше не требуются typedefs, они разрешили дублировать typedefs в С++ с самого начала.

Как говорили другие, люди со временем осознали, что использование повторяющихся идентичных typedefs в C также может быть полезно. По крайней мере, это не должно повредить. Вот почему эта функция С++ получила вид "backported" на C11.