【问题标题】:Must declare function prototype in C? [duplicate]必须在 C 中声明函数原型吗? [复制]
【发布时间】:2011-02-04 05:34:48
【问题描述】:

我对 C 有点陌生(我以前有 Java、C# 和一些 C++ 经验)。在 C 语言中,是否需要声明函数原型,或者代码可以在没有它的情况下编译吗?这样做是一种好的编程习惯吗?还是仅取决于编译器? (我在 Code::Blocks IDE 下运行 Ubuntu 9.10 并使用 GNU C 编译器或 gcc)

【问题讨论】:

标签: c function function-prototypes


【解决方案1】:

从不需要为 C 中的函数声明 原型,无论是在“旧”C(包括 C89/90)还是在新 C(C99)中。但是,在函数声明方面,C89/90 和 C99 之间存在显着差异。

在 C89/90 中,根本不需要声明函数。如果函数未在调用时声明,编译器会从调用中传递的参数类型隐式“猜测”(推断)声明,并假定返回类型为int

例如

int main() {
  int i = foo(5); 
  /* No declaration for `foo`, no prototype for `foo`.
     Will work in C89/90. Assumes `int foo(int)` */

  return 0;
}

int foo(int i) {
  return i;
}

在 C99 中,您调用的每个函数都必须在调用点之前声明。但是,仍然没有必要专门用 prototype 声明它。非原型声明也可以。这意味着在 C99 中,“隐式 int”规则不再有效(在这种情况下,对于推断的函数返回类型),但如果函数声明时没有原型,参数类型仍然可以从参数类型中猜出。

前面的例子不能在 C99 中编译,因为 foo 没有在调用时声明。但是,您可以添加非原型声明

int foo(); /* Declares `foo`, but still no prototype */

int main() {
  int i = foo(5); 
  /* No prototype for `foo`, although return type is known. 
     Will work in C99. Assumes `int foo(int)` */

  return 0;
}
...

最终得到有效的 C99 代码。

尽管如此,在调用函数之前声明一个原型总是一个好习惯。

补充说明:我在上面说过,从来不需要声明函数原型。事实上,对于某些功能,这是一个要求。为了在 C 中正确调用 variadic 函数(例如printf),必须在调用点之前使用原型声明该函数。否则,行为未定义。这适用于 C89/90 和 C99。

【讨论】:

  • +1 以获得详细的答案,所有的细微差别都得到了很好的解释
  • "will not compile in C99" -- gcc 上没有编译器错误(只有警告),并且在我的系统上使用-std=c99 发出叮当声。 ideone also compiles it
  • @jfs GCC 文档附带帮助:"(...) 要获得标准所需的所有诊断,您还应该指定 -pedantic(或 -pedantic-errors,如果您希望它们是错误而不是警告)(...)”。
  • @Przemek 你的意思是什么?您是否想说“-std=c99”不足以声明它“将在 c99 中编译”?也许你是对的,这取决于某人想要准确地说什么,你的解释可能更接近答案的作者“不会在 C99 中编译”的意思
  • @jfs,因为在 GCC 中“-std=c99”表示 c99 标准加上 gnu 扩展。该问题可能会因为 GNU 扩展允许它而编译。您需要为 GCC 添加“-Wpedantic”以发出严格 ISO C 要求的所有警告。
【解决方案2】:

在 ANSI C(意为 C89 或 C90)中,您不必声明函数原型;但是,使用它们是最佳实践。该标准允许您不使用它们的唯一原因是为了向后兼容非常旧的代码。

如果你没有原型,而你调用了一个函数,编译器会从你传递给函数的参数中推断出一个原型。如果稍后在同一个编译单元中声明该函数,如果函数的签名与编译器猜测的不同,则会出现编译错误。

更糟糕的是,如果函数在另一个编译单元中,则无法获得编译错误,因为没有原型就无法检查。在这种情况下,如果编译器出错,如果函数调用在堆栈上推送的类型与函数预期的不同,您可能会得到未定义的行为。

惯例是始终在与包含函数的源文件同名的头文件中声明原型。

在 C99 或 C11 中,标准 C 要求在调用任何函数之前在范围内声明函数。许多编译器在实践中不会强制执行此限制,除非您强制他们这样做。

【讨论】:

  • 如果实际函数类型与推断的不同,您不一定会得到“编译错误”。这是 C 中未定义的行为,但不是违反约束。
  • 你说得对,应该更具体一点,我的意思是大多数编译器都会抛出警告。
  • 在 C99 中,您确实需要一个函数声明——尽管我认为它不必是原型声明。 C90 (C89) 对此要松懈得多。请注意,如果函数使用“可变参数”,则您确实需要范围内的原型 - 即使在 C89/C90 中也是如此。
  • @JeremyP:默认情况下,编译器仍然(也是?)宽容,主要是因为遗留代码库仍然需要更新。编译器不会强制执行标准的字母,除非你强迫他们(例如-Werror,也许-pedantic-Wold-style-definition-Wold-style-declaration-Wmissing-prototypes-Wstrict-prototypes 或全部(或大多数) 上述的)。我通常使用-std=c11 和除-pedantic 之外的所有其他人,但我确保POSIX 或相关的#define 处于活动状态。我不经常写#define _GNU_SOURCE#define _BSD_SOURCE
  • @JonathanLeffler:我见过一些系统对非原型函数使用不同的默认调用约定,而不是用于带参数的原型函数,并且使用不同的链接- 时间命名。如果函数具有原型并且未标记为使用基于堆栈的参数,则尝试在没有可见声明的情况下调用它将导致链接时错误。
【解决方案3】:

如果函数在使用之前定义,则不是必须的。

【讨论】:

  • 使用之后定义它有什么意义?
  • 只是文件中代码的逻辑顺序,或者函数在其他文件中定义。
  • 事实上,新式的函数定义既是声明,也是原型。如果定义发生在任何使用之前,那么另一个没有主体的原型将毫无意义。
【解决方案4】:

这不是必需的,但不使用原型是不好的做法。

使用原型,编译器可以验证您是否正确调用了函数(使用正确数量和类型的参数)。

没有原型,也有可能:

// file1.c
void doit(double d)
{
    ....
}

int sum(int a, int b, int c)
{
    return a + b + c;
}

还有这个:

// file2.c

// In C, this is just a declaration and not a prototype
void doit();
int sum();

int main(int argc, char *argv[])
{
    char idea[] = "use prototypes!";

    // without the prototype, the compiler will pass a char *
    // to a function that expects a double
    doit(idea);

    // and here without a prototype the compiler allows you to
    // call a function that is expecting three argument with just
    // one argument (in the calling function, args b and c will be
    // random junk)
    return sum(argc);
}

【讨论】:

  • @NeilButterworth:编译 file2.c 时,doit 的定义(在 file1.c 中)不可见。
  • @NeilButterworth - 我将重新格式化,但 cmets // file1.c// file2.c 意味着这些是不同的文件。编译file2.c时,编译器将看不到file1.c中的定义
  • 我的评论在您的编辑之前。我会删除它。
【解决方案5】:

在C语言中,如果我们不声明函数原型并使用函数定义,则没有问题,如果函数的返回类型为“整数”,程序将编译并生成输出。在所有其他情况下,都会出现编译器错误。原因是,如果我们调用一个函数但没有声明函数原型,那么编译器会生成一个返回整数的原型,然后它会搜索类似的函数定义。如果函数原型匹配,则编译成功。如果返回类型不是整数,则函数原型不匹配并生成错误。所以最好在头文件中声明函数原型。

【讨论】:

    【解决方案6】:

    C 允许调用之前没有声明过的函数,但我强烈建议您在使用它们之前为所有函数声明一个原型,以便编译器可以在您使用错误的参数时拯救您。

    【讨论】:

      【解决方案7】:

      您应该将函数声明放在头文件 (X.h) 中,将定义放在源文件 (X.c) 中。然后其他文件可以#include "X.h" 并调用该函数。

      【讨论】:

      • 为什么是-1?这是你应该做的。
      • 有人能解释一下为什么这是一个坏主意吗,我一直认为这是标准做法?由此产生的唯一问题是循环包含。但是,否则,如果您计划包含您将不会遇到这个问题。
      • 因为并非所有功能都是公开的。如果你有 static void foo() 定义 after static int bar(),并且 bar 调用 foo .. 好吧 :) 你为什么要原型化不暴露在公共标头中的函数?不过,我不是投反对票的人。
      • 我认为反对票(不是来自我)是因为这不能解决问题。
      • @Tim,bar 可以调用 foo 因为编译器已经从原型中知道了它。除非这在 C 中不正确,否则在 C++ 中应该是正确的。您能否发布一个示例,以便我们可以更好地了解您的意思? @Neil即使它没有直接回答问题,但它确实与与我认为不需要投票的问题相关的“最佳实践”有关。也许不是赞成票,但这不是我想要删除或不必要的东西。
      【解决方案8】:

      根据C99 标准,函数原型不是强制性的。

      【讨论】:

        【解决方案9】:

        声明一个函数来编译调用代码并不是绝对必要的。不过有一些警告。假定未声明的函数返回int,编译器将首先发出有关未声明函数的警告,然后发出有关返回类型和参数类型不匹配的警告。

        话虽如此,使用原型正确声明函数显然是一种更好的做法。

        【讨论】:

          【解决方案10】:

          选择“选项”菜单,然后选择“编译器 | C++ 选项'。在弹出的对话框中,选择‘CPP always’ 在“使用 C++ 编译器”选项中。 再次选择“选项”菜单,然后选择“环境 | 编辑'。确保默认扩展名是“C”而不是 'CPP'。

          【讨论】:

          • 这似乎没有回答所提出的问题,而是关于特定 IDE 的一些松散相关的问题。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2017-07-16
          • 2012-07-15
          • 2018-07-03
          • 2012-07-16
          • 2011-08-07
          • 2014-10-10
          • 1970-01-01
          相关资源
          最近更新 更多