我有这个奇怪的事情:
在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并没有包含在file1.c中(这就是为什么谁用它写extern,我猜)。
这个项目编译得很好,我认为这是因为在file1.c中foo()只会在链接期间查找,我是对的吗?
但我真正的问题是:为什么联系很紧张? 毕竟,没有像foo这样的功能有2个参数.... 而且我在c ..所以没有超载..
那是怎么回事?
答案 0 :(得分:7)
因为没有重载,所以C编译器不会修饰函数名。链接器在file2.c
中找到对函数foo
的引用,在file1.c
中找到函数foo
。它无法知道它们的参数列表不匹配并乐于使用它们。
当然,当函数foo
运行时,z
的值是垃圾,程序的行为从那时起变得不可预测。
答案 1 :(得分:1)
调用具有错误数量(或类型)参数的函数是错误的。 该标准要求实现检测一些,但不是全部。
标准调用实现的内容通常是具有单独链接器(以及其他一些内容)的编译器,其中编译器将单个转换单元(即,预处理的源文件)转换为目标文件,稍后将其链接在一起。 虽然标准在它们之间没有区别,但它的作者当然是在考虑典型设置时写的。
C11(n1570)6.5.2.2“函数调用”,p2:
如果表示被调用函数的表达式具有包含原型的类型,则参数数量应与参数数量一致。每个参数都应具有一个类型,使其值可以分配给具有相应参数类型的非限定版本的对象。
这是在“约束”部分,这意味着,实施(在这种情况下,即编译器)必须投诉,如果违反“必须”要求,可能会中止翻译。
在您的情况下,有一个原型可见,因此函数调用的参数必须与匹配原型。
类似的要求适用于范围内具有原型声明的函数定义;如果你的函数定义与原型不匹配,你的编译器必须告诉你。换句话说,只要您确保对函数的所有调用和该函数的定义都在同一原型的范围内,就会告诉您是否存在不匹配。如果原型位于头文件中,这可以通过调用该函数的所有文件以及包含其定义的文件包含在内。我们正是因为这个原因而使用带有原型的头文件。
在显示的代码中,通过提供不匹配的原型并且不包括标题 file2.h 来绕过此检查。
同上。 P9:
如果使用与表示被调用函数的表达式指向的类型(表达式)类型不兼容的类型定义函数,则行为未定义。
未定义的行为意味着,编译器可以自由地假设它不会发生,并且不需要检测它是否会发生。
事实上,在我的机器上,生成的目标文件来自 file2.c (我插入了一个return 0;
来拥有一些功能体),如果我删除一个没有区别函数参数,这意味着,目标文件不包含任何有关参数的信息,因此只有 file2.o 和 file1.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没有对函数重载的内置支持,并且对于编译器无法看到类型不匹配的情况,行为未定义,允许为没有任何类型信息的函数生成符号。
答案 2 :(得分:0)
首先
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不应该关注其他地方的事情:这是 spaghetti编程在呼叫者和模块之间产生不必要的紧密耦合。实际函数也有可能与本地原型不匹配,在这种情况下,您可能会遇到链接器错误。但是没有保证。
代码必须像这样修复:
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)
{
....
}