【问题标题】:Why isn't the ODR violated by taking the address of an inline-defined static const integral member variable?为什么获取内联定义的静态 const 整数成员变量的地址不会违反 ODR?
【发布时间】:2014-05-27 13:54:57
【问题描述】:

这样的事情如果编译的话,显然违反了 C++ 的单一定义规则:

// Case 1
// something.h

struct S {};

struct A
{
   static const S val = S();
};

因为如果 something.h 包含在多个模块中,A::val 的定义将被重复。但是,这是允许的:

// Case 2    
// someOtherThing.h

struct B
{
   static const int val = 3;
};

据我了解,这种情况正常的原因是因为B::val 是一个整数类型的编译时常量,所以编译器基本上可以对所有对B::val 的引用进行搜索和替换文字3(反汇编的检查表明这正是它所做的)。因此,在某种意义上,在最终产品中,B::val 的定义,因此 ODR 不适用。但是,请考虑一下:

// Case 3
// yetAnotherThing.h

struct C
{
   static const int val = 3;

   const int* F()
   {
      return &val;
   }
};

这是允许的,反汇编显示在这种情况下,实际上已经预留了一些内存位置来存储C::val的值。从表面上看,这意味着如果 yetAnotherThing.h 包含在多个模块中,我们现在违反了 ODR,因为 static const int val = 3 现在会导致存储被“发射”。然而编译器和链接器 (VC++2012) 都没有抱怨。

为什么?这只是编译器/链接器的作者必须处理的令人讨厌的特殊情况吗?如果是这样,为什么不能使用相同的系统来使案例 #1 也可以工作?

(欢迎使用标准中的相关引用,但它们不一定能回答问题。如果标准说pink_elephants 关键字的任何使用都应该导致数字 42 的每个实例都被替换为 666,那么就是这样,但我们仍然想知道为什么存在这样一个奇怪的规则。)

【问题讨论】:

  • 您的问题是什么?为什么标准允许合理方便的事情?因为它们既合理又方便。
  • @n.m.我会质疑#3是否合理,因为这可能需要编译器跳过一堆箍以使其工作以换取(IMO)非常小的便利。但如果是的话,那么#1当然也是合理方便的,但它是被禁止的。
  • 它不起作用。如果您使用非内联函数 F(因此不会删除整个内容),您会注意到(使用 gcc)符号 _ZN1C3valE 未定义。
  • 编译器已经必须为模板类的静态成员完成这项工作,因此删除此功能并不能真正为编译器编写者节省任何工作。这与案例#1 的区别在于,在案例#1 中,编译器无法证明val 的多个定义是相同的。
  • 我误解了你的问题。但是,这里没有禁止任何内容,“如果成员被 odr-used,则仍应在命名空间范围内定义”(9.4.2/3)。因此,如果您还没有这样做,那么 Microsoft 编译器会让您侥幸逃脱。

标签: c++ static-members language-lawyer one-definition-rule


【解决方案1】:

您的第一个示例违反了 ODR,因为 在类中声明静态成员不是 一个定义,只是一个声明。静态成员必须是 在单个翻译单元中定义,例如:

S const A::val;

在源文件中(不是标题)。

在 C++11 之前的版本中,当声明为静态时,具有整型 并且是 const,它被允许(作为一个特殊例外) 指定一个初始化器,前提是初始化器是一个常量 积分表达式。然而,即使在这种情况下,你正式 需要一个定义(没有初始化器),并且只有 一个源文件。如果缺少,则结果未定义 行为。 (IIFC,有一个例外:如果变量只是 在需要整数常量表达式的上下文中使用, 不需要定义。)

我认为 C++11 已经扩展了一些,并允许一些 非整数类型以及在类中有一个初始化器 定义。但它仍然需要定义之外的 类。

关于您声称有效的最后一个示例:它是 在 C++11 之前的版本中是合法的,它会导致许多错误 编译器。 (我的印象是 C++11 做到了 合法,并留给编译器生成实例, 但我找不到合适的词。如果它被制作 在 C++11 中合法,那么这只是 VC++2012 的 C++11 功能 实现。)

【讨论】:

  • 糟糕;你对案例 #1 是对的;我编辑了它以包含一个作业,因为这是我的想法。
  • @dlf c++11 如果将 const 替换为 constexpr,则允许 case 1。无论是否合法,这都不是 ODR 违规,因为这里没有定义,只是一个声明,即使有一个初始化程序。
  • @n.m.我对案例二和案例三涉及在类定义中定位成员的 definition 存在(错误)理解。事实上,正如您和 James Kanze 已经明确指出的那样,所发生的一切只是 initialization 的位置仍然只是一个声明。你在问题下面引用的那个认识和标准部分让我明白了。
  • @JamesKanze:根据 n.m 引用的 C++11 草案的 9.4.2/3,您最后一段中的括号注释似乎不正确。问题下方。除非在草稿之后改变?
  • @dlf 在您的任何示例中都没有分配;没有赋值运算符合法的上下文。都只是简单地指定一个静态数据成员的初始化。
猜你喜欢
  • 2013-09-25
  • 2021-11-29
  • 1970-01-01
  • 1970-01-01
  • 2011-03-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-20
相关资源
最近更新 更多