【问题标题】:C++: Different classes with the same name in different translation unitsC ++:不同翻译单元中具有相同名称的不同类
【发布时间】:2012-03-11 00:08:00
【问题描述】:

考虑以下示例:

// usedclass1.hpp  
#include <iostream>  
class UsedClass
{  
public:
  UsedClass() { }  
  void doit() { std::cout << "UsedClass 1 (" << this << ") doit hit" << std::endl; }
};  

// usedclass2.hpp  
#include <iostream>
class UsedClass
{
public:
  UsedClass() { }
  void doit() { std::cout << "UsedClass 2 (" << this << ") doit hit" << std::endl; }
};

// object.hpp
class Object
{
public:
  Object();
};

// object.cpp
#include "object.hpp"
#include "usedclass2.hpp"
Object::Object()
{
  UsedClass b;
  b.doit();
}

// main.cpp
#include "usedclass1.hpp"
#include "object.hpp"
int main()
{
  Object obj;
  UsedClass a;
  a.doit();
}

代码编译时没有任何编译器或链接器错误。但是输出对我来说很奇怪:

  • Fedora x86_64 上的 gcc (Red Hat 4.6.1-9) 没有优化 [EG1]:

    UsedClass 1 (0x7fff0be4a6ff) doit hit
    UsedClass 1 (0x7fff0be4a72e) doit hit

  • 与 [EG1] 相同,但启用了 -O2 选项 [EG2]:

    UsedClass 2 (0x7fffcef79fcf) doit hit
    UsedClass 1 (0x7ffffcef79fff) doit hit

  • msvc2005 (14.00.50727.762) 在没有优化的 Windows XP 32 位 [EG3]:

    UsedClass 1 (0012FF5B) doit hit
    UsedClass 1 (0012FF67) doit hit

  • 与 [EG3] 相同,但启用了 /O2(或 /Ox)[EG4]:

    UsedClass 1 (0012FF73) doit hit
    UsedClass 1 (0012FF7F) doit hit

我希望链接器错误(假设违反了 ODR 规则)或 [EG2] 中的输出(内联代码,没有从翻译单元导出,保留 ODR 规则)。因此我的问题是:

  1. 为什么输出 [EG1]、[EG3]、[EG4] 可能?
  2. 为什么我从不同的编译器甚至从同一个编译器得到不同的结果?这让我觉得标准在这种情况下没有指定行为。

感谢您的任何建议、cmets 和标准解释。

更新
我想了解编译器的行为。更准确地说,如果违反 ODR,为什么不会产生错误。一个假设是,由于类 UsedClass1UsedClass2 中的所有函数都被标记为内联(因此 C++03 3.2 违反)链接器不报告错误,但在这种情况下输出 [EG1]、[EG3]、[EG4] 看起来很奇怪。

【问题讨论】:

  • 违反了 ODR,当发生这种情况时,您不能保证链接器错误。行为未定义。
  • 我无法回答您有关标准的问题,但这就是我们有命名空间的原因。

标签: c++ one-definition-rule


【解决方案1】:

您的程序违反了单一定义规则并调用了未定义的行为。
如果您破坏 ODR 但行为未定义,则该标准不强制要求提供诊断消息。

C++03 3.2 一定义规则

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

每个程序都应包含该程序中使用的每个非内联函数或对象的一个​​定义;无需诊断。定义可以显式出现在程序中,可以在标准或用户定义库中找到,或者(在适当时)隐式定义(参见 12.1、12.4 和 12.8)。内联函数应在使用它的每个翻译单元中定义。

该标准进一步定义了存在多个符号定义的具体要求,这些在 3.2 的第 5 段中得到了恰当的定义。

类类型(第 9 条)、枚举类型(7.2)、带外部链接的内联函数(7.1.2)、类模板(第 14 条)、非静态函数模板(14.5)可以有多个定义.5)、类模板的静态数据成员 (14.5.1.3)、类模板的成员函数 (14.5.1.1) 或在程序中未指定某些模板参数的模板特化 (14.7, 14.5.4)前提是每个定义出现在不同的翻译单元中,并且定义满足以下要求。给定这样一个名为 D 的实体在多个翻译单元中定义,那么

D 的每个定义都应包含相同的标记序列; 和 ...

【讨论】:

  • 您缺少适用的规则,这将是关于内联函数定义不一致的规则。
  • 与此处相关的 /5 的特定部分是“D 的每个定义都应包含相同的标记序列”。 UsedClass 的两个定义相差 1 个标记(一个字符串字面量),这就足够了。
  • An inline function shall be defined in every translation unit in which it is used. 在示例中我只使用了内联函数,这些函数在每个翻译单元中都有定义。
  • @user1221434:但它违反了答案中引用的粗体部分。
  • @Als:是的,在输入我的评论时没看到。
【解决方案2】:

为什么输出 [EG1]、[EG3]、[EG4] 可能?

简单的答案是行为未定义,所以一切皆有可能。

大多数编译器通过在定义它的每个翻译单元中生成一个副本来处理内联函数;然后链接器任意选择一个包含在最终程序中。这就是为什么在禁用优化的情况下,它在两种情况下都调用相同的函数。启用优化后,编译器可能会内联函数,在这种情况下,每个内联调用都将使用当前翻译单元中定义的版本。

这让我觉得标准在这种情况下没有指定行为。

没错。打破一个定义规则会产生未定义的行为,并且不需要诊断。

【讨论】:

  • 听起来很合理。例如,gcc 中的__attribute__ ((always_inline)) 使 EG1 案例输出与 EG2 相同。感谢您确认我的猜测。
【解决方案3】:

这是标准第 3.2 节中禁止您正在做的事情的规则(C++11 措辞):

类类型可以有多个定义(第 9 条)、枚举类型 (7.2)、具有外部链接的内联函数 (7.1.2)、类模板(第 14 条)、非静态函数模板 (14.5.6)、类模板的静态数据成员 (14.5.1.3)、类模板的成员函数 (14.5.1.1) 或模板特化如果每个定义出现在不同的翻译单元中,并且定义满足以下要求,则程序中未指定参数 (14.7, 14.5.5) 。给定这样一个名为D 的实体在多个翻译单元中定义,那么

  • D 的每个定义应由相同的标记序列组成;和

  • D 的每个定义中,根据3.4 查找的对应名称应指在D 定义中定义的实体,或应指同一实体,在重载决议(13.3) 之后和之后部分模板特化 (14.8.3) 的匹配,除了如果对象在 D 的所有定义中具有相同的文字类型,则名称可以引用具有内部链接或没有链接的 const 对象,并且该对象被初始化为一个常量表达式(5.19),使用对象的值(但不是地址),并且对象在D的所有定义中都具有相同的值;和

  • D的每个定义中,对应的实体应该有相同的语言链接;和

  • D 的每个定义中,引用的重载运算符、对转换函数、构造函数、运算符新函数和运算符删除函数的隐式调用,应指同一函数,或在定义中定义的函数D;和

  • D 的每个定义中,(隐式或显式)函数调用使用的默认参数被视为其标记序列存在于D 的定义中;也就是说,默认参数受上述三个要求的约束(并且,如果默认参数具有带有默认参数的子表达式,则此要求递归适用)。

  • 如果D 是一个具有隐式声明的构造函数(12.1)的类,就好像构造函数被隐式定义在每个使用它的翻译单元中,并且每个翻译单元中的隐式定义都应调用D 的基类或类成员的相同构造函数。

在您的程序中,您违反了 class UsedClass 的 ODR,因为不同编译单元中的标记不同。您可以通过将 UsedClass::doit() 的定义移到类主体之外来解决这个问题,但同样的规则也适用于内联函数的主体。

【讨论】:

    猜你喜欢
    • 2016-03-29
    • 1970-01-01
    • 1970-01-01
    • 2013-04-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多