c ++中的一个定义规则

时间:2014-05-13 07:12:15

标签: c++ one-definition-rule

根据c ++标准:

  

任何翻译单位不得包含任何一个以上的定义   变量,函数,类类型,枚举类型或模板。

//--translation_unit.cpp--//
int a;

void foo()
{
    int a; //Second defention of a. ODR fails.
}

你能解释一下ODR实际上是如何运作的吗?

4 个答案:

答案 0 :(得分:11)

这并不违反规则,因为您定义了两个不同的变量。它们具有相同的名称,但在不同的范围内声明,因此是单独的实体。每个都有一个单一的定义。

函数范围中的声明被称为隐藏全局命名空间中的声明。在函数中,非限定名称 a引用局部变量,而限定名称 ::a引用全局变量。

答案 1 :(得分:0)

您没有再次定义a

您刚刚定义了一个新变量a。它仅在函数内部具有范围,与原始范围(具有全局范围)无关,并且在函数内部隐藏原始范围。

答案 2 :(得分:0)

  

你能解释一下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 vs 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

输出不同,因为对于内联函数,编译器会在每个编译模块中生成链接器符号。此符号(在每个编译模块中)标记为&#34;选择任何一个,它们都是相同的&#34;。在第一种情况下,当禁用所有优化时,链接器从module.cpp中获取定义。在第二种情况下,编译器只是内联这两个函数,因此不需要链接器的其他工作。

还有其他一些例子,违反ODR会产生奇怪的行为。所以,不要这样做:)。

P.S。现金生活中的奖金:

/* 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 ++)或将结构声明为static(C):

/* file.cpp */
namespace {
    struct Rect { ... };
}
/* file.c */
static struct Rect { ... };

答案 3 :(得分:0)

他们没有违反ODR,因为他们的范围不同。

第一个a具有全局范围

  

具有全局范围(也称为文件范围)的变量是已知的   在定义它之后的整个文件中

第二个a具有本地范围

  

具有局部范围(也称为块范围)的变量是已知的   仅在定义它的块内

为了更清楚地了解C ++的ODR,您应该研究的概念是:存储持续时间,范围和链接