C 链接期间的外部功能?
我有一件奇怪的事: 在file1.c中有C 链接期间的外部功能?,c,linux,C,Linux,我有一件奇怪的事: 在file1.c中有 extern void foo(int x, int y); .. .. int tmp = foo(1,2); 在这个项目中,我只能找到这个foo(): 在文件2.c中: int foo(int x, int y, int z) { .... } 在文件2.h中: int foo(int x, int y, int z); file1.c中没有包含file2.h(我想这就是为什么编写它的人使用extern的原因) 这个项目编译得很好,我想这是因
extern void foo(int x, int y);
..
..
int tmp = foo(1,2);
在这个项目中,我只能找到这个foo():
在文件2.c中:
int foo(int x, int y, int z)
{
....
}
在文件2.h中:
int foo(int x, int y, int z);
file1.c中没有包含file2.h(我想这就是为什么编写它的人使用extern的原因)
这个项目编译得很好,我想这是因为在file1中,foo()只会在链接过程中被查找,对吗
但我真正的问题是:为什么这种联系是成功的?
毕竟,没有像foo这样有两个参数的函数。。。。
我在c。。所以没有过载
这是怎么回事?因为没有重载,C编译器不会修饰函数名。链接器在
file2.c
中找到对函数foo
的引用,并在file1.c
中找到函数foo
。它无法知道它们的参数列表不匹配并愉快地使用它们
当然,当函数foo
运行时,z
的值是垃圾,从那时起程序的行为变得不可预测。首先
extern void foo(int x, int y);
意思和
void foo(int x, int y);
前者只是写同样东西的一种过于明确的方式<代码>外部在这里没有其他用途。这就像写autointx
而不是intx
,它的意思完全相同
在您的例子中,“foo”模块(您称之为file2)包含函数原型和定义。这是C语言中正确的程序设计。file1.C应该做的是
#包括
foo.h
出于未知的原因,编写file1.c的人没有这样做。相反,他们只是说“在项目的其他地方,有这个函数,不关心它的定义,它在其他地方处理”
这是一种糟糕的编程实践。file1.c不应该关心其他地方是如何定义的:这是意大利面编程,它在调用方和模块之间创建了不必要的紧密耦合。还有一种可能是实际函数与本地原型不匹配,在这种情况下,您可能会得到链接器错误。但没有任何保证
代码必须按如下方式修复:
文件1.c
#include "foo.h"
...
int tmp = foo(1,2);
福安
富科
使用错误数量(或类型)的参数调用函数是错误的。 该标准要求实现检测一些,但不是全部 标准称之为实现的,通常是一个带有单独链接器(以及其他一些东西)的编译器,其中编译器将单个翻译单元(即预处理的源文件)转换为对象文件,这些文件随后链接在一起。 虽然这两个标准之间没有区别,但其作者在编写时当然考虑到了典型的设置 C11(n1570)6.5.2.2“函数调用”,p2: 如果表示被调用函数的表达式具有包含原型的类型,则参数的数量应与参数的数量一致。每个参数的类型应确保其值可分配给具有相应参数类型的非限定版本的对象 这是在“约束”部分,这意味着,如果违反了“应”要求,实现(在本例中,即编译器)必须进行投诉,并可能中止翻译 在您的例子中,有一个原型可见,因此函数调用的参数必须与原型匹配 类似的要求适用于范围内带有原型声明的功能定义;如果函数定义与原型不匹配,编译器必须告诉您。换句话说,只要您确保对函数的所有调用和该函数的定义都在同一个原型的范围内,就会被告知是否存在不匹配。如果原型位于头文件中,则可以确保这一点,该头文件包含在所有调用该函数的文件以及包含其定义的文件中。正是出于这个原因,我们使用带有原型的头文件 在所示的代码中,通过提供一个不匹配的原型而不包括头文件file2.h,该检查被旁路 同上。第9页: 如果函数定义的类型与表示被调用函数的表达式所指向的类型(表达式的类型)不兼容,则行为未定义 未定义的行为意味着,编译器可以自由地假设它没有发生,并且不需要检测它是否发生 事实上,在我的机器上,从file2.c生成的对象文件(我插入了一个
返回0;
以具有一些函数体),如果我删除其中一个函数参数,则没有区别,这意味着,目标文件不包含有关参数的任何信息,因此仅查看file2.o和file1.c的编译器没有机会检测到冲突
你提到过重载,所以我们将<强>文件FI2.2.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
将其参数合并到为其创建的符号中(一个称为名称混乱的过程),对象文件确实包含有关函数类型的一些信息。因此,我们在链接时得到一个错误:
$ 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
声明是多余的。但是它没有害处
$ 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
$ 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