【问题标题】:Is increment/decrement operators Undefined Behaviour in Bash?Bash 中的递增/递减运算符是未定义的行为吗?
【发布时间】:2015-01-05 02:46:40
【问题描述】:

在标准 C99 和 C11 中,如下表达式是 U.B. (未定义的行为):

 int x = 2;
 int ans = x++  +  x++;  

在 Bash 中定义了递增/递减运算符,gnu.org 中的官方文档说遵循标准 C 的约定。

此外,Bash 主要符合 POSIX 标准,并且在其标准文档 (http://pubs.opengroup.org/onlinepubs/9699919799/) 中说,对于算术运算,除非另有说明,否则假定为 C 标准。

由于我找不到更多信息,我的结论是在 Bash 中我们也有带有增量运算符的未定义行为:

x = 2
echo $(( x++ + x++ ))

我需要确定我的结论是否正确,或者相反,Bash 中是否存在某种取代 C 标准的约定。

附加说明:在我的系统中尝试(Ubuntu 14.04,Bash 版本 4.3.11)似乎执行了从左到右的评估,并在操作员 ++ 处立即执行增量出现。

【问题讨论】:

  • 无论$(( x++ + x++ )) 是什么意思,我保证有一种更清晰、更安全的方式来表达意图。
  • @KeithThompson:我有兴趣了解 Bash 在算术扩展中的规则。是可移植代码吗?我正在尝试了解 Bash 正式规范。
  • Bash 没有正式的规范。我并不是暗示这个问题是重复的,因为它特定于算术评估;然而,在更一般的意义上this has been asked before.
  • @kojiro:在您提供的链接中,在标题评估顺序下,您的答案是Bash interprets expressions in its primary syntax from left to right。该意见是否基于第 3.7.1 节。的Bash reference manual?
  • @R..:不过,这是一个有效的问题。了解规则(如果有的话)很有用。

标签: c linux bash posix post-increment


【解决方案1】:

您似乎正在寻求为 bash 找到一个已定义的标准,就像 C 一样。不幸的是,没有一个。

实际上只有两个指南:

  1. Volume 3 (Shell and Utilities) 的 Open Group Base Specifications,通常称为“Posix”,故意未指定。它确实声明算术评估“等同于 ISO C 标准的第 6.5 节,表达式中描述的内容”,但该标准没有为包含 mutator 和同一变量的另一种用法的表达式指定任何值(例如如x + x++ 或x++ + x++)。

  2. bash 的特定版本的实际行为和手册,它们都没有资格作为正式规范,也没有任何标准组织以任何方式认证或祝福.

因此,我不得不说,没有文档定义像 x++ + x++ 这样的表达式的算术评估 bash 中的结果。即使当前 bash 版本的 bash 手册指定了它(它没有),即使可以通过检查当前 bash 版本的源代码来推断行为(它是,但不一定容易),这在任何意义上都不是正式的规范。

这使得结果在直观意义上“未定义”:没有定义结果。

当然没有法律要求完全指定每种编程语言,而且很多都没有。事实上,虽然 C 和 C++ 都以 ISO 标准的形式进行了详尽的定义,但标准故意没有定义许多用法,部分原因是这样做需要进行错误检测,这会妨碍性能。这些决定已经并将继续存在争议,我无意偏袒任何一方。

我将简单地观察到,在正式规范的上下文中,以下内容不相同:

  • 要求评估特定程序构造的结果被检测为错误。

  • 明确声明特定构造的值是实现定义的。

  • 未能指定特定构造的值,或明确声明它未指定。

  • 明确声明评估特定构造的结果是未定义。

其中前两个绝对是正式的规范,因为它们暗示评估的结果是明确定义的(在第二种情况下,定义应该/必须出现在实施手册中)。这样的构造肯定是可用的,尽管特定于实现的构造当然会使程序不可移植。

第三个没有准确定义它所描述的结构的价值,尽管它通常会提供一个可能性列表。第四个是 C/C++ 的专长,它指定构造无效并且程序员有责任避免它,因为标准对实现没有任何要求。绝不应使用此类构造。

取自具体规范的四种情况示例:

  • 错误检测。 (Java)“如果整数除法中除数的值为0,则抛出ArithmeticException。”

  • 特定于实现。 (Posix shell)“打开的文件由从零开始的十进制数字表示。最大可能值是实现定义的;但是,所有实现都应支持至少 0 到 9,包括在内,以供申请。”

  • 未指定的行为。 (C/C++)“未指定行为的一个例子是评估函数参数的顺序。” (来自 C99 标准的定义部分)。

  • 未定义行为 (C) “在两个操作 [/ 和 %] 中,如果第二个操作数的值为零,则行为未定义。 " (与上面的 Java 对比。)

最后两个类别似乎在某些程序员中引发了一种愤怒;不相信规范有可能未指明行为,甚至不相信规范的缺失是某种隐瞒真相的阴谋(因此,必须释放真相)。而这反过来又会导致对特定语言实现的随机实验,而这肯定是徒劳的,因为该标准并未绑定所有实现来做同样的事情,甚至没有让特定实现始终做同样的事情。

避免将“未定义的行为”视为特定行为也很重要。如果使用 UB 的计算具有特定的行为,它就不会是undefined。即使计算具有一系列可能的特定行为,它也只是未指定的。

“未定义的行为”不是规范或属性。您无法检测到“未定义的行为”,因为缺乏定义意味着任何行为都是可能的,包括作为其他构造的定义结果的行为。

特别是,“未定义的行为”不与检测到的错误相同,因为实现没有义务检测它。

【讨论】:

  • 因此,在您看来,关于算术运算中的副作用,人们必须考虑 Bash 从 ISO C 继承了“未定义行为”属性,除非另有说明。对吗?
  • @pablo1977:很明显,Posix 没有指定算术表达式的求值顺序,并且 Posix 没有限制算术求值在求值过程中分配变量(它只要求它们在“扩张”)。就 bash 试图成为一个符合 Posix 的 shell 而言,它继承了缺乏特异性。给出的唯一指南是结果与标准 C/6.5 的结果“等效”,它确实指定 i + i++ 未定义(以及 i++ + i++)。什么等同于未定义的行为?我会说它确实是未定义的。
  • @pablo1977:但话虽如此,“未定义的行为”不是一个属性。这是一个行为未定义的声明,仅此而已。您无法检测到“未定义的行为”,因为缺乏定义意味着任何行为都是可能的,包括作为其他构造的已定义结果的行为。
  • 对不起,使用了“属性”这个词,我不知道如何更好地表达它。您在这里对我自己的评论的评论(关于 Posix 的所有内容)可能会成为您答案的一部分,因为这是我在主要问题中寻找的具体结论。
  • @pablo1977:我在答案中添加了 Posix 引用。我的观点是,一般来说,说 C/C++ 标准“指定 [某事] 的结果是未定义的行为”是一种误导(尽管很常见),因为这往往会具体化 UB。最好说 C/C++ 标准“没有定义 [某事] 的后果,允许任何行为产生结果”。
【解决方案2】:

查看 bash 代码(bash 4.3),来源 expr.c,我看到以下内容:

      /* post-increment or post-decrement */
      if (stok == POSTINC || stok == POSTDEC)
        {
          /* restore certain portions of EC */
          tokstr = ec.tokstr;
          noeval = ec.noeval;
          curlval = ec.lval;
          lasttok = STR;    /* ec.curtok */

          v2 = val + ((stok == POSTINC) ? 1 : -1);
          vincdec = itos (v2);
          if (noeval == 0)
        {
#if defined (ARRAY_VARS)
          if (curlval.ind != -1)
            expr_bind_array_element (curlval.tokstr, curlval.ind, vincdec);
          else
#endif
            expr_bind_variable (tokstr, vincdec);
        }
          free (vincdec);
          curtok = NUM; /* make sure x++=7 is flagged as an error */
        }

如你所见,后置增量不是用 C 后置增量实现的:

v2 = val + ((stok == POSTINC) ? 1 : -1);

恕我直言,使用该代码,以及从左到右逐个标记处理一行的事实,我可以说行为定义良好。

【讨论】:

  • 感谢您的回答,但这不取决于选择实现 Bash 的方式吗?在未来的版本中,人们可以更改实现。另一方面,您所说的令牌处理从左到右的顺序是一个很好的观点。
  • @pablo1977 我不明白指出对实现的依赖的意义。这似乎是同义反复。虽然 bash(与 XCU 相对)没有国际标准,但 bash 通常遵循或扩展 POSIX、IEEE 及其扩展中的 sh 的现有标准。在这种情况下,请参阅 操作数 here。我想说的是,似乎任何人都不太可能以破坏这种行为的方式更改 bash。
  • 与其说是v2 的设置,不如说是变量在评估时(对expr_bind_variable 的调用)反弹到新值。 PREINC 和 PREDEC 也会发生这种情况。在 C 中,++ 位可以被推迟,但在bash 中并非如此,所以这个答案是正确的。无论如何,bash 的“标准”是实际的源代码 :-)
  • @kojiro:那么,在你看来,我必须接受 j.a. 的回答吗?你们都同意他的观点,所以这似乎是正确的答案。
  • @paxdiablo:Posix 说“对算术表达式中变量的所有更改都应在算术扩展后生效”,这与 C 标准大致相同;即它允许将实际修改推迟到整个表达式的评估之后。事实上,bash 的当前实现并没有推迟分配,但我看不出它如何绑定未来版本的 bash(或者,就此而言,任何 Posix shell)。
猜你喜欢
  • 2010-12-01
  • 1970-01-01
  • 2011-02-16
  • 1970-01-01
  • 2015-10-03
  • 2014-03-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多