【问题标题】:Function declaration inside of function — why?函数内部的函数声明——为什么?
【发布时间】:2015-06-27 02:42:05
【问题描述】:

我正在阅读“用 C 语言编程”一书,在第 10 章中找到了这样一个示例:

#include <stdio.h>

void test (int  *int_pointer)
{
     *int_pointer = 100;
}

int main (void)
{
     void test (int  *int_pointer);
     int  i = 50, *p = &i;

     printf ("Before the call to test i = %i\n", i);

     test (p);
     printf ("After the call to test i = %i\n", i);

     return 0;
}

我理解这个例子,但我不理解 void test (int *int_pointer); 内的 main 行。为什么我又定义了test的签名?那是惯用的 C 吗?

【问题讨论】:

  • 这是多余的。你不需要。
  • 这是一个声明,它告诉编译器某处有一个名为test 的函数,它接受指定的参数并且不返回任何内容。它也被称为“函数原型”。在您的简单示例中您并不真正需要它,因为函数原型已经用函数定义声明,但如果您例如将定义移至main 函数下方,或移至另一个源文件。声明就是声明,声明就是声明,你可以在允许放置声明的地方放置任何你想要的声明。
  • @JoachimPileborg; “声明就是声明,声明就是声明,你可以在允许放置声明的地方放置任何你想要的声明。”?
  • @hacks 我的意思是声明就是声明,无论它声明什么。如果语言允许声明,则可以放置任何类型的声明。
  • @Olaf:我不同意你的说法。可能有一些极端和奇怪的情况是必要的,但我不同意有很多这样的情况。此外,在可能有必要的少数情况下,我认为代码将存在重大维护问题。可能标头试图过于全面,或者代码正在尝试做一些它确实不应该做的事情。

标签: c function


【解决方案1】:

void test (int *int_pointer); 只是函数test 的声明(或原型)。 main 中不需要此声明,因为您在 main 之前已经有函数定义。

如果test 的定义在main 之后,那么值得将其声明放在那里,让编译器在调用它之前知道test 的返回类型、参数数量和参数类型。

【讨论】:

    【解决方案2】:

    它绝对不是惯用的 C,尽管它是完全有效的(多个声明是可以的,多个定义不是)。这是不必要的,因此没有它,代码仍然可以完美运行。

    如果有的话,也许作者是故意的

    void test (int *int_pointer);
    
    int main (void) {
    
        ...
    
    }
    

    如果函数定义放在main ()之后。

    【讨论】:

    • 我也不认为你的两个链接是可靠的,int main(void) 是完全地道的。也许您应该打开警告,看看为什么您更喜欢void 而不是未使用的参数。
    • 使用-Wall -Wextra,他们确实会警告未使用的argcargv 与任何其他功能一样。这没关系,因为当您不使用命令行参数时,您supposed 无论如何都要使用int main(void)。仅仅因为某事困扰你并不意味着它不是惯用的。
    • 我想说的是:(void) 很好,标准且惯用。错误引用另一个答案并困扰您并不会改变这一点。
    • @haneefmubarak 你在争论一个明显错误的观点:C 标准的确切说法是“程序启动时调用的函数名为main。实现未声明此函数的原型。应使用返回类型int无参数定义。然后它继续描述argc/argv case 和其他选项,但(void) 字面上是它描述的第一件事; more 惯用语是不可能的。 (这甚至与 OP 无关!)
    • 请注意,HowStuffWorks 参考和 Learn X in Y minutes where X = C 参考都使用int main(),这不支持您关于main()签名的论文。 C 标准也不支持你的论文;它明确表示int main(void) 可以。
    【解决方案3】:

    这不是惯用的;您通常会在无法按顺序获取头文件的代码中看到它。

    任何函数要么只在一个文件中使用,要么在多个文件中使用。如果只在自己的文件中使用,应该是静态的。如果它在多个文件中使用,它的声明应该在一个头文件中,并且任何使用它的人都应该包含头文件。

    你在这里看到的风格很糟糕(函数应该是静态的,或者声明应该取自标头风格),而且毫无意义,因为编译器已经可以看到声明。由于该函数在同一个文件中,因此并不危险;如果声明和函数不匹配,编译器会告诉你。当函数在不同的文件中时,我经常看到这种事情; 那个很危险。如果有人更改功能,程序很可能会崩溃或行为不端。

    【讨论】:

      【解决方案4】:

      它是完全惯用的 C,它实际上有一个(有限的)实际用途——尽管这个例子没有证明这一点。

      当您在通常的全局级别声明一个函数或其他名称时,它会被纳入声明后代码中所有函数体的范围。声明一旦被引入,就不能从范围中删除。该功能对翻译单元的其余部分永久可见。

      当您在大括号块中声明函数或其他名称时,声明的范围仅限于该块。在另一个函数的范围内声明一个函数将限制其可见性,并且不会污染全局命名空间或使其对同一翻译单元中定义的任何其他函数可见。

      在示例的情况下这是没有意义的,因为test 的定义也将它带入了所有后续主体的范围 - 但是如果test 是在另一个翻译单元中定义的,或者即使它只定义在在这个 TU 的最底部,将声明隐藏在 main 中将保护之后定义的任何其他函数无法在其范围内看到其名称。

      实际上,这用途有限 - 通常如果您不希望某个功能可见,则将其放在另一个翻译单元中(最好将其设为 static) - 但您可能会设计一种情况您可能希望使用此功能来构建一个不导出其组件的原始声明或类似内容的模块加载系统(并且这不依赖于static/单独的目标文件这一事实可能与嵌入式/非托管目标环境有一定的相关性,其中链接步骤可能无法像在 PC 上那样工作,允许您在纯基于#include 的构建系统中实现命名空间保护措施)。

      例子:

      struct module {
          void * (* alloc)(size_t);
          void (* dealloc)(void *);
      } loaded_module;
      
      int main(void) {
          if (USE_GC) {   // dynamically choose the allocator system
              void * private_malloc_gc(size_t);
              void private_free_noop(void *);
              loaded_module = (struct module){ private_malloc_gc, private_free_noop };
          } else {
              void * private_malloc(size_t);
              void private_free(void *);
              loaded_module = (struct module){ private_malloc, private_free };
          }
          do_stuff();
          //...
      }
      
      // cannot accidentally bypass the module and manually use the wrong dealloc
      void do_stuff(void) {
          int * nums = module.alloc(sizeof(int) * 32)
          //...
          module.dealloc(nums);
      }
      
      #include "allocator_implementations.c"
      

      【讨论】:

      • 您说:当您在花括号块中声明函数或其他名称时,声明的范围仅限于该块。在另一个函数的范围内声明一个函数会限制它的可见性,并且不会污染全局命名空间或使其对同一翻译单元中定义的任何其他函数可见。第一句很好。后半段的前半段还好。 “不污染全局命名空间”无效。该函数只能在全局命名空间中定义一次,除非它在文件中是静态的。 [...继续...]
      • [...continuation...] 在其他函数中声明一个函数的缺点是你可以在不同的函数中有不同的声明,但它们都会结束up 引用相同的实际功能,然后就需要付出所有的代价(在运行时!)。不要在其他函数中声明函数。另请参阅我对主要问题的 cmets。
      • @JonathanLeffler 这些观点更适用于其他语言而不是 C; C 的顶级范围是 a) 有序且 b) 在翻译单元之间不共享。即使可以从模块中的任何位置访问外部链接的函数,如果未声明名称,它也不在范围内;声明的顺序可能意味着这对 TU 中的某些函数也有效,具体取决于其结构。函数并非始终都在范围内。
      • @Leushenko:这不是关于范围,而是关于一致性和可靠性。如果您在源文件中、在全局范围或函数内部的局部范围内声明外部函数,则此声明只是程序员的一个承诺,编译器无法对其进行简单检查。 @Jonathan Leffler 说这样的函数声明属于一个头文件,将包含在所有使用定义其实现的模块中的函数 and 的模块中,以检查一致性。 gccclang 发出警告以强制执行此有用的约定并防止代码生成不一致。
      • 在函数范围内进行外部声明的典型用法是具有大型(外部)查找表的内联函数。另一个是系统级函数,它引用链接器提供的符号,例如 heap_start。这些只需要在受影响的函数内可见。当然,如果更多功能需要这些,CU-scope 将是首选。但是,将它放在外部头文件中仍然没有任何好处。
      【解决方案5】:

      它不是惯用的 C,但仍然有效。

      该行是函数test 的声明,而不是定义。一个函数不能被多次定义,但可以多次声明。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-12-20
        • 2019-02-08
        • 2015-02-10
        • 1970-01-01
        • 2011-06-23
        • 2012-02-18
        • 2011-07-03
        • 1970-01-01
        相关资源
        最近更新 更多