【问题标题】:Implicit function declarations in CC 中的隐式函数声明
【发布时间】:2012-02-29 06:21:39
【问题描述】:

术语“函数的隐式声明”是什么意思?调用标准库函数而不包含适当的头文件会产生警告,如下所示:

int main(){
  printf("How is this not an error?");
  return 0;
}

不应该在不声明它是错误的情况下使用函数吗?请详细说明。我搜索了这个网站,发现了类似的问题,但找不到明确的答案。大多数答案都提到了包含头文件以消除警告,但我想知道这不是错误。

【问题讨论】:

  • 标准 C 库默认链接到构建中;例如,使用 gcc,您必须将 -nostdlib 作为参数显式传递给编译,以强制它不与 libc 链接。
  • @tbert 这就是链接器不抱怨的原因,但链接器对编译器对 C 代码的处理几乎没有影响。
  • 我查看了 K&R,它说如果在范围内没有可见的函数的先前声明,则假定函数使用的第一个实例是返回类型为 int 的声明,并且没有任何假设参数。谢谢大家的意见。

标签: c


【解决方案1】:

这应该被认为是一个错误。但是 C 是一门古老的语言,所以它只是一个警告。
使用-Werror (gcc) 进行编译可以解决这个问题。

当 C 没有找到声明时,它假定这个隐式声明:int f();,这意味着函数可以接收你给它的任何东西,并返回一个整数。如果这恰好足够接近(并且在printf 的情况下,它是),那么事情就可以工作了。在某些情况下(例如函数实际上返回一个指针,并且指针大于整数),它可能会造成真正的麻烦。

请注意,这已在较新的 C 标准(C99、C11)中得到修复。在这些标准中,这是一个错误。但是,gcc 默认没有实现这些标准,所以您仍然会收到警告。

【讨论】:

  • 您的回答是正确的,并且完全符合 K&R 的说法。感谢您的简明解释。
  • 请注意,即使在允许隐式声明的日子里,它们仍然会导致像 printf 这样的可变参数函数的 UB。
  • @R..,原则上你是对的。在实践中,大多数实现都处理一个给定 n 个参数的可变参数函数,就像一个有 n 个参数的普通函数一样,所以一切正常。
  • 这个答案在 24 年前是正确的。今天是错的。当前的 C 标准 (C11) 和广泛实施的前一个标准 (C99) 都明确禁止调用未声明的函数。
  • @ugoren 一个非常流行的编译器 clang 默认为 C99,而另一个非常重要的编译器 gcc 支持它。市场上唯一广泛使用的编译器,微软的 MSVC,是唯一不支持 C89 的编译器。所以,“编译器不使用更新的标准”是不正确的。
【解决方案2】:

隐式声明在 C 中无效。

C99 删除了此功能(存在于 C89 中)。

gcc 选择默认只发出警告,-std=c99 但编译器有权拒绝翻译这样的程序。

【讨论】:

  • 在相关说明中,-pedantic-errors 将使 GCC(或 CLang,就此而言)按照此处的标准运行,并拒绝编译(阅读:发出错误并中止)。
【解决方案3】:

为了完成图片,由于-Werror 可能被认为过于“侵入性”,
对于 gcc(和 llvm),更精确的解决方案是将只是此警告转换为错误,使用选项:

-Werror=implicit-function-declaration

Make one gcc warning an error?

关于-Werror 的一般用途:当然,建议使用无警告代码,但在某些开发阶段可能会减慢原型设计速度。

【讨论】:

  • 相反,只是让来自-Werror的烦人静音。
【解决方案4】:

由于历史原因可以追溯到 C 的第一个版本,假定函数具有 int function(int arg1, int arg2, int arg3, etc) 的隐式定义。

编辑:不,我对int 的论点有误。相反,它传递参数的任何类型。所以它可能是intdoublechar*。如果没有原型,编译器将传递任何大小的参数,并且被调用的函数最好使用正确的参数类型来接收它。

更多详情请查看K&R C

【讨论】:

    【解决方案5】:

    C 是一种非常低级的语言,因此它允许您创建几乎任何您能想到的合法对象 (.o) 文件。您应该将 C 视为基本修饰过的汇编语言。

    特别是,C 不需要在使用函数之前声明它们。如果您在没有声明的情况下调用函数,则该函数的使用将成为它的(隐式)声明。在我刚刚运行的一个简单测试中,这只是对像 printf 这样的内置库函数(至少在 GCC 中)的警告,但对于随机函数,它会编译得很好。

    当然,当你尝试链接时,它找不到 foo,那么你会得到一个错误。

    对于像 printf 这样的库函数,一些编译器包含对它们的内置声明,因此它们可以进行一些基本的类型检查,所以当隐式声明(来自使用)与内置声明不匹配时,你会收到警告。

    【讨论】:

    • 知道为什么 gcc 会为 printf 这样的“内置函数”这样做吗?我似乎可以很好地使用用户定义的函数。
    • “特别是,C 不需要在使用函数之前声明它们。” - 确实如此。 C89(允许这样做)不是当前标准。 C11 是,而且 C11 和它过于流行的前身 C99 都不允许调用隐式声明的函数。
    【解决方案6】:

    隐式声明的函数既没有原型也没有定义,但在代码中的某处被调用。因此,编译器无法验证这是函数的预期用途(参数的计数和类型是否匹配)。解析对它的引用是在编译之后在链接时完成的(与所有其他全局符号一样),因此从技术上讲,跳过原型不是问题。

    假设程序员知道自己在做什么,这是省略提供原型的正式合同的前提。

    如果使用错误类型或计数的参数调用函数,可能会发生严重错误。最可能的表现是堆栈损坏。

    现在这个功能可能看起来很奇怪,但在过去,它是一种减少包含的头文件数量的方法,因此编译速度更快。

    【讨论】:

    • 需要引用。我没有将隐式声明作为改进编译时间的功能包含在内。据我所知,C 的早期演变(从 B 开始)除了 int 之外没有类型,因此了解函数的类型信息并不那么重要......
    • 你所说的“原型”是指“声明”。有些声明不是原型。
    猜你喜欢
    • 2016-08-10
    • 1970-01-01
    • 1970-01-01
    • 2014-03-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多