隐式函数声明和链接
Implicit function declarations and linkage
最近我了解了 C 中的隐式函数声明。主要思想很清楚,但我在理解这种情况下的 linkage 过程时遇到了一些麻烦。
考虑以下代码(文件a.c):
#include <stdio.h>
int main() {
double someValue = f();
printf("%f\n", someValue);
return 0;
}
如果我尝试编译它:
gcc -c a.c -std=c99
我看到关于函数隐式声明的警告 f()
。
如果我尝试编译 link:
gcc a.c -std=c99
我有一个未定义的引用错误。所以一切都很好。
然后我添加另一个文件(文件b.c):
double f(double x) {
return x;
}
并调用下一个命令:
gcc a.c b.c -std=c99
令人惊讶的是,一切都 link 成功了。当然,在 ./a.out 调用之后,我看到了垃圾输出。
所以,我的问题是:具有隐式声明函数的程序如何 linked?在 compiler/linker 的幕后,我的示例中发生了什么?
我阅读了很多关于 SO 的主题,例如 this, this and this one,但仍然有问题。
编译后,所有类型信息都丢失了(调试信息中可能除外,但链接器不会注意它)。唯一剩下的是地址 0xdeadbeef 处的 "there is a symbol called "f"。
headers 的要点是告诉 C 符号的类型,包括对于函数,它需要什么参数以及它是什么 returns。如果你将真实的与你声明的不匹配(无论是显式还是隐式),你会得到未定义的行为。
首先,自 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开始变成non-standard),
并且由于函数签名不匹配 [假定函数的隐式声明具有 int
return 类型],您的代码调用 undefined behavior.
只是为了增加一点参考,如果你试图在 same 编译单元 after 调用中定义函数,你由于签名不匹配,将会出现编译错误。
但是,您的函数是在单独的编译单元中定义的(并且缺少原型声明),编译器无法检查签名。编译后,链接器获取目标文件,并且由于链接器中没有任何 type-checking(目标文件中也没有信息),很高兴链接它们。最后,它将以 成功 编译和链接 and UB 告终。
How are programmes with implicitly declared functions are linked? And what happens in my example under the hood of compiler/linker?
自 C99 以来,implicit int 规则已被 C 标准禁止。因此,具有隐式函数声明的程序是无效的。
自 C99 起无效。在此之前,如果可见原型不可用,则编译器隐式声明一个 int
return 类型。
Surprisingly everything is linked successfully. Of course after
./a.out invocation I see a rubbish output.
因为您没有原型,编译器为 f()
隐式声明了一个具有 int
类型的原型。但是f()
的实际定义是return一个double
。这两种类型不兼容,这是 undefined behaviour.
即使在 C89/C90 中也是未定义的,其中隐式 int 规则是有效的,因为隐式原型与实际类型 f()
return 不兼容。所以这个例子是(a.c
和 b.c
)在所有 C 标准中是 undefined。
隐式函数声明不再有用或无效。因此,compiler/linker 如何处理的实际细节仅具有历史意义。它可以追溯到 K&R C 的 pre-standard 时代,它没有函数原型和默认函数 return int
。在 C89/C90 标准中,函数原型被添加到 C 中。底线是,您必须为有效 C 程序中的所有函数提供原型(或在使用前定义函数)。
这是正在发生的事情。
- 如果没有
f()
的声明,编译器会假设一个像 int f(void)
这样的隐式声明。然后愉快地编译a.c
.
- 在编译
b.c
时,编译器没有对f()
的任何预先声明,所以它从f()
的定义中直觉。通常你会在头文件中放置一些 f()
的声明,并将其包含在 a.c
和 b.c
中。因为这两个文件将看到相同的声明,所以编译器可以强制一致性。它将抱怨与声明不匹配的实体。但是在这种情况下,没有共同的原型可以参考。
- 在
C
中,编译器不在对象文件中存储任何关于原型的信息,并且linker 不执行任何一致性检查(它不能)。它所看到的只是 a.c
中未解析的符号 f
和 b.c
中定义的符号 f
。它愉快地解决了符号,并完成了 link.
- 尽管如此,事情在 运行 时发生了故障,因为编译器根据它在那里假定的原型在
a.c
中设置了调用。这与 b.c
中的定义不匹配。 f()
(来自 b.c
)将从堆栈中获取一个垃圾参数,return 它作为 double
,将在 [=44] 上被解释为 int
=] 在 a.c
.
最近我了解了 C 中的隐式函数声明。主要思想很清楚,但我在理解这种情况下的 linkage 过程时遇到了一些麻烦。
考虑以下代码(文件a.c):
#include <stdio.h>
int main() {
double someValue = f();
printf("%f\n", someValue);
return 0;
}
如果我尝试编译它:
gcc -c a.c -std=c99
我看到关于函数隐式声明的警告 f()
。
如果我尝试编译 link:
gcc a.c -std=c99
我有一个未定义的引用错误。所以一切都很好。
然后我添加另一个文件(文件b.c):
double f(double x) {
return x;
}
并调用下一个命令:
gcc a.c b.c -std=c99
令人惊讶的是,一切都 link 成功了。当然,在 ./a.out 调用之后,我看到了垃圾输出。
所以,我的问题是:具有隐式声明函数的程序如何 linked?在 compiler/linker 的幕后,我的示例中发生了什么?
我阅读了很多关于 SO 的主题,例如 this, this and this one,但仍然有问题。
编译后,所有类型信息都丢失了(调试信息中可能除外,但链接器不会注意它)。唯一剩下的是地址 0xdeadbeef 处的 "there is a symbol called "f"。
headers 的要点是告诉 C 符号的类型,包括对于函数,它需要什么参数以及它是什么 returns。如果你将真实的与你声明的不匹配(无论是显式还是隐式),你会得到未定义的行为。
首先,自 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开始变成non-standard),
并且由于函数签名不匹配 [假定函数的隐式声明具有
int
return 类型],您的代码调用 undefined behavior.
只是为了增加一点参考,如果你试图在 same 编译单元 after 调用中定义函数,你由于签名不匹配,将会出现编译错误。
但是,您的函数是在单独的编译单元中定义的(并且缺少原型声明),编译器无法检查签名。编译后,链接器获取目标文件,并且由于链接器中没有任何 type-checking(目标文件中也没有信息),很高兴链接它们。最后,它将以 成功 编译和链接 and UB 告终。
How are programmes with implicitly declared functions are linked? And what happens in my example under the hood of compiler/linker?
自 C99 以来,implicit int 规则已被 C 标准禁止。因此,具有隐式函数声明的程序是无效的。
自 C99 起无效。在此之前,如果可见原型不可用,则编译器隐式声明一个 int
return 类型。
Surprisingly everything is linked successfully. Of course after ./a.out invocation I see a rubbish output.
因为您没有原型,编译器为 f()
隐式声明了一个具有 int
类型的原型。但是f()
的实际定义是return一个double
。这两种类型不兼容,这是 undefined behaviour.
即使在 C89/C90 中也是未定义的,其中隐式 int 规则是有效的,因为隐式原型与实际类型 f()
return 不兼容。所以这个例子是(a.c
和 b.c
)在所有 C 标准中是 undefined。
隐式函数声明不再有用或无效。因此,compiler/linker 如何处理的实际细节仅具有历史意义。它可以追溯到 K&R C 的 pre-standard 时代,它没有函数原型和默认函数 return int
。在 C89/C90 标准中,函数原型被添加到 C 中。底线是,您必须为有效 C 程序中的所有函数提供原型(或在使用前定义函数)。
这是正在发生的事情。
- 如果没有
f()
的声明,编译器会假设一个像int f(void)
这样的隐式声明。然后愉快地编译a.c
. - 在编译
b.c
时,编译器没有对f()
的任何预先声明,所以它从f()
的定义中直觉。通常你会在头文件中放置一些f()
的声明,并将其包含在a.c
和b.c
中。因为这两个文件将看到相同的声明,所以编译器可以强制一致性。它将抱怨与声明不匹配的实体。但是在这种情况下,没有共同的原型可以参考。 - 在
C
中,编译器不在对象文件中存储任何关于原型的信息,并且linker 不执行任何一致性检查(它不能)。它所看到的只是a.c
中未解析的符号f
和b.c
中定义的符号f
。它愉快地解决了符号,并完成了 link. - 尽管如此,事情在 运行 时发生了故障,因为编译器根据它在那里假定的原型在
a.c
中设置了调用。这与b.c
中的定义不匹配。f()
(来自b.c
)将从堆栈中获取一个垃圾参数,return 它作为double
,将在 [=44] 上被解释为int
=] 在a.c
.