隐式函数声明和链接

时间:2016-01-04 19:06:56

标签: c linkage function-declaration implicit-declaration

最近我在 C 中了解了隐式函数声明。主要观点很明确,但在这种情况下我对理解联系过程有一些麻烦。

考虑以下代码(文件 a.c ):

#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

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

然后我添加另一个文件(文件 b.c ):

double f(double x) {
    return x;
}

然后调用下一个命令:

gcc a.c b.c -std=c99

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

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

我在thisthisthis one上阅读了很多关于搜索引擎优化的主题,但仍有问题。

4 个答案:

答案 0 :(得分:5)

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

  
      
  • 删除隐式函数声明
  •   

据说C11,章节§6.5.2.2

  

如果使用不包含原型的类型定义函数,并且类型为   促销后的参数与之后的参数不兼容   促销,行为未定义。

所以,在你的情况下,

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

  • 由于函数签名不匹配[假定某个函数的隐式声明具有int返回类型],您的代码会调用undefined behavior

只是为了添加更多的引用,如果你试图在调用之后在相同的编译单元中定义函数,你会得到一个编译错误,因为不匹配的签名。

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

答案 1 :(得分:2)

以下是发生的事情。

  1. 如果没有f()的声明,编译器会假定像int f(void)这样的隐式声明。然后愉快地编译a.c
  2. 编译b.c时,编译器没有f()的任何先前声明,因此它可以从f()的定义中看出来。通常,您会在头文件中添加一些f()声明,并将其包含在a.cb.c中。因为两个文件都会看到相同的声明,所以编译器可以强制执行一致性。它会抱怨与声明不符的实体。但在这种情况下,没有共同的原型可供参考。
  3. C中,编译器不会在目标文件中存储有关原型的任何信息,并且链接器不会执行任何一致性检查(它不能)。它看到的只是f中未解析的符号a.cf中定义的符号b.c。它很乐意解析符号,并完成链接。
  4. 事情在运行时发生故障,因为编译器根据它在那里假设的原型在a.c中设置调用。这与b.c中的定义不匹配。 f()(来自b.c)将从堆栈中获取垃圾参数,并将其作为double返回,在int返回时将被解释为a.c

答案 2 :(得分:1)

  

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

自C99起,隐式int 规则已被C标准取缔。因此,具有隐式函数声明的程序不是有效

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

  

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

由于您没有原型,编译器会隐式声明int类型为f()的类型。但f()的实际定义会返回double。这两种类型不兼容,这是undefined behaviour

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

具有隐式函数声明不再有用或有效。因此,编译器/链接器处理方式的实际细节仅具有历史意义。它可以追溯到K&amp; R C的预标准时间,它没有函数原型,默认情况下函数返回int。在C89 / C90标准中将功能原型添加到C中。最重要的是,必须为有效C程序中的所有函数提供原型(或在使用前定义函数)。

答案 3 :(得分:0)

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

标题的要点是告诉C关于符号的类型,包括对于函数,它需要什么参数以及它返回什么。如果你将真实的那些与你声明的那些(显式或隐式)不匹配,则会得到未定义的行为。