Область действия предварительно объявленной структуры

Представьте, что у нас есть это:

void f(struct s *);

Прочитав стандарт ($ 6.2.1), я немного запутался в том, что такое область применения тегов. Сначала вот это:

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

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

Поскольку свойства идентификатора были ранее определены как:

  1. Идентификатор может обозначать объект; функция; тег или элемент структуры, объединения или перечисления; имя 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
}

Но тогда после этого имеем:

  1. Теги структуры, объединения и перечисления имеют область действия, которая начинается сразу после появления тега в спецификаторе типа, который объявляет тег. Каждая константа перечисления имеет область действия, которая начинается сразу после появления определяющего ее перечислителя в список перечислителей. Любой другой идентификатор имеет область действия, которая начинается сразу после завершения его декларатора.

Что означает совсем другое:

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 (будучи тегом неполной структуры и, следовательно, сообщениями об ошибках).


person AnArrayOfFunctions    schedule 26.03.2016    source источник
comment
Пожалуйста, предоставьте нам полный фрагмент кода, который показывает ожидаемое и неожиданное поведение, а также строки с ошибками/предупреждениями.   -  person Paul Ogilvie    schedule 26.03.2016
comment
Связано: stackoverflow.com/questions /30725769/   -  person Mohit Jain    schedule 28.03.2016
comment
@MohitJain Не совсем так.   -  person AnArrayOfFunctions    schedule 28.03.2016
comment
@FISOCPP Я сказал, что это связано, а не дублируется.   -  person Mohit Jain    schedule 29.03.2016


Ответы (2)


Прежде всего, вам не разрешено определять класс в списке параметров. Я не могу найти компилятор, который скомпилирует void f(struct s {int _;});, поэтому, возможно, у вас был несоответствующий компилятор, когда вы задавали этот вопрос. Вы можете найти это правило в стандарте по адресу [dcl.fct]:

Типы не должны определяться в типах возврата или параметра.

Во-вторых, в соответствии со стандартом C++03 (ISO/IEC 14882:2003) вплоть до C++17 (N4659) правила, касающиеся предварительного объявления типа в списке параметров, не изменились ([basic. область видимости.pdecl]):

если уточненный-спецификатор-типа используется в decl-specifier-seq или в предложении-объявления параметра функции, определенной в области пространства имен, идентификатор< /em> объявляется как имя класса в пространстве имен, содержащем объявление

Следовательно, область действия типа, который вы предварительно объявляете в списке параметров функции, находится в пространстве имен этой функции. Вы все равно должны определить тип, прежде чем использовать его. Начиная с C++03 код, подобный следующему, допустим: Demo

void f(struct s); // forward declares s
struct s{int i;}; // defines s forward declared above

void f(s a){ // use s
   std::cout << a.i << std::endl;
}

int main()
{
   s g;
   g.i = 0;
   f(g);
}

Чтобы более конкретно ответить на ваш вопрос, struct s, который вы объявляете в функции f, имеет область имен.

person AndyG    schedule 28.07.2017

Сложный вопрос. Я не стандартный гуру, но, возможно, смогу пролить свет на поведение.

Когда вы объявляете свою функцию f, вы даете ей параметр типа struct s с определением этой структуры как {int _;}. Размещение этого определения означает, что определение не будет видно за пределами этого объявления прототипа (интересно, как вы вообще могли вызывать f). Компилятор помечает это как предупреждение. Ошибки могут быть выданы позже при использовании f.

Затем вы объявляете переменную типа struct s, но поскольку ваше более раннее определение уже вышло за рамки, и компилятор не видел его определения ранее (до f), он помечает ошибку об объявлении переменной, которую компилятор не может вычислить размер (здесь компилятор должен выделить память).

Наконец, старые компиляторы были однопроходными, что означает, что вся информация должна передаваться в точном порядке, в частности, что типы должны быть определены до того, как могут быть объявлены переменные типа. Многие современные компиляторы являются двухпроходными, то есть они сканируют входной текст два раза, и теперь во втором проходе должна быть возможность завершить перевод. Компилятор помечает вам попытку «упреждающего объявления», которая может быть разрешена только во втором проходе. Насколько мне известно, язык C предназначен для однопроходной обработки.

Единственное предварительное объявление, возможное в C, - это объявление указателя на тип, который не был [полностью] указан, поскольку компилятор знает размер указателя.

person Paul Ogilvie    schedule 26.03.2016