【问题标题】:Can statically unreachable calls cause undefined reference errors?静态无法访问的调用会导致未定义的引用错误吗?
【发布时间】:2016-07-30 08:01:18
【问题描述】:

考虑以下代码,它对undefinedFunction 进行了无法访问的调用。

void undefinedFunction();

template <bool b = false>
void foo()
{
  static_assert(b == false);
  if (b)
    undefinedFunction();
}

int main()
{
  foo();
}

GCC 毫无怨言地编译和链接它。使用static_assert,很难看出编译器如何做不同的事情,但是标准对此有什么要说的吗?如果static_assert 被删除怎么办?编译器是否有义务删除分支,或者它实际上是否可以发出无法访问的调用指令,导致链接器抱怨?

【问题讨论】:

  • 在一般情况下确定无法访问的代码并非易事......
  • 删除死代码是优化器的工作,而不是代码生成器。如果你当时没有打开它,你打赌,你会让链接器抱怨。该标准没有规定优化器应该如何完成其​​工作。顺便说一句,很难猜到为什么你自己看不到这个,修改 -O 选项。
  • 我相信这是一个错误。此代码不应编译,因为当模板函数 foo 被实例化时,undefinedFunction 是 odr-used。如果分支不会被评估原因b == false 无关紧要。
  • @Hans,我确实尝试过优化和不优化,并且 gcc 在这两种情况下都成功构建,但你的观点是有道理的。
  • 它可能没有链接 - 但程序格式错误,不需要诊断,所以它看起来也可以正常工作。

标签: c++ c++11 c++14 template-meta-programming


【解决方案1】:

通常,调用函数 odr 会使用它,但有两个例外:如果它是纯虚拟的,或者如果它没有被潜在评估 ([basic.def.odr]/5)。除非它是未计算的操作数或其子表达式 ([basic.def.odr]/2),否则可能会计算表达式。未计算的操作数出现在typeidsizeofnoexceptdecltype,这些都不适用。因此,只要实例化了foo,就可以使用undefinedFunction,它就是这样。 “静态无法访问”的代码也不例外。

【讨论】:

  • 很好的总结,必须定义可能的符号。如果我没记错的话,不可能在每种情况下都无法确定代码是否无法访问,所以它〜无法保证〜
  • 谢谢你,很好的解释 - 希望我能接受两个答案。
  • “通常,调用函数 odr-使用它,但有两个例外:如果它是纯虚拟的”.. 这是错误的。即使是纯的,如果调用也需要定义。这是合乎逻辑的:编译器怎么知道函数的作用,否则呢?
  • @JohannesSchaub-litb 如果所有调用都调度到最终的覆盖器,那么纯虚函数不需要定义。我相信你和我一样清楚这一点。我试图保持答案简短,所以我没有提到纯虚函数以显式限定调用时的例外情况。
  • @Brian 好的,所以你的答案中“调用函数 foo”的意思是“在函数调用中命名函数 foo”。我没有意识到这一点,我很抱歉。但是,对于非纯虚函数,并不是您在表达式中提到它,这使得它需要一个定义:尽管如此,它们仍然需要一个定义。命名不会以任何方式影响这一点。这样,您的措辞使我感到困惑,因为那里的“纯”似乎是说“纯”会影响函数调用中的处理。但它反而影响了对一个整体定义的先验需求。
【解决方案2】:

根据 C++ 标准§3.2/p4 单一定义规则 [basic.def.odr](强调我的)

每个程序都应包含每个非内联的确切定义 在该程序中使用 odr 的函数或变量;没有诊断 需要。 定义可以显式出现在程序中,它可以 可以在标准库或用户定义库中找到,或者(当 适当)它是隐式定义的(见 12.1、12.4 和 12.8)。一个 内联函数应在它所在的每个翻译单元中定义 是 odr 使用的。

模板函数 foo 被实例化 undefinedFunction 是 odr-used(即,需要 undefinedFunction 的定义)。不评估 if 子句并不重要。因此,该程序格式不正确,并且由于不需要诊断,因此它可能会链接,也可能不会链接。

【讨论】:

    【解决方案3】:

    正如其他人评论的那样,两个单独的步骤有助于实现这一点。无法访问的代码可能会被优化器删除,因此永远不会请求undefinedFunction() 的链接。 编译步骤不关心symbols that are not defined(更多关于编译的信息在this community answer)。

    这与static_assert 无关。您可以在模板代码中有未定义的引用,这些引用永远不会被初始化,并且编译成功,因为编译器从不考虑代码,它永远不会发出对链接的要求。

    如果符号通过并在稍后的某个步骤中被请求,则链接将失败。这与使用模板类编译库时发生的情况相同,然后尝试使用具有未显式初始化类的参数类型的模板,您将使用自行编译好的库获得对类型的未定义引用.

    如果您想检查您的编译器是否真的在消除死代码,this answer 详细了解如何在 GCC 中分析死代码。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-27
      • 2012-09-08
      • 2011-04-09
      • 2011-05-15
      • 2014-07-03
      • 2015-06-20
      相关资源
      最近更新 更多