2016-01-04 27 views
3

最近我已经了解到隐式函数声明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调用后,我看到垃圾输出。

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

我读了很多关于SO的话题,如this,thisthis one,但仍然有问题。

回答

5

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

  • 删除隐函数声明

这就是说,按照C11,章§6.5.2。2

如果功能与不包括一个原型,和类型的 推广后的参数是不与后 促进那些参数兼容的类型定义的,该行为是未定义的。

所以,你的情况,

  • 函数调用本身是隐含的声明(这成为自C99非标),

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

只需添加更多的参考,如果你尝试后调用定义在相同编译单元的功能,你会得到一个编译错误,由于不匹配的签名。但是,您的函数在单独的编译单元中定义(并且缺少原型声明),编译器无法检查签名。编译之后,链接器接受目标文件,并且由于链接器中没有任何类型检查(并且目标文件中没有任何信息),可以愉快地链接它们。最后,它将以成功汇编和链接 UB。

+1

感谢详细的解答。 –

0

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

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

1

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

隐式int规则自C99以来已被C标准取缔。所以它不是有效的有隐式函数声明的程序。

从C99开始无效。在此之前,如果一个可见的原型不可用,那么编译器隐式声明一个返回类型为int

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

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

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

这不是有用或无效了有隐函数的声明。所以编译器/链接器如何处理的实际细节只是具有历史意义。它回到K & R C的预标准时间,它没有函数原型,函数默认返回int。 C89/C90标准中将函数原型添加到C中。底线,你必须必须具有有效的C程序中的所有功能原型(或使用前定义功能)。

+0

很好的解释,谢谢。 –

+0

这不完整。这个问题与隐式声明无关,但是由于链接器对类型一无所知,他发现了一个名为'f'的函数的定义,并且适用于链接。为了防止这样的问题,一些语言使用名称修改来将类型编码到函数的名称中;但是在C中并非如此。原型并不足够,您需要一致的原型! –

+0

@ Jean-BaptisteYunès我已经解释过隐式原型的类型和实际是不兼容的,UB是作为结果调用的。没有“一致原型”这样的东西。原型是关于函数返回类型及其参数的完整信息。 –

2

这是发生了什么事。

  1. 没有为f()声明,编译器假定像int f(void)一个隐含的声明。然后愉快地编译a.c
  2. 在编译b.c时,编译器没有任何关于f()的预先声明,所以它从f()的定义中直接得出结论。通常你会在头文件中放一些f()的声明,并将它包含在a.cb.c中。因为这两个文件都会看到相同的声明,编译器可以强制执行一致性。它会抱怨与声明不符的实体。但在这种情况下,没有共同的原型可以参考。
  3. C,编译器不存储有关原型在对象文件中的任何信息,并且链接器不执行一致性任何检查(它不能)。所有它认为是一个未解决的符号fa.c和符号fb.c定义。它高兴地解决了符号,并完成了链接。
  4. 事情在运行时分解,但因为编译器建立在此基础上假设有原型在a.c呼叫。这与b.c寻找的定义不符。 f()(从b.c)会得到一个垃圾论证出栈,并返回为double,这将在a.c被解释为int上的回报。
+0

我认为在处理隐式声明时,编译器假定'int f()'而不是'int f(void)'。 –

+0

我会检查,但鉴于'a.c'中的调用没有参数,假设'int f(void)'是有意义的,因为'int f()'意味着任意数量的参数。 – Ziffusion

+1

隐式声明在现代C中是无效的,并且是UB。隐式的不提供像'int f(void);'。它只提供像int f()这样的声明;编译'b.c'时编译器不会“intuit”,并且它不需要定义前面定义的原型。函数定义也提供了它的原型。如果之前提供了原型,则会对其进行检查。否则,不。 “共同的原型”不是一回事。原型可用或不可用。编译器如何获取这些信息(通过头文件或实际定义等)是无关紧要的。 –