【问题标题】:Scope of forward-declared struct前向声明结构的范围
【发布时间】:2016-07-14 07:45:54
【问题描述】:

想象一下我们有这个:

void f(struct s *);

通过阅读标准(6.2.1 美元),我对标签的范围有点困惑。首先是这个:

  1. 标签名称是唯一一种具有函数作用域的标识符。它可以使用(在一个 goto 语句)出现在函数中的任何位置,并且被隐式声明 通过它的句法外观(后跟一个 : 和一个语句)。

  2. 每个其他标识符的范围由其声明的位置决定(在声明符或类型说明符中)。如果声明者或 声明标识符的类型说明符出现在任何 块或参数列表,标识符具有文件范围,其中 在翻译单元的末尾终止。如果声明者或 声明标识符的类型说明符出现在块内或 在函数定义的参数声明列表中, 标识符具有块作用域,它在 关联块。 如果声明符或类型说明符声明 标识符出现在参数声明列表中 函数原型(不是函数定义的一部分),标识符 具有函数原型范围,它在 function declarator. 如果一个标识符指定了两个不同的 相同名称空间中的实体,范围可能重叠。如果是这样,则 一个实体的范围(内部范围)将严格在 另一个实体的范围(外部范围)。在内部范围内, 标识符指定在内部范围内声明的实体;这 在外部范围内声明的实体在内部隐藏(不可见) 内部范围。

由于标识符属性被早期定义为:

  1. 标识符可以表示一个对象;一个函数; 结构的标签或成员、联合或枚举;类型定义名称;标签名称;一种 宏名;或宏参数。相同的标识符可以表示 程序中不同点的不同实体。一个成员 枚举称为枚举常数。宏名称和宏 这里不考虑参数,因为在 程序翻译的语义阶段 任何出现的宏名称 在源文件中被预处理标记序列替换 构成它们的宏定义。

这让我得出这个结论:

由于类型说明符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 *);

有谁愿意解释在函数原型f 中确定s 标识符范围的正确方法是什么?

我指的是 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 冲突 - 或者是吗? - 我不知道)。

g 的声明中的 p7 struct s 应该引用 f 的函数原型中声明的相同标识符。因此,定义类型不完整的变量应该不会导致任何编译器错误。

但是在f的函数原型中声明的p4 struct s必须具有在函数声明符末尾终止的范围。所以struct s 的声明在g 的声明中应该创建另一个标识符s(作为不完整结构的标记,因此错误消息)。

【问题讨论】:

  • 请向我们提供一段完整的代码,显示预期和意外的行为以及错误/警告在哪一行。
  • @MohitJain 不完全是。
  • @FISOCPP 我说的是相关而不是重复。

标签: c scope language-lawyer c11


【解决方案1】:

很难的问题。我不是标准的大师,但也许可以对这种行为有所启发。

当你声明你的函数f时,你给它一个struct s类型的参数,该结构的定义为{int _;}。这个定义的位置意味着这个定义在这个原型声明之外是不可见的(我想知道你怎么能调用f)。编译器将此标记为警告。稍后使用f时可能会报错。

然后你声明了一个struct s 类型的变量,但是由于你之前的定义已经超出了范围,并且编译器之前没有看到它的定义(在f 之前)它会标记一个关于声明的错误编译器无法计算大小的变量(编译器必须在此处留出内存)。

最后,旧的编译器是一次性的,这意味着它必须以精确的顺​​序提供所有信息,特别是必须在声明该类型的变量之前定义类型。许多当前的编译器是双通道的,这意味着它会扫描输入文本两次,现在在第二通道中必须能够完成翻译。编译器会向您标记“前向声明”的尝试,该尝试只能在第二遍中解决。据我所知,C 语言是为一次性指定的。

C 中唯一可能的前向声明是声明指向尚未[完全]指定的类型的指针,因为编译器知道指针的大小。

【讨论】:

    【解决方案2】:

    首先,您不能在参数列表中定义类。我找不到可以编译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 在命名空间范围内定义的函数,identifier 被声明为命名空间中的 class-name 包含声明

    因此,您在函数的参数列表中转发声明的类型的范围在该函数的命名空间中。您仍然必须在使用它之前定义类型。自 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);
    }
    

    为了更具体地回答您的问题,您在函数 f 中转发声明的 struct s 具有命名空间范围。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-10-11
      • 2012-06-09
      • 1970-01-01
      • 1970-01-01
      • 2018-11-06
      • 2017-04-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多