【问题标题】:C Prototype scopeC 原型范围
【发布时间】:2010-12-16 17:34:24
【问题描述】:

我知道了

声明的类型说明符 参数列表中的标识符 函数原型中的声明 (不是函数定义的一部分), 标识符有函数原型 范围,在结束时终止 函数声明器。

请看下面提到的C程序。

void fn (struct st {int a;} a, struct st b) ;

struct st obj ;

编译器会立即发出错误,因为“obj”大小未知(或)struct st 不是“类型”。这是正确的!结构“struct st”的声明在原型声明处结束。

我相信原型有这个限制,因为我们也可以在原型声明中使用一些变量名。这些名称可能与同一范围内的变量(如函数原型)冲突。如下所示。

void fn (int a) ;
int a ;

因此,为了允许上述声明,原型的范围是有限的。 (如果我错了,请纠正我)

但是,对于原型声明,参数变量名是没有用的。那么,为什么它是“狭义的”?有参数变量名有什么意义?语言设计者(或)规范对此有何想法?

【问题讨论】:

  • 你写“允许”的地方我认为你的意思是“范围狭窄”,其他人可能会认为“不允许在范围之外使用”。因此,当另一个短语(例如“范围狭窄”)可能起作用时,我会避免使用“允许”这个词。
  • @Heath 谢谢!我已经更正了。
  • 我更喜欢总是 typedef 我将要使用的结构,并且没有遇到这个问题。
  • 你在编写代码时需要始终牢记可读性,一些技术性的东西可能会根据这个或那个 ANSI-C 规则工作,但主要的事情是编写人们可以阅读而不是误解的代码。跨度>
  • @Anders 没错!我总是可以用好的 cmets 做到这一点。但是为什么语言支持这些东西只是为了可读性呢?

标签: c prototype scope designer specifications


【解决方案1】:

原型作用域的一种可能用法是声明一个函数,该函数需要传递void * 类型的指针,传递其他类型的指针会导致诊断消息。如果您将函数原型化为:

int f(void *);

那么传递任何指针类型都是有效的。但是如果你将函数原型化为:

int f(struct dummy *);

那么就没有办法为函数声明兼容的指针来传递,而函数本质上需要一个void *参数。

这在现实世界中是否有用,它是美丽的还是丑陋的,这些都是我改天讨论的话题。

【讨论】:

    【解决方案2】:

    我会回应 chrisharrisJonathan Leffler 所说的话。将标识符限定在原型范围内很有用。比没有它们更有用。此外,它让原型遵循与函数声明相同的语法,从语言设计的角度和实现者的角度来看,这可能都很好。

    我认为这不是您的提议,但为了完整起见,使这些标识符具有原型之外的范围几乎没有意义,并且可能会导致问题。

    你可以在原型中声明一些在原型之外无用的东西是一个缺点(我猜),但它是无害的。解决方法是“不要那样做”。我不知道让你声明不能在原型之外使用的东西有任何隐藏问题。

    如果我们要解决这个问题,我们还应该解决 C 允许您声明类似以下内容的事实:

    struct {
        int obj;
    };
    

    它声明了一个不太有用的结构类型。

    【讨论】:

      【解决方案3】:

      参数名称可以帮助记录参数的使用。

      考虑一个内存设置函数:

      void mem_set(void *, int, int);
      
      void mem_set(void *buffer, int value, int nbytes);
      

      哪个更容易理解?


      所写的结构类型声明对于函数原型来说是局部的。您可能知道(现在,如果不是以前),您需要在原型范围之外定义类型才能成功使用它。也就是说,你必须写:

      struct st {int a;};
      void fn(struct st a, struct st b);
      

      你说:

      我相信原型有这个限制,因为我们也可以在原型声明中使用一些变量名。这些名称可能与同一范围内的变量(如函数原型)冲突。如下所示。

      void fn(int a);
      int a;
      

      因此,为了允许上述声明,原型的范围是有限的。 (如果我错了,请纠正我)

      带有'-Wshadow'的GCC有可能会警告参数'a'遮蔽了全局变量'a' - 它肯定会在函数定义中这样做,并且可能在函数声明中这样做。但这不是强制性警告;编写的代码是合法的 C - 尽管由于阴影而有点可疑。


      cmets 中有一个关于“为什么 C 限制(阻止)你在参数列表中声明类型”的长期讨论,子文本“因为 C++ 确实允许你这样做”:

      评论

      允许使用 /**/,程序员有责任(根据编码实践)在此处添加有关该语言使用的适当 cmets。我相信除了向 cmets 提供帮助之外,还必须有“一些东西”。 – Ganesh Gopalasubramanian

      好的-相信吧。其余的原因是与 C++ 所做的兼容,并在此处添加了参数名称以提高可读性。请参阅 Stroustrup 的“C++ 的设计和演变”。请注意,原型中的参数名称不是接口的一部分 - 请参阅有关按名称而不是位置提供参数的讨论。 ——乔纳森·莱弗勒

      我相信 OP 提出的问题是“拥有函数原型范围有什么意义?”。不幸的是,您的回答并没有说明任何问题。坦白说,我也不知道。如果他们只是想像 OP 猜测的那样限制命名参数声明的范围(在非定义声明中),他们可以在不引入范围的情况下完成它(例如在 C++ 中所做的)。 - 安德烈T

      @AndreyT:在函数定义中,参数是函数体的局部参数(不再可能通过函数体最外层块中的局部变量隐藏参数)。原型逻辑上在函数内部声明了一个类型,因此应该按照定义的范围进行定义 - 因此,仅在函数原型中定义的类型不能在函数外部使用,并且无法调用的函数对任何人都没有什么好处。 ——乔纳森·莱弗勒

      @Jonathan Leffler:您似乎在解释为什么允许使用参数名称(“与 C++ 的兼容性” - 好的)。我想知道的是引入“函数原型范围”的基本原理。为什么他们认为有必要引入这样的范围? C++ 不会那样做。 C++ 中没有函数原型作用域。 - 安德烈T

      @AndreyT 是的!我们都淹死在同一条船上:) – Ganesh Gopalasubramanian

      反例

      这表明 C++“确实是这样做的”。

      #include <cstdio>
      using namespace std;
      
      extern void x(struct c {int y;} b);
      
      void x(struct c b)
      {
          printf("b.y = %d\n", b.y);
      }
      
      int main()
      {
          struct c a;
          a.y = 0;
          x(a);
          return(0);
      }
      

      此代码不能使用 G++(MacOS X 10.5.8 上的 4.0.1)编译。它抱怨:

      $ g++ -o xx xx.cpp
      xx.cpp:4: error: types may not be defined in parameter types
      $
      

      错误发生在默认级别的警告/错误 - 以及迂腐级别。

      因此,在这种情况下说“C 的行为与 C++ 一样”似乎是公平和准确的。你能用一个反例来演示如何在 C++ 的函数原型中定义一个类型,并指定哪个 C++ 编译器和平台允许它吗?

      【讨论】:

      • 被允许使用 /**/,程序员有责任(根据编码实践)在此处添加有关该语言使用的适当 cmets。我认为除了向 cmets 提供帮助之外,还必须有“一些东西”。
      • 我相信 OP 提出的问题是“拥有函数原型范围有什么意义?”。不幸的是,您的回答并没有说明任何问题。坦白说,我也不知道。如果他们只是想像 OP 猜测的那样限制命名参数声明的范围(在非定义声明中),他们可以在不引入范围的情况下完成它(例如在 C++ 中所做的)。
      • 好吧——相信吧。其余的原因是与 C++ 所做的兼容,并在此处添加了参数名称以提高可读性。请参阅 Stroustrup 的“C++ 的设计和演变”。请注意,原型中的参数名称不是接口的一部分 - 请参阅有关按名称而不是位置提供参数的讨论。
      • @AndreyT BTW 什么是 OP?我是新手 :)
      • @Ganesh:OP 是“Original Poster”的缩写,意思是提出问题的人。
      【解决方案4】:

      两个 - 让我们说三个 - 在函数原型中使用参数名称的明显原因:

      • 名称表示每个参数的用途。
      • 名称让您可以更轻松地在 API 文档中引用参数(例如在 Doxygen 中)。
      • 您可以更轻松地根据定义生成原型(剪切和粘贴,或自动化)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-03-28
        • 1970-01-01
        • 1970-01-01
        • 2011-09-22
        • 1970-01-01
        • 1970-01-01
        • 2011-05-06
        • 2015-06-19
        相关资源
        最近更新 更多