Представьте, что у нас есть это:
void f(struct s *);
Прочитав стандарт ($ 6.2.1), я немного запутался в том, что такое область применения тегов. Сначала вот это:
Имя метки — это единственный тип идентификатора, который имеет область действия. Его можно использовать (в операторе goto) в любом месте функции, в которой он появляется, и он неявно объявляется своим синтаксическим представлением (за которым следует : и оператор).
Каждый другой идентификатор имеет область действия, определяемую размещением его объявления (в описателе или спецификаторе типа). Если декларатор или спецификатор типа, который объявляет идентификатор, появляется за пределами любого блока или списка параметров, идентификатор имеет файловую область, которая заканчивается в конце единицы перевода. Если декларатор или спецификатор типа, который объявляет идентификатор, появляется внутри блока или в списке объявлений параметров в определении функции, идентификатор имеет область действия блока, которая заканчивается в конце связанного блока. Если декларатор или спецификатор типа, который объявляет идентификатор, появляется в списке объявлений параметров в прототипе функции (не является частью определения функции), идентификатор имеет область действия прототипа функции, которая заканчивается в конце декларатора функции. Если идентификатор обозначает два разных объекта в одном и том же пространстве имен, области действия могут перекрываться. Если это так, область действия одного объекта (внутренняя область) будет заканчиваться строго перед областью действия другого объекта (внешняя область). Во внутренней области идентификатор обозначает сущность, объявленную во внутренней области; сущность, объявленная во внешней области, скрыта (и не видна) во внутренней области.
Поскольку свойства идентификатора были ранее определены как:
- Идентификатор может обозначать объект; функция; тег или элемент структуры, объединения или перечисления; имя typedef; название ярлыка; имя макроса; или параметр макроса. Один и тот же идентификатор может обозначать разные сущности в разных точках программы. Член перечисления называется константой перечисления. Имена макросов и параметры макросов далее здесь не рассматриваются, потому что до семантической фазы преобразования программы любые вхождения имен макросов в исходном файле заменяются последовательностями токенов предварительной обработки, которые составляют их макроопределения.
Что приводит меня к такому выводу:
Поскольку спецификатор типа struct s
объявляет идентификатор s
в «списке объявлений параметров в прототипе функции», он имеет (идентификатор s
) область действия прототипа функции. Что означает что-то вроде этого:
void f2()
{ //inside **some** function block after the above declaration
struct s { int a; } v; //new s identifier being declared
f(&v); //not compatible types
}
Но тогда после этого имеем:
- Теги структуры, объединения и перечисления имеют область действия, которая начинается сразу после появления тега в спецификаторе типа, который объявляет тег. Каждая константа перечисления имеет область действия, которая начинается сразу после появления определяющего ее перечислителя в список перечислителей. Любой другой идентификатор имеет область действия, которая начинается сразу после завершения его декларатора.
Что означает совсем другое:
void f3()
{ //inside **some** function block after the above declaration
struct s { int a; } v; //completing incomplete type
f(&v); //ok
}
Похоже, что gcc и clang следуют за p4 (в сумме с этим предупреждением о компиляции объявления f
):
warning: ‘struct s’ declared inside parameter list will not be visible outside of this definition or declaration
void f(struct s *);
Аналогично в случае с clang:
warning: declaration of 'struct s' will not be visible outside of this function [-Wvisibility]
void f(struct s *);
Кто-нибудь хочет объяснить, как правильно определить область действия идентификатора s
в прототипе функции f
?
Я имею в виду стандартную бумагу INCITS/ISO/IEC 9899-2011[2012]; компиляция компилятором gcc (и clang) со следующими флагами:
-std=c11 -pedantic
Запрос полного кода:
В настоящее время (при компиляции с помощью GCC и clang) это:
void f(struct s {int _;});
struct s g;
Выдаст следующую ошибку (от clang):
prog.c:1:15: warning: declaration of 'struct s' will not be visible outside of this function [-Wvisibility]
void f(struct s {int _;});
^
prog.c:3:10: error: tentative definition has type 'struct s' that is never completed
struct s g;
^
prog.c:3:8: note: forward declaration of 'struct s'
struct s g;
Что, цитируя стандарт, может быть или не быть правильным поведением в этом случае (конфликтующие p4 и p7 - или они? - я не знаю).
По p7 struct s
в объявлении g
должен ссылаться на тот же идентификатор, объявленный в прототипе функции f
. И поэтому не должно быть никаких ошибок компилятора, вызванных определением переменной с неполным типом.
Но по p4 struct s
, объявленный в прототипе функции f
, должен иметь область действия, которая заканчивается в конце декларатора функции. Итак, тогда объявление struct s
в объявлении g
должно создать еще один идентификатор s
(будучи тегом неполной структуры и, следовательно, сообщениями об ошибках).