简体   繁体   English

联动期间的外部功能?

[英]extern function during linkage?

I have this weird thing: 我有这个奇怪的事情:

in a file1.c there's 在file1.c中有

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

int tmp = foo(1,2);

in the project I could find only this foo(): 在项目中,我只能找到这个foo():

in file2.c : 在file2.c中:

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

in file2.h : 在file2.h中:

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

file2.h isn't included in file1.c (this is why who wrote it used extern, i guess). file2.h中没有包含file2.h(这就是为什么谁用extern写的,我猜)。

this project compiles fine, I think that's because in file1.c foo() will be looked for only during linkage, am I right? 这个项目编译得很好,我认为这是因为在file1.c中foo()只会在链接时查找,我是对的吗?

but my real question is : why is the linkage succssful ? 但我真正的问题是:为什么联系很紧张? after all, there is no such function as foo with 2 parameters.... and i'm in c .. so there's no overloading.. 毕竟,没有像foo这样的函数有2个参数....而且我在c ..所以没有超载..

so what's going on ? 发生什么了 ?

Because there is no overloading, the C compiler does not decorate the function names. 因为没有重载,C编译器不会修饰函数名称。 The linker finds in file2.c a reference to function foo and in file1.c it finds a function foo . 链接器在file2.c找到对函数foo的引用,在file1.c它找到一个函数foo It cannot know their parameter lists do not match and happily use them. 它无法知道它们的参数列表不匹配并乐于使用它们。

Of course, when the function foo runs the value of z is garbage and the behavior of the program becomes unpredictable from that point on. 当然,当函数foo运行时, z的值是垃圾,程序的行为从那时起变得不可预测。

Calling a function with the wrong number (or types) of arguments is an error. 调用具有错误数量(或类型)参数的函数是错误的。 The standard requires the implementation to detect some, but not all of them. 该标准要求实现检测一些但不是全部。

What the standard calls an implementation, is typically a compiler with a separate linker (and some other things), where a compiler translates single translation units (that is, a preprocessed source file) into object files, which later get linked together. 标准称为实现的内容通常是具有单独链接器(以及其他一些内容)的编译器,其中编译器将单个转换单元(即,预处理的源文件)转换为目标文件,稍后将其链接在一起。 While the standard doesn't distinct between them, its authors of course wrote it with the typical setup in mind. 虽然标准在它们之间没有区别,但它的作者当然是在考虑典型设置的情况下编写的。

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

If the expression that denotes the called function has a type that includes a prototype, the number of arguments shall agree with the number of parameters. 如果表示被调用函数的表达式具有包含原型的类型,则参数的数量应与参数的数量一致。 Each argument shall have a type such that its value may be assigned to an object with the unqualified version of the type of its corresponding parameter. 每个参数都应具有一个类型,使得其值可以分配给具有其相应参数类型的非限定版本的对象。

This is in a "constraints" section, which means, the implementation (in this case, that's the compiler) must complain and may abort translation if a "shall" requirement is violated. 这是在“约束”部分,这意味着,实施(在这种情况下,这是编译器)必须投诉,如果违反“应”要求,可能会中止翻译。

In your case, there was a prototype visible, so the arguments of the function call must match with the prototype . 在您的情况下,有一个原型可见,因此函数调用的参数必须与原型匹配。

Similar requirements apply for a function definition with a prototype declaration in scope; 类似的要求适用于范围内具有原型声明的函数定义; if your function definition doesn't match the prototype, your compiler must tell you. 如果你的函数定义与原型不匹配,你的编译器必须告诉你。 In other words, as long as you ensure that all calls to a function and that function's definition are in the scope of the same prototype, you are told if there is a mismatch. 换句话说,只要您确保对函数的所有调用和该函数的定义都在同一原型的范围内,就会告诉您是否存在不匹配。 This can be ensured if the prototype is in a header file which is included by all files with calls to that function and by the file containing its definition. 如果原型位于头文件中,这可以通过调用该函数的所有文件以及包含其定义的文件包含在内。 We use header files with prototypes exactly for that reason. 出于这个原因,我们使用带有原型的头文件。

In the code shown, this checking is by-passed by providing a non-matching prototype and not including the header file2.h . 在所示的代码中,通过提供不匹配的原型并且不包括头文件2.h来绕过检查

Ibid. 同上。 p9: P9:

If the function is defined with a type that is not compatible with the type (of the expression) pointed to by the expression that denotes the called function, the behavior is undefined. 如果函数定义的类型与表示被调用函数的表达式指向的类型(表达式)不兼容,则行为未定义。

Undefined behaviour means, the compiler is free to assume it doesn't happen, and is not required to detect if it does. 未定义的行为意味着,编译器可以自由地假设它不会发生,并且不需要检测它是否存在。

And in fact, on my machine, the generated object files from file2.c (I inserted a return 0; to have some function body), don't differ if I remove one of the function arguments, which means, the object file doesn't contain any information about the arguments und thus a compiler seeing only file2.o and file1.c hasn't got any chance to detect the violation. 事实上,在我的机器上,从file2.c生成的目标文件(我插入一个return 0;有一些函数体),如果我删除其中一个函数参数,则没有区别,这意味着,目标文件没有不包含有关参数的任何信息,因此编译器只看到file2.ofile1.c没有机会检测到违规。

You've mentioned overloading, so let's compile file2.c (with two and three arguments) as C++ and look at the object files: 你已经提到过载了,所以让我们编译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

Function foo has its arguments incorporated into the symbol created for it (a process called name mangling), the object file indeed carries some information about the function types. 函数foo将其参数合并到为其创建的符号中(名为mangling的过程),目标文件确实包含有关函数类型的一些信息。 Accordingly, we get an error at link time: 因此,我们在链接时收到错误:

$ 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

This behaviour would also be allowed for a C implementation, aborting translation is a valid manifestation of undefined behaviour at compile or link time. C实现也允许这种行为,中止转换是编译或链接时未定义行为的有效表现。

The way overloading in C++ is usually done actually allows such checks at link time. C ++中的重载方式通常实际上允许在链接时进行此类检查。 That C doesn't have built-in support for function overloading, and that the behaviour is undefined for the cases where the compiler cannot see the type mismatches, allows to generate symbols for functions without any type information. C没有内置的函数重载支持,并且对于编译器无法看到类型不匹配的情况,行为未定义,允许为没有任何类型信息的函数生成符号。

First of all 首先

extern void foo(int x, int y);

means exactly the same thing as 意思是完全一样的

void foo(int x, int y);

The former is just an overly explicit way to write the same thing. 前者只是一种过于明确的方式来编写同样的东西。 extern fills no other purpose here. extern在这里没有其他目的。 It is like writing auto int x; 这就像写auto int x; instead of int x , it means the very same thing. 而不是int x ,它意味着完全相同的东西。


In your case, the "foo" module (which you call file2) contains the function prototype as well as the definition. 在您的情况下,“foo”模块(您称之为file2)包含函数原型和定义。 This is proper program design in C. What file1.c should be doing is to #include the foo.h. 这是C中正确的程序设计.file1.c应该做的是#include foo.h.

For reasons unknown, whoever wrote file1.c didn't do this. 由于未知原因,编写file1.c的人没有这样做。 Instead they are just saying "elsewhere in the project, there is this function, do not care about its definition, that's handled elsewhere". 相反,他们只是说“在项目的其他地方,有这个功能,不关心它的定义,那是在其他地方处理的”。

This is bad programming practice. 这是糟糕的编程习惯。 file1.c shouldn't concern itself with how things are defined elsewhere: this is spaghetti programming which creates a needless tight coupling between the caller and the module. file1.c不应该关注其他地方的事情:这是意大利面条编程 ,它会在调用者和模块之间产生不必要的紧密耦合。 There is also the chance that the actual function doesn't match the local prototype, in which case you would hopefully get linker errors. 实际函数也有可能与本地原型不匹配,在这种情况下,您可能会遇到链接器错误。 But there are no guarantees. 但是没有保证。

The code must be fixed like this: 代码必须像这样修复:

file1.c 在file1.c

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

foo.h foo.h中

#ifndef FOO_H
#define FOO_H

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

#endif

foo.c 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