【问题标题】:Do parameter declarations in function declarators have function prototype scope?函数声明器中的参数声明是否具有函数原型范围?
【发布时间】:2018-10-26 12:36:10
【问题描述】:

C11 ISO 标准(草案)的第 6.2.1(2) 条将函数原型定义为(强调我的)

函数原型是函数的声明,它声明了其参数的类型。

所以声明如void (*f)( struct s { int c; } ); 不是函数的声明(它是指向函数的指针的声明),因此标记 s 具有文件或块范围(取决于此声明出现的位置)。然后似乎以下翻译单元void (*f)( struct s { int c; } ); struct s a[42]; 应该完全符合(撇开任何可用性问题)。然而,gcc 会产生一个诊断性的“不完整类型数组”(以及关于s 的范围仅限于参数列表的警告),表明s 具有函数原型范围,即使正式没有函数声明。

标准的意图是说每个函数 declarator 中的参数列表定义了自己的范围(gcc 和所有其他编译器似乎都是这样解释的)?这种意图是否在标准的任何地方更正式地表达?任何尚未纳入标准的缺陷报告?

当然,这是一个语言律师的问题,我很欣赏这种“偷偷摸摸”的标签声明是不好的风格。除了s 是上述a 声明中的不完整类型之外,范围问题也使得无法定义 具有*f 原型的函数。最后,为了避免使用f 本身的问题,可以将f 的整个声明放在sizeof 中,如int b[sizeof(void (*)( struct s { int c; } ))];

【问题讨论】:

  • “warning: its scope is only this definition or declaration, which is probably not what you want”有什么难理解的?您的原型同时声明了 参数类型 和 类型定义 本身 - 语法上有效,但实际上无用..
  • @Clifford:检查 [language-lawyer] 标签。警告不难理解,但对吗?如果是这样,为什么?为什么范围这么小?
  • @Clifford:有点吹毛求疵,struct 并非完全无用,因为人们可以用适当的原型定义一个函数,甚至在 不同的翻译单元,根据 6.2.7(1),尽管这种做法值得怀疑。问题是gcc 是否遵循标准,而不是为什么它会抛出这个警告。
  • @MSalters :我很欣赏这个标签,这就是为什么我发表评论而不是答案。我的意思很简单,问题是“标签 s 具有文件或块范围”,而显然它具有 声明范围。可能是 stackoverflow.com/questions/16831605/… 的近似副本
  • @Clifford:绝对相关,但这不是 [语言律师] 的问题。此处的正确答案将显示 C11 的哪个部分使标记 s 具有 声明范围(“会”,因为 alexsh 是正确的。C11 有 4 种类型的范围,并且 声明范围 不是其中之一)

标签: c scope language-lawyer c11


【解决方案1】:

在 cmets 的长时间讨论过程中,很明显这个问题实际上是关于 §6.2.1/2 中括号注释的确切含义,“A 函数原型是声明其参数类型的函数声明。”

据我所知,“函数原型”这个短语没有正式的定义,它在标准中以多种方式使用。例如:

  1. §6.2.7/3 兼容类型和复合类型:

    如果只有一种类型是带有参数类型列表的函数类型(函数原型),则复合类型是带有参数类型列表的函数原型。

    在这种情况下,“函数原型”的第一次使用显然不限于函数声明,因为它表示“函数类型”——它可能是在解析指向函数的过程中隐式创建的类型声明符——“函数原型”的第二种用法可能根本没有语法表示,因为它是在复合类型算法过程中创建的类型

  2. §6.5.2.2 函数调用

    这里,第 2 段适用于“包含原型的类型”的函数,而第 6 段将“不包含原型的类型”的函数与“包含原型的类型”的函数进行对比。这些用法与上面“函数原型”的“定义”是不相容的,因为该定义适用于整个函数类型,而“原型”这里的用法显然是在谈论函数类型的一部分。

  3. §6.7.6.3 函数声明符(包括原型)

    本节标题可以解释为关于所有“函数声明符”,包括那些碰巧是“原型”的,或者是关于“函数声明符”和可能包含的“原型”。

  4. §6.9.1/7 函数定义

    如果声明符包含参数类型列表,该列表还指定了所有参数的类型;这样的声明符还可以作为函数原型,供以后在同一个翻译单元中调用同一个函数。

    这里的短语“函数原型”似乎是在谈论与函数类型相关联或包含在函数类型中的东西,但函数声明符可以用作代理(“充当”)。

我认为这不会通过像Principia Mathematica 这样的作品的形式严谨性标准,但它与大多数司法规范和其他标准相当,其中需要一点常识来解释一些段落。

确实,有问题的引用:

“函数原型是声明其参数类型的函数的声明。”

暗示参数列表中的参数是类型声明,而不仅仅是规范(如果这两个短语之间确实存在差异)。

此外,如果我们解构 §6.2.1/4(部分引用如下),我们会看到它说:

每个其他标识符 [即,除了标签或宏名称] 的范围由其声明的位置决定(在声明符或类型说明符中)。

然后继续列出“声明标识符的声明符或类型说明符”的三个备选方案:

  1. “出现在任何块或参数列表之外”

  2. “出现在块内”或“在参数声明列表中 函数定义”

  3. “出现在函数原型的参数声明列表中(不是函数定义的一部分)”

假定这三种可能性是排他性的(因此这些规定没有排序)和全面性(因为它们必须适用于“每个标识符”)。然后,常识必须使我们假设函数原型是一个函数声明符,其中包括一个参数列表,或者(在某些情况下)该句法构造的某个子集或语义抽象,所以第一个问题的答案是“是的,这就是标准的意图”。

这是否是标准中的缺陷是我不选择参与的意见问题。我检查了我认为是最新的列表](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2244.htm) 并没有发现任何相关内容,据我所知,它还没有被报告为缺陷。


下面是我的原始答案的某些版本,以防它对阅读 cmets 的人有用。

标准对参数列表中标识符范围的规定对我来说似乎很清楚。 §6.2.1/4 为参数列表中声明的标识符定义了两个可能的范围(添加了重点):

如果声明标识符的声明符或类型说明符出现在块内或函数定义中的参数声明列表内,则标识符具有块范围,即在关联块的末尾终止。

如果声明标识符的声明符或类型说明符出现在函数原型的参数声明列表中(不是函数定义的一部分),则标识符具有函数原型范围,它在函数声明符的末尾终止。 (§6.2.1/4)

在问题的示例中,struct s 的声明符出现了一个 parameter-list,这正是“参数声明列表”。所以第二句适用,它们的范围仅限于参数列表本身。这使得struct 实际上毫无用处,因此编译器会警告您。

如果结构类型说明符出现在定义的参数列表中,它可以在函数体内使用,但该结构的实例不能从出现定义的翻译单元传递给函数。

我相信标准的意图是标记类型的类型说明符声明出现在参数列表之外。 §6.7.2.3(标签)的语言强化了这一点:

  1. 特定类型的内容最多只能定义一次。

和

  1. 结构、联合或枚举类型的两个声明在不同的范围内或使用不同的标记声明不同的类型。不包含标记的结构、联合或枚举类型的每个声明都声明了不同的类型。

【讨论】:

    【解决方案2】:

    先打个比方

    struct foo* x;
    

    声明一个指向struct foo 的指针,但该声明中的struct foo 仍然是struct 类型的(前向)声明。

    现在

    int (*f)(int);
    

    是函数指针的声明。该指针是一个指向基类型int ()(int) 的指针,它是一个带有原型的函数类型。

    然后

    int (*g)(struct toto { int a; });
    

    又是一个函数指针的声明。该指针是指向基类型int ()(struct toto { int a; }) 的指针,该类型是具有原型的函数类型。

    或者总而言之,一个指向某事物的指针的声明也声明了该指针指向的那个事物。

    【讨论】:

    • 这没有意义。声明声明了一个标识符。在这个例子中,g 是一个标识符,它被声明为 pointer to function 类型。如果这也声明函数类型,则必须有第二个名称。
    • 该声明中的 ... 仍然 struct foo 是结构类型的(前向)声明... 是 6.7.2.3(6) 的主题。标准中提到声明符(6.7.6)的相应部分不包括这样的语言,但是,关于声明符(struct ... 是一个类型说明符),而是非常小心地使用了术语specify(not declare) 引用各种类型的声明符时。
    • @alexsh:但是在第二个 next 子句中,它使用参数的单词 declaration 作为“函数返回类型”>>
    • @rici:参数声明是一个有效的语法概念。 C11 甚至引入了“类型名称范围”的概念来专门处理类型名称,因此它可以将无名参数视为声明。但是,在我上面的示例中,没有函数声明,作为参数或其他方式。
    【解决方案3】:

    所以void (*f)( struct s { int c; } );这样的声明不是函数的声明(它是指向函数的指针的声明)...

    我不同意。

    第 6.7.6 条声明符包含:

    语法

    • 声明者:

      指针opt 直接声明符

    • 直接声明符:
      标识符
      (声明者)
      直接声明符 [ 类型限定符列表opt assignment-expressionopt ]
      直接声明符 [静态类型限定符列表opt 赋值表达式]
      直接声明符 [类型限定符列表静态赋值表达式]
      直接声明符 [ 类型限定符列表opt * ]
      直接声明符(参数类型列表)
      直接声明符(标识符列表opt)
    • 指针:
      * 类型限定符列表选择
      * 类型限定符列表opt 指针
      ...

    我对那个子句的理解是void (*f)( struct s { int c; } );确实是一个指针声明,但是那个指针声明在它的direct-declarator部分包含了一个函数原型定义。所以函数原型定义的所有语义都适用。

    所以struct s的作用域是指针声明的直接声明部分的函数原型声明。并且 6.2.1 的 §4 说:

    如果出现声明标识符的声明符或类型说明符 在函数原型中的参数声明列表中(不是函数的一部分 定义),标识符具有函数原型范围,它终止于 函数声明器。

    我们正是在那种情况下(没有定义),所以struct s的声明范围在行尾结束。

    因此,当 gcc 生成诊断性“不完整类型数组”(以及有关 s 范围仅限于参数列表的警告)时,gcc 是完全正确的,表明 s 具有函数原型范围,即使形式上没有函数声明.

    【讨论】:

    • 正如我在问题开头所写的,“函数原型”的概念在标准中定义为“...函数 声明 ...”(不是声明符)。该标准没有关于一个声明包括另一个声明的任何语言,但是这种解释必须是诱人的。此外(我在这里吹毛求疵),标准中没有提到“函数原型定义”之类的东西。没有原型,没有范围。
    • @alexsh: 6.7.6 §3 将完整声明符定义为不属于另一个声明符的声明符。意图在这里很明确。
    • 确实,声明符的嵌套意图很明确。但是,声明是另一回事。
    【解决方案4】:

    实际上参数或参数是函数Prototype部分的1。 一个函数的原型有 3 个主要组成部分..这些是 1.功能类型 2.函数名称 3. 参数或参数

    【讨论】:

      猜你喜欢
      • 2011-08-07
      • 2011-06-12
      • 1970-01-01
      • 2020-06-08
      • 2015-04-12
      • 2012-07-15
      • 2021-03-17
      • 1970-01-01
      相关资源
      最近更新 更多