c++中的一个定义规则

One definition rule in c++

本文关键字:一个 定义 规则 c++      更新时间:2023-10-16

根据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 { ... };