1.c
:
int main(){ return 0;}
2.c
:
int main(){ return 0;}
发出命令
gcc 1.c 2.c
将会导致一个重复符号
的链接错误。为什么在结构体和类中不会发生同样的情况呢?为什么我们可以拥有多个相同标记的相同结构体定义?1.c
:
int main(){ return 0;}
2.c
:
int main(){ return 0;}
gcc 1.c 2.c
将会导致一个重复符号
的链接错误。为什么在结构体和类中不会发生同样的情况呢?为什么我们可以拥有多个相同标记的相同结构体定义?#
开头的所有内容,这里并不重要。.c
或 .cpp
文件以及它包含的标头)。编译器逐个读取每个翻译单元,生成类及其成员的内部列表,然后根据结构列表生成每个函数的汇编代码。如果函数调用没有被内联(例如,在不同的 TU 中定义),编译器会为链接器生成一个“链接”-“请在此处插入函数 X”,以供链接器读取。总之:
一个定义规则的存在是为了保护程序员免受自己的伤害。如果他们意外地定义了一个函数两次,链接器将会注意到并且不会生成可执行文件。
然而,类定义在每个翻译单元中都是必需的,因此不能为它们设置这样的规则。由于语言无法强制执行,程序员必须是负责任的人,并且不要以不同的方式定义相同的类。
ODR还有其他限制,例如,您 必须在头文件中定义模板函数(或模板类方法)。您也可以承担责任并告诉编译器“这个函数的每个定义都是相同的,请相信我”,并使该函数成为inline
。
class myclass;
。然后后续的函数可以具有对该类型的引用或指针,但不能具有该类型的值,也不能访问任何其成员/方法。通常情况下这并没有什么用处。 - Mooing Duckclass myclass;
。然后后续的函数可以有该类型的引用或指针,但不能有该类型的值,也不能访问其成员/方法。这通常是没有用的。 - undefined一个函数有两个定义是没有用处的。要么这两个定义是相同的,使它变得无用,要么编译器无法确定你想要哪个。
但这不适用于类或结构。允许它们有多个定义也有很大的优点,例如如果我们想在多个文件中使用一个 class
或 struct
。(由于包含文件的原因)这最终会间接导致多个定义。
实际上,每个编程元素都与其适用范围相关联。在此范围内,您不能将同一名称与元素的多个定义相关联。在编译世界中:
C/C++编译非常注重编译性能。检查两个对象(如函数或类)是否相同是一项耗时的任务。因此,不会这样做。仅考虑名称进行比较。最好认为2种类型是不同的并出错,而不是检查它们是否相同。唯一的例外是文本宏。
宏是预处理器的概念,从历史上看,允许具有多个相同的宏定义。如果定义更改,则会生成警告。比较宏上下文很容易,只需进行简单的字符串比较,但某些宏定义可能非常大。
类型是编译器的概念,由编译器解析。类型在对象库中不存在,由相应变量的大小表示。因此,在此范围内检查类型名称冲突没有意义。
另一方面,函数和变量是指向可执行代码或数据的命名指针。它们是应用程序的构建块。应用程序通常从世界各地的代码和库组装而成。为了使用别人的函数,最好知道它的名称,并且不希望其他人使用相同的名称。在共享库中,函数和变量的名称通常存储在哈希表中。那里没有重复的地方。
正如我已经提到的,很少检查函数是否具有相同的内容,但有一些情况,但不适用于C或C++。
在编程中阻止使用相同事物的两个不同定义的原因是为了避免在运行时决定使用哪个定义而产生歧义。
如果您有两个不同的实现来共存于一个程序中,则存在将它们别名化(每个都用不同的名称)成一个公共引用的可能性,以便在运行时决定使用其中之一。
无论如何,为了区分两者,您必须能够告诉编译器您想要使用哪一个。在C++中,您可以重载函数,给它相同的名称和不同的参数列表,以便您可以区分您想要使用的两个函数。但是在C中,编译器只保留函数的名称,以便能够在链接时解决哪个定义与您在不同编译单元中使用的名称匹配。如果链接器最终得到具有相同名称的两个不同定义,则无法为您决定使用哪一个,因此会发出错误并放弃构建过程。
如何以有效的方式使用这种歧义?这实际上是您必须问自己的问题。
结构体、类、联合和枚举定义了可以在多个编译单元中使用的类型,以定义这些类型的对象。因此,每个编译单元都需要知道如何定义这些类型,例如为对象正确分配内存或确保类的指定成员确实存在。
对于函数(如果它们不是内联函数),只需要具有其声明而不是定义即可生成例如函数调用。
但是,函数定义应该是唯一的。否则,编译器将不知道调用哪个函数,或者由于重复而使目标代码过大,并且容易出现错误。
struct Foo
的定义和哪个f
的定义:
1.c
:
struct Foo { int x; };
static void f(void) { struct Foo foo; ... }
2.c
:
struct Foo { double d; };
static void f(void) { struct Foo foo; ... }
int main(void) { ... }
但是将以下内容链接在一起是无效的,因为链接器不知道要调用哪个f
。
1.c
:
void f(void) { ... }
2.c
:
void f(void) { ... }
int main(void) { f(); }
#include
指令会读取一个文件并直接复制整个内容到该指令的位置。通过使用头文件,你可以在每个包含(include
)此头文件的文件中获得该结构体的定义副本。 - Yksisarvinenclass
/struct
之后以及名称前的{};
之间的部分。 - Yksisarvinen.c
文件)。因此,当编译器编译2.c
时,它不会“记住”main
在1.c
中被定义。链接器也看不到main
的源代码,因此它不知道这两个定义是相同的。因此,如果链接器看到重复的符号,它会抛出一个错误。 - user3386109static
,函数对编译单元可见,而结构体则不是。 - ikegami