是增量/减量运算符在Bash中未定义的行为?

在标准C99和C11中,像下面这样的expression式是UB(未定义行为):

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)似乎从左到右的评估是执行,增量立即完成操作符++出现。

你似乎正在寻求为bash找到一个明确的标准,就像C有。不幸的是,没有一个。

实际上只有两个指南:

  1. Open Group Base Specifications 第3卷(Shell and Utilities) ,通常被称为“Posix”,这是故意不明确的。 它确实指出算术评估“等同于ISO C标准第6.5节”表达式“中描述的算术评估”,但是该标准没有为包含同一个变量的增变和其他用途的表达式指定任何值(例如作为x + x++x++ + x++ )。

  2. bash的特定版本的实际行为和手册,既不符合正式的规范,也没有任何标准组织认证或祝福。

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

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

当然,没有任何法律要求每一种编程语言都有完整的规定,而且很多都不是。 事实上,虽然C和C ++都以ISO标准的形式享有详尽的定义,但是这些标准故意没有定义许多用法,部分原因是因为这样做会要求形式的错误检测,这会妨碍性能。 这些决定已经并将继续引起争议,我也无意支持。

我只是简单地观察一下,在正式的规范中,下面的内容是一样的:

  • 将评估特定程序结构的结果作为错误进行检测的要求

  • 显式声明特定构造的值是实现定义的

  • 未能指定某个特定构造的值,或者未明确指明该构造的明确声明。

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

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

第三个不能准确地定义它所描述的结构的价值,虽然它通常会提供一个可能性列表。 第四个是C / C ++专业,它规定构造是无效的,程序员有责任避免这个构造,因为这个标准对实现没有任何要求 。 永远不要使用这样的构造。

从具体规格中抽取的四起案件的例子:

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

  • 实现特定的。 (Posix shell)“打开的文件以十进制表示,从零开始,最大的可能值是实现定义的 ;但是,所有的实现应该至少支持0到9(包括0和9),以供应用程序使用。

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

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

最后两类似乎引起了某些程序员的愤怒。 难以置信的是,说明书留下的行为可能是不确定的,即使没有说明书,也是某种隐藏真相(因此必须是自由的)的阴谋。 这反过来导致随机的实验与特定的语言实现,这必然是徒劳的,因为标准并没有约束所有的实现做同样的事情,甚至一个特定的实现一致地做同样的事情。

避免将“未定义的行为”视为特定的行为也很重要。 如果使用UB进行计算具有特定行为,则不会是未定义的 。 即使计算有一系列可能的具体行为,也只是没有具体说明。

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

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

看看bash代码(bash 4.3),source 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); 

恕我直言,与该代码,以及事实上,一条线是从左到右的令牌处理令牌,我可以说,行为是很好的定义。