c语言 隐式声明,关于C#:隐式函数声明和链接

最近,我了解了C语言中的隐式函数声明。主要思想很明确,但在这种情况下,我对理解链接过程有些麻烦。

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

#include

int main() {

double someValue = f();

printf("%f

", 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上读了许多这样的话题,但仍然有问题。

首先,由于C99,从标准中删除了函数的隐式声明。编译器可能支持此功能以编译旧代码,但这不是强制性的。引用标准序言,

remove implicit function declaration

也就是说,根据C11第6.5.2.2节

If the function is defined with a type that does not include a prototype, and the types of

the arguments after promotion are not compatible with those of the parameters after

promotion, the behavior is undefined.

所以,就您而言,

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

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

仅添加一点参考,如果您在调用后尝试在同一编译单元中定义函数,由于签名不匹配,您将收到编译错误。

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

感谢您的详细回答。

这是正在发生的事情。

如果没有f()的声明,则编译器会采用像int f(void)这样的隐式声明。然后愉快地编译a.c。

编译b.c时,编译器没有f()的任何先前声明,因此可以从f()的定义中直观地看出来。通常,您会在头文件中放置f()的声明,并将其同时包含在a.c和b.c中。因为两个文件将看到相同的声明,所以编译器可以强制执行一致性。它将抱怨与声明不匹配的实体。但是在这种情况下,没有通用的原型可以参考。

在C中,编译器不会在目标文件中存储有关原型的任何信息,并且链接程序不会执行任何一致性检查(不能)。它所看到的只是a.c中未解析的符号f和b.c中定义的符号f。它很高兴地解析符号,并完成了链接。

但是,事情在运行时会失败,因为编译器会根据在此假设的原型在a.c中设置调用。哪个与b.c中的定义不匹配。 f()(来自b.c)将从堆栈中获取一个垃圾参数,并将其返回为double,在a.c中返回时将被解释为int。

我认为编译器在处理隐式声明时采用int f()而不是int f(void)。

病态检查,但考虑到a.c中的调用没有参数,因此假设int f(void)是有意义的,因为int f()表示any number of arguments。

隐式声明在现代C语言中无效,并且是UB。不像int f(void);那样提供隐式的。它仅提供类似int f();的声明,编译器在编译b.c时不会" intuit",并且不需要事先定义原型。函数定义也提供其原型。如果以前提供过原型,则对其进行检查。否则,不会。"通用原型"不是一回事。原型是否可用。与编译器如何获取该信息无关(通过头文件或实际定义等)。

虽然(3)和(4)很好,但在标准中将其简单定义为未定义的行为。

@ I3x所有这些都是问题的外围部分,并且大多是在挑剔我表达某些观点的方式。但是无所谓。

How are programmes with implicitly declared functions are linked? And what happens in my example under the hood of compiler/linker?

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

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

Surprisingly everything is linked successfully. Of course after

./a.out invocation I see a rubbish output.

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

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

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

这还不完整。这个问题与隐式声明无关,但是由于链接器对类型一无所知,他找到了一个名为f的函数的定义,该函数适用于链接。为了避免这种问题,某些语言使用名称修饰将代码类型编码到函数名称中。但是在C语言中不是这样。拥有原型是不够的,您需要一致的原型!

@ Jean-BaptisteYuns我已经解释过,隐式原型的类型与实际类型不兼容,因此UB被调用。没有"一致原型"之类的东西。原型是有关函数返回类型及其参数的完整信息。

@ i3x,但最大的问题(我认为)是链接为什么完成,以及解释链接过程的详细信息。

@ Jean-BaptisteYuns是的,主要的误解是关于这种情况下的链接问题。因此,我阅读了Sourav和l3x的答案。所以他们有点混在我的头上。现在,Souravs的答案似乎更加完整。

@Ziffusion我是说它的UB,并且由于C.Btw的古老规则而被编译和链接,我只是读了您的答案,它并不完全准确。

@ i3x我想你错过了重点。主要原因是:原型不一致(隐式声明边界效应)和无类型链接。两者都与"古代C规则"无关。

编译后,所有类型信息都将丢失(调试信息中可能除外,但链接程序不会对此进行注意)。唯一剩下的就是"在地址0xdeadbeef处有一个名为" f"的符号"。

标头的目的是告诉C符号的类型,包括对于函数而言,它采用什么参数以及它返回什么。如果将实际值与声明的值(显式或隐式)不匹配,则会得到未定义的行为。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值