为什么C/C++中存在一个定义规则

42fyovps  于 12个月前  发布在  C/C++
关注(0)|答案(6)|浏览(93)

在C和C++中,你不能有一个函数有两个定义。例如,假设我们有以下两个文件:
1.c

int main(){ return 0;}

2.c

int main(){ return 0;}

发出命令gcc 1.c 2.c将给予您一个duplicate symbol链接器错误。为什么结构和类不发生同样的事情呢?为什么我们允许同一个结构体有多个定义,只要它们有相同的标记?

zzoitvuj

zzoitvuj1#

要回答这个问题,必须深入研究编译过程以及每个部分需要什么(为什么要执行这些步骤的问题更具有历史意义,可以追溯到C标准化之前的开始)
C和C++程序是通过多个步骤编译的:
1.预处理
1.汇编
1.联动
预处理是以#开始的所有内容,在这里并不重要。
编译是在每个翻译单元上执行的(通常是一个.c.cpp文件加上它包含的头文件)。编译器每次获取一个翻译单元,读取它并生成类及其成员的内部列表,然后在给定单元中(基于结构列表)汇编每个函数的代码。如果函数调用不是内联的(例如它是在不同的TU中定义的),编译器产生一个“链接”-“* 请在这里插入函数X *”供链接器读取。
然后,链接器获取所有编译的翻译单元,并将它们合并为一个二进制文件,替换编译器指定的所有链接。
那么,每个阶段都需要什么呢?
对于编译阶段,您需要

  • 此文件中使用的每个类的 definition-编译器需要知道每个类成员的大小和偏移量以生成汇编
  • 这个文件中使用的每个函数的 * 声明 *-以产生那些“链接”。

由于函数定义不需要用于生成程序集(只要它们在某处编译),因此它们在编译阶段不需要,仅在链接阶段需要。
总而言之:
有一个定义规则是为了保护程序员不受这些影响。如果他们不小心定义了一个函数两次,链接器会注意到,并没有产生可执行文件.
然而,每个翻译单元都需要类定义,因此无法为它们设置这样的规则。由于它不能被语言所强迫,程序员必须是负责任的人,不能以不同的方式定义同一个类。
ODR还有其他限制,例如:你have to define template functions (or template class methods) in header files。你也可以对编译器说:“这个函数的每个定义都是一样的,相信我,伙计”,然后把函数变成inline

k3bvogb1

k3bvogb12#

对于具有2个定义的函数没有用例。要么这两个定义必须相同,使其变得无用,要么编译器无法分辨您指的是哪一个。
对于类或结构则不是这样。允许它们的多个定义也有很大的优点,即如果我们想在多个文件中使用classstruct。(由于包含,这会间接导致多个定义。)

qqrboqgw

qqrboqgw3#

结构、类、联合和枚举定义了可在多个编译单元中用于定义这些类型的对象的类型。因此,每个编译单元都需要知道类型是如何定义的,例如,为对象正确分配内存或确保类的指定成员确实存在。
对于函数(如果它们不是内联函数),有它们的声明而没有它们的定义就足以生成例如函数调用。
但功能定义应单一。否则,编译器将不知道要调用什么函数,或者由于重复,目标代码将太大,并且容易出错。

w6mmgewl

w6mmgewl4#

很简单这是一个范围的问题。非静态函数被链接在一起的每个编译单元看到(可调用),而结构只在定义它们的编译单元中看到。
例如,将以下内容链接在一起是有效的,因为很清楚使用的是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(); }
e0bqpujr

e0bqpujr5#

实际上,每个编程元素都与其适用范围相关联。并且在此范围内,不能将同一个名称与一个元素的多个定义关联。在编译世界中:
1.在一个文件中,不能有多个同名的类定义。但是你可以在不同的编译单元中拥有它。
1.在一个链接单元(库或可执行文件)中,您不能具有相同的函数或全局变量名称,但您可以在不同的库中使用相同名称的函数。
1.你不能在同一个目录下拥有相同名称的共享库,但是你可以在不同的目录下拥有它们。
C/C编译后的性能非常好。检查2个对象(如函数或类)的身份是一项耗时的任务。所以,还没有完成。仅考虑名称进行比较。最好是考虑到两种类型是不同的,并且错误,然后检查它们的身份。此规则的唯一例外是文本宏。
宏是一个预处理器概念,历史上允许有多个相同的宏定义。如果定义发生更改,则会生成警告。比较宏上下文很容易,只是一个简单的字符串比较,但有些宏定义可能很庞大。
类型是编译器的概念,由编译器解析。类型不存在于对象库中,由相应变量的大小表示。因此,没有理由在此范围内检查类型名称冲突。
另一方面,函数和变量被命名为指向可执行代码或数据的指针。它们是应用程序的构建块。在某些情况下,应用程序是由来自世界各地的代码和库组装而成的。为了使用别人的函数,你最好知道它的名字,你不希望别人使用相同的名字。在共享库中,函数和变量的名称通常存储在哈希表中。那里没有复制品的地方。
正如我已经提到的,检查函数的相同内容很少被执行,但是有一些情况,但不是在c或c
中。

hmtdttj4

hmtdttj46#

阻止在编程中使用同一事物的两个不同定义的原因是为了避免在运行时决定使用哪个定义的模糊性。
如果你有两个不同的实现在一个程序中共存,那么就有可能将它们(每个都有不同的名字)别名为一个公共引用,以便在运行时决定使用其中的哪一个。
无论如何,为了区分两者,你必须能够指出你想要使用的编译器。在C++中,你可以重载一个函数,给它相同的名字和不同的参数列表,这样你就可以区分你想使用哪一个。但是在C中,编译器只保留函数的名称,以便在链接时能够解决哪个定义与您在不同编译单元中使用的名称相匹配。如果链接器以两个同名的不同定义结束,它无法为您决定使用哪一个,因此它会发出错误并放弃构建过程。
以富有成效的方式使用这种模糊性的意图应该是什么?这才是你真正要问自己的问题。

相关问题