繁体   English   中英

联动期间的外部功能?

[英]extern function during linkage?

我有这个奇怪的事情:

在file1.c中有

extern void foo(int x, int y);
..
..

int tmp = foo(1,2);

在项目中,我只能找到这个foo():

在file2.c中:

int foo(int x, int y, int z)
{
....
}

在file2.h中:

int foo(int x, int y, int z);

file2.h中没有包含file2.h(这就是为什么谁用extern写的,我猜)。

这个项目编译得很好,我认为这是因为在file1.c中foo()只会在链接时查找,我是对的吗?

但我真正的问题是:为什么联系很紧张? 毕竟,没有像foo这样的函数有2个参数....而且我在c ..所以没有超载..

发生什么了 ?

因为没有重载,C编译器不会修饰函数名称。 链接器在file2.c找到对函数foo的引用,在file1.c它找到一个函数foo 它无法知道它们的参数列表不匹配并乐于使用它们。

当然,当函数foo运行时, z的值是垃圾,程序的行为从那时起变得不可预测。

调用具有错误数量(或类型)参数的函数是错误的。 该标准要求实现检测一些但不是全部。

标准称为实现的内容通常是具有单独链接器(以及其他一些内容)的编译器,其中编译器将单个转换单元(即,预处理的源文件)转换为目标文件,稍后将其链接在一起。 虽然标准在它们之间没有区别,但它的作者当然是在考虑典型设置的情况下编写的。

C11(n1570)6.5.2.2“函数调用”,p2:

如果表示被调用函数的表达式具有包含原型的类型,则参数的数量应与参数的数量一致。 每个参数都应具有一个类型,使得其值可以分配给具有其相应参数类型的非限定版本的对象。

这是在“约束”部分,这意味着,实施(在这种情况下,这是编译器)必须投诉,如果违反“应”要求,可能会中止翻译。

在您的情况下,有一个原型可见,因此函数调用的参数必须与原型匹配。

类似的要求适用于范围内具有原型声明的函数定义; 如果你的函数定义与原型不匹配,你的编译器必须告诉你。 换句话说,只要您确保对函数的所有调用和该函数的定义都在同一原型的范围内,就会告诉您是否存在不匹配。 如果原型位于头文件中,这可以通过调用该函数的所有文件以及包含其定义的文件包含在内。 出于这个原因,我们使用带有原型的头文件。

在所示的代码中,通过提供不匹配的原型并且不包括头文件2.h来绕过检查

同上。 P9:

如果函数定义的类型与表示被调用函数的表达式指向的类型(表达式)不兼容,则行为未定义。

未定义的行为意味着,编译器可以自由地假设它不会发生,并且不需要检测它是否存在。

事实上,在我的机器上,从file2.c生成的目标文件(我插入一个return 0;有一些函数体),如果我删除其中一个函数参数,则没有区别,这意味着,目标文件没有不包含有关参数的任何信息,因此编译器只看到file2.ofile1.c没有机会检测到违规。

你已经提到过载了,所以让我们编译file2.c (带有两个和三个参数)作为C ++并查看目标文件:

$ g++ file2_three_args.cpp -c
$ g++ file2_two_args.cpp -c
$ nm file2_three_args.o   
00000000 T _Z3fooiii
$ nm file2_two_args.o 
00000000 T _Z3fooii

函数foo将其参数合并到为其创建的符号中(名为mangling的过程),目标文件确实包含有关函数类型的一些信息。 因此,我们在链接时收到错误:

$ cat file1.cpp
extern void foo(int x, int y);

int main(void) {
        foo(1,2);
}
$ g++ file2_three_args.o file1.cpp
In function `main':
file1.cpp:(.text+0x19): undefined reference to `foo(int, int)'
collect2: error: ld returned 1 exit status

C实现也允许这种行为,中止转换是编译或链接时未定义行为的有效表现。

C ++中的重载方式通常实际上允许在链接时进行此类检查。 C没有内置的函数重载支持,并且对于编译器无法看到类型不匹配的情况,行为未定义,允许为没有任何类型信息的函数生成符号。

首先

extern void foo(int x, int y);

意思是完全一样的

void foo(int x, int y);

前者只是一种过于明确的方式来编写同样的东西。 extern在这里没有其他目的。 这就像写auto int x; 而不是int x ,它意味着完全相同的东西。


在您的情况下,“foo”模块(您称之为file2)包含函数原型和定义。 这是C中正确的程序设计.file1.c应该做的是#include foo.h.

由于未知原因,编写file1.c的人没有这样做。 相反,他们只是说“在项目的其他地方,有这个功能,不关心它的定义,那是在其他地方处理的”。

这是糟糕的编程习惯。 file1.c不应该关注其他地方的事情:这是意大利面条编程 ,它会在调用者和模块之间产生不必要的紧密耦合。 实际函数也有可能与本地原型不匹配,在这种情况下,您可能会遇到链接器错误。 但是没有保证。

代码必须像这样修复:

在file1.c

#include "foo.h"
...
int tmp = foo(1,2);

foo.h中

#ifndef FOO_H
#define FOO_H

int foo(int x, int y, int z);

#endif

foo.c的

#include "foo.h"

int foo(int x, int y, int z)
{
....
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM