根据c++标准:
任何翻译单元不得包含一个以上的任何定义变量、函数、类类型、枚举类型或模板
//--translation_unit.cpp--//
int a;
void foo()
{
int a; //Second defention of a. ODR fails.
}
你能解释一下ODR到底是怎么工作的吗?
这并不违反规则,因为您定义了两个不同的变量。它们具有相同的名称,但在不同的作用域中声明,因此是独立的实体。每个都有一个单独的定义。
函数作用域中的声明被称为隐藏全局命名空间中的声明。在函数中,限定名 a
指的是局部变量,限定名 ::a
指的是全局变量。
它们不违反ODR,因为它们具有不同的作用域。
第一个a
具有全局作用
已知具有全局作用域(也称为文件作用域)的变量在定义
之后的整个文件
第二个a
具有局部作用域
已知具有局部作用域(也称为块作用域)的变量仅在定义它的块内
为了更清楚地理解c++的ODR,你应该研究的概念是:存储持续时间,范围和链接
您没有重新定义a
。
您刚刚定义了一个新的变量a
。它的作用域仅在函数内部,与原函数无关(原函数具有全局作用域),并在函数内部遮蔽原函数。
你能解释一下ODR到底是怎么工作的吗?
下面是一个违反ODR的例子:
/* file : module.cpp */
#include <stdio.h>
inline int foo() {
printf("module.foo: %pn", &foo);
return 1;
}
static int bar = foo();
/* file : main.cpp */
#include <stdio.h>
inline int foo() {
printf("main.foo: %pn", &foo);
return 2;
}
int main(int argc, char *argv[]) {
return foo();
}
可以看到,函数int foo()
在两个模块中定义不同。现在观察它如何根据所请求的优化级别(O3 vs 0)产生不同的行为:
$ clang++ --std=c++11 -O0 main.cpp module.cpp && ./a.out
module.foo: 0x100a4aef0
module.foo: 0x100a4aef0
$ clang++ --std=c++11 -O3 main.cpp module.cpp && ./a.out
module.foo: 0x101146ee0
main.foo: 0x101146ee0
输出是不同的,因为对于内联函数编译器在每个编译模块中产生一个链接器符号。这个符号(在每个编译模块中)被标记为"任意选择,它们都是相同的"。在第一种情况下,当所有优化都被禁用时,链接器从module.cpp中获取定义。在第二种情况下,编译器只是内联两个函数,因此不需要链接器的额外工作。
还有其他违反ODR产生奇怪行为的例子。所以,不要这样做:)
注:奖励,来自现实生活的案例:
/* a.cpp */
struct Rect
{
int x,y,w,h;
Rect(): x(0),y(0),w(0),h(0)
};
/* b.cpp */
struct Rect
{
double x,y,w,h;
Rect(): x(0),y(0),w(0),h(0)
};
这里的问题与前面的示例相同(因为Rect构造函数是隐式内联的)。根据月球编译器的不同阶段,会选择一种实现或另一种实现,产生奇怪的结果(int
版本将留下doubles
的一部分未初始化,double
版本将在int
之外并损坏内存)。防止这种情况的好方法是使用匿名命名空间(c++)或将struct声明为static
(C):
/* file.cpp */
namespace {
struct Rect { ... };
}
/* file.c */
static struct Rect { ... };