【问题标题】:Strange behavior with constexpr static member variableconstexpr 静态成员变量的奇怪行为
【发布时间】:2018-08-06 00:57:14
【问题描述】:

这是Undefined reference to static constexpr char[][] 的后续问题。

以下程序构建并运行良好。

#include <iostream>

struct A {
   constexpr static char dict[] = "test";

   void print() {
      std::cout << A::dict[0] << std::endl;
   }
};

int main() {
   A a;
   a.print();
   return 0;
}

但是,如果我将 A::print() 更改为:

   void print() {
      std::cout << A::dict << std::endl;
   }

我在 g++ 4.8.2 中收到以下链接器错误。

/tmp/cczmF84A.o:在函数“A::print()”中: socc.cc:(.text._ZN1A5printEv[_ZN1A5printEv]+0xd): 对‘A::dict’的未定义引用 collect2:错误:ld 返回 1 个退出状态

链接器错误可以通过添加一行来解决:

constexpr char A::dict[];

在类定义之外。

但是,我不清楚为什么使用数组的成员之一不会导致链接器错误,而使用数组会导致链接器错误。

【问题讨论】:

  • 因为前者需要一个值,而后者需要一个地址?
  • 我认为两者都是 odr-uses。在dict[0] 案例中,您刚刚(不)走运。
  • clang 也不喜欢您的第一个版本,提供与您的第二个版本相同的未定义参考消息。
  • g++ 似乎在幕后做了一些不该做的有趣事情。

标签: c++ c++11


【解决方案1】:

该标准不要求对未能提供定义的任何诊断。

3.2 一种定义规则[basic.def.odr]

4 每个程序都应包含该程序中 odr 使用的每个非内联函数或变量的准确定义;无需诊断。 [...]

这意味着允许实现优化对此类变量的访问,这就是在您使用 GCC 的第一个案例中发生的情况。

GCC 和 clang 都决定他们更喜欢一致的用户体验,其中有关缺少定义的错误消息不依赖于优化级别。通常,这意味着任何缺少的定义都会导致错误消息。但是,在这种情况下,即使在 -O0 处,GCC 也会进行一些最小的优化,从而避免了错误。

但无论哪种方式,程序都是错误的,因为即使 A::dict[0] 也是 ODR 使用:

3.2 一种定义规则[basic.def.odr]

3 变量 x 的名称显示为潜在求值表达式 exex odr 使用,除非将左值到右值转换 (4.1) 应用到 x 会产生一个常量表达式 ( 5.19) 不调用任何非平凡函数,如果x 是一个对象,ex 是表达式e 的潜在结果集的一个元素,其中左值到右值转换 ( 4.1) 应用于e,或者e 是丢弃值表达式(第5 条)。 [...]

A::dict的使用不涉及左值到右值的转换,它涉及到数组到指针的转换,所以这个异常不适用。

【讨论】:

  • 这可能受益于关于为什么 A::dict[0] 是 odr-use 的讨论。
  • @T.C.它不再是了。
  • @Columbo 感谢您的更新。我正要编辑我的答案,但后来注意到问题被标记为 C++11,所以信息仍然正确。 :)
【解决方案2】:

除了@hvd in his answer提供的信息...

来自 C++ 标准草案 N3337(重点是我的):

9.4.2 静态数据成员

3 如果非易失性const static 数据成员是整数或枚举类型,则其在类定义中的声明可以指定一个brace-or-equal-initializer,其中每个 赋值表达式的初始化子句是一个常量表达式(5.19)。可以在类定义中使用 constexpr 说明符声明文字类型的 static 数据成员;如果是这样,它的声明应指定一个 brace-or-equal-initializer,其中作为 assignment-expression 的每个 initializer-clause 都是一个常量表达式。 [ 注意: 在这两种情况下,成员都可能出现在常量表达式中。 — 结束说明 ] 如果该成员在程序中被 odr-used (3.2) 使用,则该成员仍应在命名空间范围内定义,并且命名空间范围定义不应包含 initializer.

鉴于A::data 是在表达式A::data[0] 中使用的,根据标准,它应在命名空间范围内定义。 g++ 能够成功创建程序而无需在名称空间范围内定义 A::data 的事实并不能使程序正确。为了符合标准,应定义A::data

【讨论】:

  • 呵呵,你是对的,还有那个。这没有说“不需要诊断”,所以你甚至可以合理地争辩说 GCC 是错误的,因为未能诊断出违反该规则的情况。 (但更有可能的是,这在措辞中是一个小问题,并不意味着需要进行诊断。)
  • @hvd,我认为“不需要诊断”仍然适用,因为它引用了“odr-used (3.2)”,
  • 是的,但是 odr-used 只是指 [3.2]p3,它指定哪些变量是 odr-used,而不是 [3.2]p4,它需要定义那些被 odr-used 的变量。 [3.2]p3 是“odr-used”以斜体字出现的地方,这种斜体字的出现意味着它定义了“odr-used”一词的含义。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-10-28
  • 2019-08-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-25
相关资源
最近更新 更多