4

最近我了解了C中的隐式函数声明。主要思想很清楚,但是在这种情况下,我在理解链接过程时遇到了一些麻烦。

考虑以下代码(文件ac):

#include <stdio.h>

int main() {
    double someValue = f();
    printf("%f\n", someValue);
    return 0;
}

如果我尝试编译它:

gcc -c a.c -std=c99

我看到有关函数隐式声明的警告f()

如果我尝试编译和链接:

gcc a.c -std=c99

我有一个未定义的参考错误。所以一切都很好。

然后我添加另一个文件(文件bc):

double f(double x) {
    return x;
}

并调用下一个命令:

gcc a.c b.c -std=c99

令人惊讶的是,一切都已成功链接。当然,在./a.out调用之后,我看到了垃圾输出。

所以,我的问题是:具有隐式声明函数的程序如何链接?在我的示例中,在编译器/链接器的引擎盖下会发生什么?

我阅读了一些关于 SO 的主题,例如thisthisthis ,但仍然有问题。

4

4 回答 4

5

首先,C99从标准中删除了函数的隐式声明。编译器可能支持这个来编译遗留代码,但这不是强制性的。引用标准的前言,

  • 删除隐式函数声明

也就是说,根据C11第 §6.5.2.2 章

如果函数是用不包含原型的类型定义的,并且提升后的参数类型与提升后的参数类型不兼容,则行为未定义。

所以,在你的情况下,

  • 函数调用本身是隐式声明(自 C99 以来已成为非标准),

  • 并且由于函数签名不匹配[假定函数的隐式声明具有int返回类型],您的代码会调用未定义的行为

只是为了添加更多参考,如果您尝试在调用后在同一编译单元中定义函数,则会由于签名不匹配而导致编译错误。

但是,您的函数在单独的编译单元中定义(并且缺少原型声明),编译器无法检查签名。编译后,链接器获取目标文件,并且由于链接器中没有任何类型检查(目标文件中也没有信息),所以很高兴地链接它们。最后,它将以成功的编译和链接UB结束。

于 2016-01-04T19:15:59.617 回答
3

这是正在发生的事情。

  1. 如果没有 的声明f(),编译器会假定一个隐式声明,如int f(void). 然后愉快地编译a.c
  2. 编译b.c时,编译器没有任何事先声明f(),所以它从f(). 通常你会将一些声明f()放在头文件中,并将其包含在a.c和中b.c。因为这两个文件都将看到相同的声明,所以编译器可以强制执行一致性。它将抱怨与声明不匹配的实体。但在这种情况下,没有通用的原型可供参考。
  3. C中,编译器不会在目标文件中存储有关原型的任何信息,并且链接器不会执行任何一致性检查(它不能)。它所看到的只是一个未解析的符号和一个定义在 中f的符号。它愉快地解析了符号,并完成了链接。a.cfb.c
  4. 但是,事情在运行时会崩溃,因为编译器会a.c根据它在那里假设的原型设置调用。b.c这与寻找的定义不匹配。f()(from b.c) 将从堆栈中获取一个垃圾参数,并将其返回为double,这将被解释为inton return in a.c
于 2016-01-04T19:52:11.667 回答
2

具有隐式声明函数的程序如何链接?在我的示例中,在编译器/链接器的引擎盖下会发生什么?

自C99 以来,C 标准已禁止隐式 int规则。因此,具有隐式函数声明的程序是无效的。

自 C99 起无效。在此之前,如果一个可见原型不可用,那么编译器会隐式声明一个带有int返回类型的原型。

令人惊讶的是,一切都已成功链接。当然,在 ./a.out 调用之后,我看到了垃圾输出。

因为您没有原型,所以编译器隐式声明了一个int类型为 for 的原型f()。但实际定义f()返回 a double。这两种类型不兼容,这是未定义的行为

即使在隐式 int 规则有效的 C89/C90 中,这也是未定义的,因为隐式原型与实际类型f()返回不兼容。所以这个例子是 (with a.cand b.c)在所有 C 标准中都是未定义的。

隐式函数声明不再有用或无效。因此,编译器/链接器如何处理的实际细节仅具有历史意义。它可以追溯到没有函数原型并且函数int默认返回的 K&R C 的标准前时代。在 C89/C90 标准中,函数原型被添加到 C 中。最重要的是,您必须拥有有效 C 程序中所有函数的原型(或在使用前定义函数)。

于 2016-01-04T19:19:00.557 回答
1

编译后,所有类型信息都会丢失(可能在调试信息中除外,但链接器不会注意这一点)。唯一剩下的是“在地址 0xdeadbeef 处有一个名为“f”的符号”。

headers 的目的是告诉 C 符号的类型,包括,对于函数,它需要什么参数以及它返回什么。如果您将真实的与您声明的(显式或隐式)不匹配,您将获得未定义的行为。

于 2016-01-04T19:15:21.300 回答