繁体   English   中英

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

[英]Why does the one definition rule exist in C/C++

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

1.c

int main(){ return 0}

2.c

int main(){ return 0}

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

要回答这个问题,必须深入研究编译过程以及每个部分需要什么(为什么执行这些步骤的问题更具历史性,在标准化之前要追溯到 C 的开头)

C 和 C++ 程序分多个步骤编译:

  1. 预处理
  2. 汇编
  3. 连锁

预处理是以#开头的所有内容,这里并不重要。

对每个翻译单元(通常是单个.c.cpp文件加上它包含的头文件)执行编译。 编译器一次接受一个翻译单元,读取它并生成类及其成员的内部列表,然后是给定单元中每个函数的汇编代码(基于结构列表)。 如果函数调用没有内联(例如它在不同的 TU 中定义),编译器会生成一个“链接”-“请在此处插入函数 X ”以供链接器读取。

然后链接器获取所有已编译的翻译单元并将它们合并为一个二进制文件,替换编译器指定的所有链接。


现在,每个阶段需要什么?

对于编译阶段,您需要

  • 此文件中使用的每个类的定义- 编译器需要知道每个类成员的大小和偏移量才能生成程序集
  • 此文件中使用的每个函数的声明- 生成这些“链接”。

由于生成程序集不需要函数定义(只要它们在某处编译),因此在编译阶段不需要它们,只在链接阶段需要。


总结:

一个定义规则是为了保护程序员免受他们自己的伤害。 如果他们不小心定义了两次函数,链接器会注意到并没有生成可执行文件。

但是,每个翻译单元都需要类定义,因此无法为它们设置这样的规则。 既然不能被语言强制,程序员就必须是负责任的人,不能以不同的方式定义同一个类。

ODR 还有其他限制,例如您必须在头文件中定义模板函数(或模板类方法) 你也可以承担起责任,对编译器说“这个函数的每个定义都是一样的,相信我,伙计”并使函数inline

具有 2 个定义的函数没有用例。 要么这两个定义必须相同,使其无用,要么编译器无法分辨您的意思。

这不是类或结构的情况。 允许对它们进行多个定义还有一个很大的优势,即如果我们想在多个文件中使用一个classstruct (由于包含,这间接导致了多个定义。)

结构、类、联合和枚举定义了可以在多个编译单元中使用的类型来定义这些类型的对象。 因此,每个编译单元都需要知道类型是如何定义的,例如为对象正确分配内存或确保类的指定成员确实存在。

对于函数(如果它们不是内联函数),只需在没有定义的情况下声明它们即可生成例如函数调用。

但函数定义应该是单一的。 否则编译器将不知道要调用什么函数,或者目标代码会因重复而太大并且容易出错。

这很简单:这是一个范围问题。 每个链接在一起的编译单元都可以看到(可调用)非静态函数,而结构只能在定义它们的编译单元中看到。

例如,将以下内容链接在一起是有效的,因为很清楚正在使用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(); }

实际上,每个编程元素都与其适用范围相关联。 在此范围内,您不能使用与元素的多个定义相关联的相同名称。 在编译世界中:

  1. 在一个文件中不能有多个同名的类定义。 但是您可以在不同的编译单元中使用它。
  2. 在单个链接单元(库或可执行文件)中不能具有相同的函数或全局变量名称,但在不同的库中可能具有相同的函数名称。
  3. 不能将同名的共享库放在同一目录中,但可以将它们放在不同的目录中。

C/C++ 编译非常看重编译性能。 检查 2 个对象(如函数或类)的身份是一项耗时的任务。 所以,它没有完成。 仅考虑名称进行比较。 最好考虑两种类型是不同的并且出错然后检查它们的身份。 此规则的唯一例外是文本宏。

宏是一个预处理器概念,历史上允许有多个相同的宏定义。 如果定义发生更改,则会生成警告。 比较宏上下文很容易,只是一个简单的字符串比较,但一些宏定义可能很大。

类型是编译器的概念,它们由编译器解析。 类型不存在于对象库中,由相应变量的大小表示。 因此,没有理由在此范围内检查类型名称冲突。

另一方面,函数和变量被命名为指向可执行代码或数据的指针。 它们是应用程序的构建块。 在某些情况下,应用程序是由来自世界各地的代码和库组合而成的。 为了使用其他人的功能,您最好现在使用它的名称,并且您不希望其他人使用相同的名称。 在共享库中,函数和变量的名称通常存储在哈希表中。 那里没有重复的地方。

正如我已经提到的,很少会检查相同内容的函数,但是有一些情况,但不是在 c 或 c++ 中。

阻止在编程中使用同一事物的两个不同定义的原因是为了避免在运行时决定使用哪个定义的歧义。

如果您对同一事物有两种不同的实现以共存于一个程序中,则有可能将它们(每个名称不同)别名为一个公共引用,以在运行时决定使用这两者中的哪一个。

无论如何,为了区分两者,您必须能够指示编译器要使用哪一个。 在 C++ 中,您可以重载一个函数,赋予它相同的名称和不同的参数列表,这样您就可以区分要使用两者中的哪一个。 但是在 C 中,编译器只保留函数的名称,以便能够在链接时解决哪个定义与您在不同编译单元中使用的名称相匹配。 如果链接器以两个具有相同名称的不同定义结尾,则它无法为您决定使用哪个,因此它会发出错误并放弃构建过程。

以富有成效的方式使用这种模糊性的意图是什么? 这是您实际上要问自己的问题。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM