为什么一个定义规则,而不是一个声明规则?
Why One Definition Rule, not One Declaration Rule?
我已阅读以下材料:
https://www.wikiwand.com/en/One_Definition_Rule
http://en.cppreference.com/w/cpp/language/definition
What is the difference between a definition and a declaration?
但是还是想不通为什么是一定义规则而不是一声明规则?
我认为声明是定义的子集,因此 一个定义规则 就足够了。
因为同一个声明,在一个.h文件中,可能包含在多个编译单元中,而且因为多个定义肯定是编程错误,而多个声明则不是。
一个声明规则过于严格,会阻止多次使用相同 header 的程序进行编译。这也使得无法使用反向引用定义数据结构。
查看第一点(使用 headers)的一个简单方法是考虑一个由两个翻译单元组成的程序,A.cpp
和 B.cpp
,它们都包含 <string>
header.
翻译单元 A.cpp
和 B.cpp
是独立翻译的。通过包含 <string>
,两个翻译单元都获得了 std::string
.
的声明
至于第二点(具有反向引用的数据结构),请考虑定义一棵树的示例,其中每个节点都有对其 parent 树的反向引用:
// Does not compile
struct tree {
struct node *root;
};
struct node {
struct node *left;
struct node *right;
struct tree *owner;
};
此示例无法编译,因为 struct node *tree
中的 node
未声明。切换 struct node
和 struct tree
声明的顺序不会有帮助,因为这样 struct tree *owner
中的 tree
将不会被声明。 C 和 C++ 中的唯一解决方案是为两个 struct
中的任何一个引入第二个声明。
因为当你在头文件中声明了一个函数
// header toto.h
int f(void);
而你想在它所属的编译单元中定义它,你会做
#include "toto.h"
int f(void) {
return 0;
}
定义也是一个声明,所以这个编译单元看到两个声明,一个在头文件中,一个在.c
或.cpp
文件中。
简而言之,多重声明规则允许检查不同源文件之间的一致性。
真正的原因是 C++ 翻译模型可以轻松处理冲突的多个声明;它只需要工具集的编译器部分来检测源代码中的此类错误:
int X();
void X(); // error
编译器可以轻松做到这一点。
当任何翻译单元中都没有这样的错误时,就没有问题;每个翻译单元中的每个 X()
调用都是相同的;剩下要做的就是 linker 每次呼叫到一个正确的目的地时 link。声明已经完成了它们的工作,不再起作用。
现在有了多个定义,没那么容易了。定义涉及多个翻译单元,超出了编译阶段的范围。
我们已经在上面的示例中看到了这一点。 X()
调用已经到位,但现在我们需要保证它们都到达相同的目的地,相同的 X()
.[=14] 的 定义 =]
这样的定义只能有一个应该很清楚,但是如何执行呢?简单的说,到link目标代码在一起的时候,源代码已经处理好了。
答案是C++基本选择把负担推给程序员。强制 compiler/linker 实施者检查所有多个定义是否相等并检测差异将超出 C++ 工具集在大多数现实生活中的能力,或者完全破坏这些工具的工作方式,因此实用的解决方案是禁止它 and/or 强制程序员确保它们都是相同的,否则会出现未定义的行为。
定义是声明的子集,而不是相反。每个定义都是声明,也有不是定义的声明。
int i = 3; // definition and declaration
extern int i; // ok: (re)declaration
int i = 4; // error: redefinition
extern int j; // declaration
extern int j; // ok: (re)declaration
int j = 5; // ok: (re)declaration and definition
int j = 6; // error: redefinition
我已阅读以下材料:
https://www.wikiwand.com/en/One_Definition_Rule
http://en.cppreference.com/w/cpp/language/definition
What is the difference between a definition and a declaration?
但是还是想不通为什么是一定义规则而不是一声明规则?
我认为声明是定义的子集,因此 一个定义规则 就足够了。
因为同一个声明,在一个.h文件中,可能包含在多个编译单元中,而且因为多个定义肯定是编程错误,而多个声明则不是。
一个声明规则过于严格,会阻止多次使用相同 header 的程序进行编译。这也使得无法使用反向引用定义数据结构。
查看第一点(使用 headers)的一个简单方法是考虑一个由两个翻译单元组成的程序,A.cpp
和 B.cpp
,它们都包含 <string>
header.
翻译单元 A.cpp
和 B.cpp
是独立翻译的。通过包含 <string>
,两个翻译单元都获得了 std::string
.
至于第二点(具有反向引用的数据结构),请考虑定义一棵树的示例,其中每个节点都有对其 parent 树的反向引用:
// Does not compile
struct tree {
struct node *root;
};
struct node {
struct node *left;
struct node *right;
struct tree *owner;
};
此示例无法编译,因为 struct node *tree
中的 node
未声明。切换 struct node
和 struct tree
声明的顺序不会有帮助,因为这样 struct tree *owner
中的 tree
将不会被声明。 C 和 C++ 中的唯一解决方案是为两个 struct
中的任何一个引入第二个声明。
因为当你在头文件中声明了一个函数
// header toto.h
int f(void);
而你想在它所属的编译单元中定义它,你会做
#include "toto.h"
int f(void) {
return 0;
}
定义也是一个声明,所以这个编译单元看到两个声明,一个在头文件中,一个在.c
或.cpp
文件中。
简而言之,多重声明规则允许检查不同源文件之间的一致性。
真正的原因是 C++ 翻译模型可以轻松处理冲突的多个声明;它只需要工具集的编译器部分来检测源代码中的此类错误:
int X();
void X(); // error
编译器可以轻松做到这一点。
当任何翻译单元中都没有这样的错误时,就没有问题;每个翻译单元中的每个 X()
调用都是相同的;剩下要做的就是 linker 每次呼叫到一个正确的目的地时 link。声明已经完成了它们的工作,不再起作用。
现在有了多个定义,没那么容易了。定义涉及多个翻译单元,超出了编译阶段的范围。
我们已经在上面的示例中看到了这一点。 X()
调用已经到位,但现在我们需要保证它们都到达相同的目的地,相同的 X()
.[=14] 的 定义 =]
这样的定义只能有一个应该很清楚,但是如何执行呢?简单的说,到link目标代码在一起的时候,源代码已经处理好了。
答案是C++基本选择把负担推给程序员。强制 compiler/linker 实施者检查所有多个定义是否相等并检测差异将超出 C++ 工具集在大多数现实生活中的能力,或者完全破坏这些工具的工作方式,因此实用的解决方案是禁止它 and/or 强制程序员确保它们都是相同的,否则会出现未定义的行为。
定义是声明的子集,而不是相反。每个定义都是声明,也有不是定义的声明。
int i = 3; // definition and declaration
extern int i; // ok: (re)declaration
int i = 4; // error: redefinition
extern int j; // declaration
extern int j; // ok: (re)declaration
int j = 5; // ok: (re)declaration and definition
int j = 6; // error: redefinition