C++ ODR的意图是什么?
我确实理解ODR所说的,但我不理解它试图实现什么 我看到违反它的两个后果-用户将得到语法错误,这是完全正确的。而且可能会有一些致命的错误,同样,用户是唯一有罪的人 作为违反ODR并得到一些致命错误的示例,我想象如下:C++ ODR的意图是什么?,c++,language-design,linkage,one-definition-rule,C++,Language Design,Linkage,One Definition Rule,我确实理解ODR所说的,但我不理解它试图实现什么 我看到违反它的两个后果-用户将得到语法错误,这是完全正确的。而且可能会有一些致命的错误,同样,用户是唯一有罪的人 作为违反ODR并得到一些致命错误的示例,我想象如下: Class layout (32-bit, alignment member): 0x00: char {c} 0x01: [alignment member, 3 bytes] 0x04: string {str} 0x14
Class layout (32-bit, alignment member):
0x00: char {c}
0x01: [alignment member, 3 bytes]
0x04: string {str}
0x14: short {s}
0x16: [alignment member, 2 bytes]
0x18: unsigned long long {ull}
0x20: float {f}
Size: 36 bytes.
Class layout (64-bit, alignment member):
0x00: char {c}
0x01: [alignment member, 3 bytes]
0x04: string {str}
0x1C: short {s}
0x1E: [alignment member, 2 bytes]
0x20: unsigned long long {ull}
0x28: float {f}
Size: 44 bytes.
Class layout (32-bit, reordered):
0x00: string {str}
0x10: unsigned long long {ull}
0x18: float {f}
0x1C: short {s}
0x1E: char {c}
Size: 31 bytes.
Class layout (64-bit, reordered):
0x00: string {str}
0x18: unsigned long long {ull}
0x20: float {f}
0x24: short {s}
0x26: char {c}
Size: 39 bytes.
a、 cpp
如果示例与ODR无关,请纠正我
那么,ODR是否试图禁止用户做这种有害的事情?我不这么认为
它是否试图为编译器编写者设置一些规则,以避免违反规则带来的潜在危害?可能不会,因为大多数编译器不检查ODR冲突
还有什么?当函数希望获得这些结构中的一个时,您将其重新声明为不同的结构,该函数接收哪个结构,以及如何接收?请记住,C++是静态的,所以如果按值发送结构,函数必须知道它的结构。因为C++是类型安全的,允许违反ODR会违反这种类型的安全性。
最重要的是,缺少ODR有什么好处?我能想到成百上千的事情,如果没有它,会变得更难,而没有任何收获。从字面上讲,在同一名称空间中踩踏以前声明的类型是没有灵活性的。在最佳情况下,它只会使多个包含不需要头保护,这是一个非常小的增益最多。 < P> ODR决定C++程序是如何形成的。ODR冲突意味着您的程序格式不正确,标准没有规定程序将做什么,如果它应该编译,等等。大多数ODR冲突被标记为“无需诊断”,以使编译器编写器的工作更容易 < >这允许C++编译器对你所给的代码做出某些简化的假设,比如<>代码>:A/<代码>到处都是相同的结构类型,并且不必在每个使用点检查。
编译器可以免费获取您的代码并将其编译为c:。或者别的什么。它可以自由地检测ODR冲突,并使用它来证明代码分支无法运行,并消除指向该分支的路径。据我所知,该规则的目的是防止对象在不同的翻译单元中被不同地定义
// a.cpp
#include <iostream>
class SharedClass {
int a, b, c;
bool d;
int e, f, g;
public:
// ...
};
void a(const SharedClass& sc) {
std::cout << "sc.a: " << sc.getA() << '\n'
<< "sc.e: " << sc.getE() << '\n'
<< "sc.c: " << sc.getC() << std::endl;
}
// -----
// b.cpp
class SharedClass {
int b, e, g, a;
bool d;
int c, f;
public:
// ...
};
void b(SharedClass& sc) {
sc.setA(sc.getA() - 13);
sc.setG(sc.getG() * 2);
sc.setD(true);
}
// -----
// main.cpp
int main() {
SharedClass sc;
/* Assume that the compiler doesn't get confused & have a heart attack,
* and uses the definition in "a.cpp".
* Assume that by the definition in "a.cpp", this instance has:
* a = 3
* b = 5
* c = 1
* d = false
* e = 42
* f = -129
* g = 8
*/
// ...
a(sc); // Outputs sc.a, sc.e, and sc.c.
b(sc); // Supposedly modifies sc.a, sc.g, and sc.d.
a(sc); // Does NOT do what you think it does.
}
如果编译器将大小相同的字段按从大到小的顺序放在一起:
// a.cpp
Class layout:
0x00: int {a}
0x04: int {b}
0x08: int {c}
0x0C: int {e}
0x10: int {f}
0x14: int {g}
0x18: bool {d}
Size: 25 bytes.
// b.cpp
Class layout:
0x00: int {b}
0x04: int {e}
0x08: int {g}
0x0C: int {a}
0x10: int {c}
0x14: int {f}
0x18: bool {d}
Size: 25 bytes.
// main.cpp
One of the above, up to the compiler.
Alternatively, may be seen as undefined.
如果愿意,请注意,虽然类在两个定义中的大小相同,但其成员的顺序完全不同
Field comparison (with alignment member):
a.cpp field b.cpp field
a b
b e
c g
d & {align} a
e d & {align}
f c
g f
Field comparison (with hidden reordering):
a.cpp field b.cpp field
a b
b e
c g
e a
f c
g f
d d
因此,从a()
的角度来看,b()
实际上改变了sc.e
,sc.c
,或者sc.a
或者sc.d
(取决于编译方式),完全改变了第二次调用的输出。[请注意,这甚至可能出现在您从未预料到的假定无害的情况下,例如a.cpp
和b.cpp
对SharedClass
具有相同的定义,但指定了不同的对齐方式。这将更改对齐成员的大小,再次为类提供不同的内存布局不同的翻译单位。]
现在,如果相同的字段在不同的翻译单元中有不同的布局,就会发生这种情况。想象一下,如果类在不同的单元中有完全不同的字段,会发生什么
// c.cpp
#include <string>
#include <utility>
// Assume alignment of 4.
// Assume std::string stores a pointer to string memory, size_t (as long long), and pointer
// to allocator in its body, and is thus 16 (on 32-bit) or 24 (on 64-bit) bytes.
// (Note that this is likely not the ACTUAL size of std::string, but I'm just using it for an
// example.)
class SharedClass {
char c;
std::string str;
short s;
unsigned long long ull;
float f;
public:
// ...
};
void c(SharedClass& sc, std::string str) {
sc.setStr(std::move(str));
}
这个SharedClass
不仅有不同的字段,而且大小完全不同。试图把每一个翻译单元当作它们拥有相同的SharedClass
可以而且将会破坏某些东西,而默默地协调每个定义是不可能的。想象一下,如果我们在SharedClass
的同一个实例上调用a()
、b()
和c()
,会发生什么混乱,甚至如果我们尝试创建SharedClass
的实例会发生什么。有了三个不同的定义,而编译器不知道哪一个是真正的定义,事情可能会也会变得糟糕
这完全破坏了单元间的可操作性,要求使用类的所有代码要么在同一个翻译单元中,要么在每个单元中共享完全相同的类定义。因此,ODR要求每个单元只定义一次类,并在所有单元中共享相同的定义,以保证它始终具有相同的定义,并防止整个问题
类似地,考虑这个简单函数,<代码>函数():< /> > < /P> 在这种情况下,每个翻译单元定义变量
global\u int
和Globals::ns\u int
,这意味着程序将有两个完全相同的名称的不同变量。这只能在链接阶段结束,链接器将符号的每个实例视为引用同一实体Globals::ns_int
将比global_int
有更多问题,因为有两个不同的初始化值硬编码到文件中;假设链接器不只是爆炸,程序保证有未定义的行为
ODR的复杂性因所涉及的实体而异。有些东西在整个程序中只能有一个定义,但有些东西可以有多个定义,只要它们完全相同并且每个翻译单元只有一个。无论是哪种情况,目的都是让每个单位都能以完全相同的方式看到实体 不过,主要原因是方便。编译器不仅可以更容易地假设每个翻译单元都遵循ODR,而且速度更快,占用的CPU、内存和磁盘更少。如果没有ODR,编译器必须比较每个翻译单元,以确保每个共享类型和内联函数定义都相同,并且每个
Field comparison (with alignment member):
a.cpp field b.cpp field
a b
b e
c g
d & {align} a
e d & {align}
f c
g f
Field comparison (with hidden reordering):
a.cpp field b.cpp field
a b
b e
c g
e a
f c
g f
d d
// c.cpp
#include <string>
#include <utility>
// Assume alignment of 4.
// Assume std::string stores a pointer to string memory, size_t (as long long), and pointer
// to allocator in its body, and is thus 16 (on 32-bit) or 24 (on 64-bit) bytes.
// (Note that this is likely not the ACTUAL size of std::string, but I'm just using it for an
// example.)
class SharedClass {
char c;
std::string str;
short s;
unsigned long long ull;
float f;
public:
// ...
};
void c(SharedClass& sc, std::string str) {
sc.setStr(std::move(str));
}
Class layout (32-bit, alignment member):
0x00: char {c}
0x01: [alignment member, 3 bytes]
0x04: string {str}
0x14: short {s}
0x16: [alignment member, 2 bytes]
0x18: unsigned long long {ull}
0x20: float {f}
Size: 36 bytes.
Class layout (64-bit, alignment member):
0x00: char {c}
0x01: [alignment member, 3 bytes]
0x04: string {str}
0x1C: short {s}
0x1E: [alignment member, 2 bytes]
0x20: unsigned long long {ull}
0x28: float {f}
Size: 44 bytes.
Class layout (32-bit, reordered):
0x00: string {str}
0x10: unsigned long long {ull}
0x18: float {f}
0x1C: short {s}
0x1E: char {c}
Size: 31 bytes.
Class layout (64-bit, reordered):
0x00: string {str}
0x18: unsigned long long {ull}
0x20: float {f}
0x24: short {s}
0x26: char {c}
Size: 39 bytes.
// z.cpp
#include <cmath>
int func(int x, int y) {
return static_cast<int>(round(pow((2 * x) - (3 * y), x + y) - (x / y)));
}
// -----
// y.cpp
int func(int x, int y) { return x + y; }
// -----
// x.cpp
int q = func(9, 11);
// Compiler has a heart attack, call 911.
// i.cpp
int global_int;
namespace Globals {
int ns_int = -5;
}
// -----
// j.cpp
int global_int;
namespace Globals {
int ns_int = 5;
}