为什么不通过获取内联定义的静态const积分成员变量的地址来违反ODR?

时间:2014-05-27 13:54:58

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

如果编译了这样的话,那么显然会违反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是整数类型的编译时常量,所以编译器基本上可以搜索和替换对{{1的所有引用使用文字B::val(对反汇编的检查表明这正是它的作用)。因此,在某个意义上,最终产品中3定义,因此ODR不适用。但是,请考虑一下:

B::val

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

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

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

1 个答案:

答案 0 :(得分:3)

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

S const A::val;

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

在pre-C ++ 11中,当声明是静态的时,有整数类型 并且是const,它被允许(作为特殊例外) 如果初始值设定项是常量,则指定初始值设定项 积分表达。然而,即使在这种情况下,你也是正式的 需要定义(没有初始化器)在一个,只有 一个源文件。如果它丢失了,结果是不确定的 行为。 (IIFC,但有一个例外:如果变量只是 用于需要整数常量表达的上下文中, 不需要定义。)

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

关于你声称有效的最后一个例子:它是 在预先C ++ 11中合法,并且它会导致很多错误 编译器。 (我的印象是C ++ 11成功了 合法的,并将其留给编译器来生成实例, 但我无法找到合适的词语。如果是的话 在C ++ 11中合法,那么这只是VC ++ 2012的C ++ 11特性 实现。)