根据 c++ 标准:
任何翻译单元都不得包含一个以上的任何变量、函数、类类型、枚举类型或模板的定义。
//--translation_unit.cpp--//
int a;
void foo()
{
int a; //Second defention of a. ODR fails.
}
你能解释一下 ODR 是如何工作的吗?
根据 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: %p\n", &foo);
return 1;
}
static int bar = foo();
/* file : main.cpp */
#include <stdio.h>
inline int foo() {
printf("main.foo: %p\n", &foo);
return 2;
}
int main(int argc, char *argv[]) {
return foo();
}
如您所见,函数int foo()
在两个模块中定义不同。现在观察,它如何根据请求的优化级别(O3与O0)产生不同的行为:
$ 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
输出是不同的,因为对于内联函数,编译器会在每个编译模块中生成一个链接器符号。这个符号(在每个编译模块中)被标记为“pick any,它们都是一样的”。在第一种情况下,当所有优化都被禁用时,链接器会从 module.cpp 中获取定义。在第二种情况下,编译器只内联这两个函数,因此不需要链接器的额外工作。
当违反 ODR 产生奇怪的行为时,还有其他示例。所以,不要这样做:)
PS奖金,来自现实生活的案例:
/* 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
s 并破坏那里的内存)。防止这种情况的好方法是使用匿名命名空间(C++)或将结构声明为static
(C):
/* file.cpp */
namespace {
struct Rect { ... };
}
/* file.c */
static struct Rect { ... };