一个定义规则警告

One definition rule warning

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

我被一个严重违反"一个定义规则"的行为咬了一口。我现在害怕在我的项目中有很多细微的错误。

例如,下面的程序将在visual studio 2015中导致空指针解引用:

Source1.cpp:
----------
struct S {
    double d = 0;
};
void Foo() {
    S s;
}

Source2.cpp:
-----------
struct S {
    int a = 0;
};
int main() {
    int value = 5;
    int& valueRef = value;
    S s;           // valueRef is erased due to S::d initialization from Source1.cpp
    valueRef++;    // crash
}

编译时没有警告。

这很讨厌,因为Source2.cpp甚至没有使用Source1.cpp的任何东西。如果我从项目中删除Source1.cpp,它仍然可以编译,并且不再有问题。

在大型项目中,似乎很难确保没有cpp文件"本地"定义具有已定义名称的结构或类。

我有一些类如Point, Serie, State, Item,…我认为这是OK的小cpp文件,但我意识到这是不安全的。

是否有编译器警告来捕获此类错误?如果没有,避免违反ODR的最佳实践是什么?

在这个特殊的例子中,在最底部的ODR冲突(它实际上导致了您正在观察的问题)是S类的隐式定义的内联构造函数。你的程序有两个不匹配的内联S::S()函数版本,这可以看作是由原始ODR冲突引起的另一个ODR冲突(即相同的类定义不同)。

在当前的c++编译基础结构中,实现很难"看到"这个错误。当然,只要足够努力,这是可以做到的。

在这种情况下,为了使错误"可见",可以显式地声明并定义类构造函数为空体的非内联函数。存在两个非内联S::S()将触发链接器错误。

可以理解的是,您可能会认为这是一个过度人为的度量,在某些情况下是不可接受的,因为它可能会改变类的"聚合"状态。

如果没有,避免违反ODR的最佳实践是什么?

这就是命名空间存在的根本原因。

每个软件组件使用一个知名的命名空间(例如boost, std, asio, sql, mytool, yourlib等)。

名称的命名空间实际上构成了其名称的一部分,因此如下:

namespace X {
  struct S {};
}
namespace Y {
  struct S {};
}
struct S {};

导致定义三个不同的类。一个叫X::S,一个叫Y::S,另一个叫S,也叫::S

::全局命名空间。避免在这里声明名称是一个好主意,因为您在程序中使用的任何C组件(或天真编写的c++组件)都会很快用自己的名称污染这个命名空间。

  • 使用强大的namespace,即使使用数百万代码,组织类/结构名也不难。不要忘记namespace可以定义嵌套关卡

  • 如果你真的想要一些"本地"的定义,试试匿名namespace

  • 我记得如果程序员违反了ODR,标准明确地不需要任何诊断,所以依靠我们自己。

cppcheck将检测ODR违规,并且可以集成到Visual Studio中。

下面是一个使用cppcheck ver 2.6的快速命令行调用来查找ODR:

cppcheck classes/config/foo.cpp
输出:

classes/config/foo.h:15:1: error: The one definition rule is violated, different classes/structs have the same name 'MyClass' [ctuOneDefinitionRuleViolation]
class MyClass
^
classes/config/foo.h:60:1: note: The one definition rule is violated, different classes/structs have the same name 'MyClass'
class MyClass
^
classes/config/foo.h:15:1: note: The one definition rule is violated, different classes/structs have the same name 'MyClass'
class MyClass
^

请注意,正确捕获所有odr需要能够立即查看完整的应用程序源代码——完整的程序分析。因此,cppcheck和其他静态分析程序可能会给出误报或错过一些违规行为。然而,这是一种寻找可能有问题的程序结构的简单的自由/开源软件方法。