想象一下,我们有这个:
void f(struct s *);
从阅读标准($ 6.2.1)开始,我对标签的范围感到困惑。首先是:
标签名称是唯一具有功能范围的标识符。它可以使用(在一个 goto语句)它出现的函数中的任何位置,并且是隐式声明的 通过它的句法外观(后面是:和声明)。
- 醇>
每个其他标识符的范围由其声明的位置(在声明符或类型说明符中)确定。如果是声明者或 声明标识符的类型说明符出现在any之外 块或参数列表,标识符有文件范围,其中 终止于翻译单元的末尾。如果是声明者或 声明标识符的类型说明符出现在块或 在函数定义的参数声明列表中, 标识符具有块作用域,终止于作用域的末尾 相关块。 如果声明的声明符或类型说明符 标识符出现在a中的参数声明列表中 函数原型(不是函数定义的一部分),标识符 有函数原型范围,终止于 函数声明符。如果标识符指定两个不同的 在同一名称空间中的实体,范围可能会重叠。如果是这样的话 一个实体的范围(内部范围)将严格地在此之前结束 另一个实体的范围(外部范围)。在内部范围内, 标识符指定在内部范围内声明的实体;该 在外部范围内声明的实体隐藏(并且不可见) 内在范围。
标识符属性早期定义为:
- 标识符可以表示对象;功能; 标签或结构的成员,联合或枚举;一个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
}
但在此之后我们有:
- 结构,联合和枚举标记的范围是在声明了类型说明符中的标记出现之后开始的。 标记。每个枚举常量的范围都在...之后开始 枚举器列表中定义的枚举器的外观。任何其他 identifier具有在其完成之后开始的范围 说明符。
醇>
这意味着完全不同的东西:
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相冲突 - 或者是他们? - 我不知道)。
struct s
声明中的p7 g
应该引用f
函数原型中声明的相同标识符。因此,定义不完整类型的变量不会导致任何编译器错误。
但是在struct s
的函数原型中声明的p4 f
必须具有终止于函数声明符末尾的作用域。因此,struct s
声明中g
的声明应该创建另一个标识符s
(是不完整结构的标记,因此错误消息)。
答案 0 :(得分:0)
首先,您不能在参数列表中定义类。我找不到编译void f(struct s {int _;});
的编译器,所以当你提出这个问题时,你可能有一个不符合的编译器。您可以在标准中找到此规则
[dcl.fct]:
不应在返回或参数类型中定义类型。
其次,根据C ++ 03(ISO / IEC 14882:2003)标准一直到C ++ 17(N4659),关于参数列表中类型的前向声明的规则没有改变( [basic.scope.pdecl]):
如果在 decl-specifier-seq 或参数声明子句中使用了 elaborated-type-specifier 在命名空间作用域中定义的函数,标识符在命名空间中声明为类名 包含声明
因此,您在函数的参数列表中声明的类型的范围位于该函数的命名空间中。您仍然必须在使用之前定义类型。自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
具有命名空间范围。
答案 1 :(得分:-1)
难题。我不是一个标准的大师,但可能会对行为有所启发。
当您声明函数f
时,为其指定一个类型为struct s
的参数,其结构定义为{int _;}
。这个定义的放置意味着在原型声明之外不会看到定义(我想知道如何调用f
)。编译器将此标记为警告。使用f
时可能会在以后发生错误。
然后你声明一个struct s
类型的变量,但由于你之前的定义已经超出了范围,并且编译器没有看到它之前的定义(在f
之前)它标记有关声明编译器无法计算大小的变量的错误(编译器必须在此处留出内存)。
最后,较旧的编译器是一次通过,这意味着它必须以精确的顺序提供信息,特别是必须在声明类型的变量之前定义类型。许多当前的编译器是两遍的,这意味着它扫描输入文本两次,现在在第二遍中必须能够完成翻译。编译器会向您标记尝试&#34;前向声明&#34;只能在第二次通过中解决。据我所知,C语言被指定为一次通过。
C中唯一可能的前向声明是声明指向未完全指定的类型的指针,因为编译器知道指针的大小。