【问题标题】:Infinite loop vs infinite recursion. Are both undefined?无限循环与无限递归。两者都未定义吗?
【发布时间】:2019-09-08 02:51:53
【问题描述】:

没有副作用的无限循环是未定义的行为。请参阅 here 以获取来自 cppreference 的示例。更简单的例子:

int foo() {
    while(true) {}
    return 42;
}

现在考虑几乎等价的

int bar() {
    if (true) return bar();
    return 42;
}

这是否也会引发未定义的行为?

或者换种说法:根据语言,无限递归属于哪一类错误?

PS:请注意,我知道运行时的含义:循环原则上可以永远运行,而递归最终会导致堆栈溢出。虽然我主要对编译器对它们做了什么感兴趣。也许是一个相当学术的问题......

【问题讨论】:

  • “循环原则上可以永远运行,而递归最终会导致堆栈溢出。” - 实际上,因为它是UB,它可以做任何事情。由于 as-if 规则,它不必首先在实际硬件上递归。特别是,如果启用了优化,编译器可能会用有效的循环(尾调用优化)替换您的递归示例,即使它有副作用。
  • @ArneVogel 是的,代码是人为设计的,但在实践中,大多数 C++ 代码没有具有非平凡 dtors 的对象,并且大多数时候无法进行尾部优化
  • @ArneVogel 将“原则上”读作“如果不是 ub 会发生什么”
  • @user463035818:我会说如果不是 UB 我会说一个实现应该避免执行任何如果省略循环就不会发生的操作,或者任何操作它将对循环中的任何内容具有不可提升的数据依赖关系,但可能会执行如果省略循环会发生的任何操作,除非或直到它遇到不可提升的数据依赖关系。

标签: c++ loops recursion language-lawyer undefined-behavior


【解决方案1】:

不,没有区别。 [basic.progress]p1:

实现可能假设任何线程最终都会执行以下操作之一:

  • 终止,

  • 调用库 I/O 函数,

  • 通过易失性左值执行访问,或

  • 执行同步操作或原子操作。

无限循环的方式并不重要;如果它没有做到以上任何一点,你就会得到 UB。包括以下内容:

int bar(int cond) {
    if (cond == 42) bar(cond);
    return 42;
}
bar(some_user_input);

允许编译器假设 some_user_input 永远不会是 42。

【讨论】:

  • @Jean-BaptisteYunès 它在“可能假设”部分。如果您的程序不这样做,那么您就违反了编译器的假设,这与 UB 相同。确实如此,但这只是可能发生的事情的一个例子。
  • 我有点困惑。天真地说:编译器知道我的stacksize是有限的,无限递归会导致栈溢出,线程会终止,一切正常。我哪里错了?
  • @user463035818 在 C++ 抽象机中没有堆栈大小。这是实现定义的限制之一,但如果有意义的话,它不算作程序终止。
  • @Rakete1111 你想告诉我stackoverflow 不是真的吗? :P 说真的,就 c++ 语言而言,stackoverflow 是什么?它根本不存在,因此没有定义?
  • @user463035818 是的,这不是语言所关心的。但不一定是未定义的,只是抽象机器中不存在这个概念。
【解决方案2】:

在大多数现实世界的情况下,假设某事会发生的邀请意味着一个人没有义务采取任何特殊措施来允许它不会发生的可能性,但不是许可以任意方式行事,如果人们发现所讨论的事件不会发生。

然而,根据 C 标准的流行解释,如果标准会邀请他们做出的任何假设被证明是不正确的,那么没有理由期望实现会避免完全荒谬的时尚。

我不认为标准的作者打算让 C 编译器使用复杂的推理引擎,将假设级联到编译器给出的程度,例如

if (x > 1000) foo(x);
while ( x <= 1000)
  somethingWithNoSideEffectsThatCantAffectX();

会得出结论,无论x 的值如何,都应该无条件执行foo。更有可能的是,他们打算让编译器给出如下内容:

volatile int yy;
void test(int x, int *restrict arr)
{
  int y;
  do
    x=arr[x];
  while(x & 1);
  y=yy;
  if (y)
    printf("%d\n",x);
}

将被允许使用 x 的最终值可能需要也可能不需要重新排列代码以在不需要的情况下更有效的事实(通过完全跳过其计算)。从本质上讲,一段代码需要时间来执行的事实——即使无限——本身不应被视为副作用,并且如果编译器可以证明一段代码代码在到达某个位置之前无法执行任何可见的副作用,它不需要等待那段代码完成后才在该位置​​执行代码。

不幸的是,标准的作者依靠编译器作者来识别基于某些假设可能有用的操作,而编译器作者假设他们可以得出的任何可能的推论都应该被假定为有用。

【讨论】:

    猜你喜欢
    • 2016-10-06
    • 1970-01-01
    • 2014-10-13
    • 2020-02-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-31
    • 1970-01-01
    相关资源
    最近更新 更多