increment/decrement operators 是 Bash 中的未定义行为吗?
Is increment/decrement operators Undefined Behaviour in Bash?
在标准 C99 和 C11 中,如下所示的表达式是 U.B。 (未定义的行为):
int x = 2;
int ans = x++ + x++;
在Bash中定义了increment/decrement运算符,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 代码 (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 */
}
如您所见,post 增量未使用 C post-增量实现:
v2 = val + ((stok == POSTINC) ? 1 : -1);
恕我直言,使用该代码,以及从左到右逐个标记处理一行的事实,我可以说行为定义明确。
您似乎正在寻求为 bash 找到定义的标准,就像 C 一样。不幸的是,没有。
真的只有两个指南:
Open Group Base Specifications 的Volume 3 (Shell and Utilities),俗称"Posix",故意未指定。它确实声明了算术评估 "be equivalent to that described in Section 6.5, Expressions, of the ISO C standard,",但该标准没有为包含增变符和同一变量的另一种使用(例如 x + x++
或 x++ + x++
)的表达式指定任何值。
bash特定版本的实际行为和手册,两者都不符合正式规范,也都不是以任何方式获得任何标准组织的认证或认可。
因此,我不得不说,没有文档定义 bash 中对 x++ + x++
等表达式的算术求值结果。即使当前 bash 版本的 bash 手册指定了它(它没有),即使有可能从当前 [=124] 的源代码检查中推断出行为=] 版本(确实如此,但不一定容易),从任何意义上讲都不是正式规范。
这使得结果 "undefined" 在单词的直观意义上:没有定义结果。
当然没有法律要求完全指定每种编程语言,而且很多都没有。事实上,虽然 C 和 C++ 都享有 ISO 标准形式的详尽定义,但标准有意未定义许多用法,部分原因是这样做需要各种形式的错误检测,这会影响性能。这些决定一直存在并将继续存在争议,我无意偏袒任何一方。
我会简单地观察一下,在正式规范的上下文中,以下是不相同的:
要求对特定程序构造的评估结果被检测为错误。
明确声明特定构造的值是实现定义的。
未能指定特定构造的值,或未指定的明确声明。
明确声明对特定构造求值的结果是未定义。
前两个绝对是正式规范,因为它们暗示评估结果是明确定义的(在第二种情况下,定义 should/must 出现在实施手册中)。这样的构造肯定是可用的,尽管特定于实现的构造当然会使程序不可移植。
第三个没有准确定义它所描述的构造的值,尽管它通常会提供一个可能性列表。第四个是 C/C++ 特长,指定构造无效,程序员有责任避免它,因为标准对 没有任何要求一个实现。永远不应使用此类结构。
具体规范中的四种情况示例:
错误检测。 (Java)"if the value of the divisor in an integer division is 0, then an ArithmeticException is thrown."
特定于实现。 (Posix shell) "Open files are represented by decimal numbers starting with zero. The largest possible value is implementation-defined; however, all implementations shall support at least 0 to 9, inclusive, for use by the application."
未指定的行为。 (C/C++) "An example of unspecified behavior is the order in which the arguments to a function are evaluated."(来自 C99 标准的定义部分)。
未定义的行为 (C) "In both operations [/ and %], if the value of the second operand is zero, the behavior is undefined."(与上面的 Java 对比。)
最后两个类别似乎在某些程序员中引发了一种愤怒;不相信规范可能会留下未指定的行为,甚至没有规范是某种隐藏真相的阴谋(因此必须释放)。这反过来又会导致对特定语言实现的随机实验,这肯定是徒劳的,因为标准不会绑定所有实现来做同样的事情,甚至不会让特定的实现始终如一地做同样的事情。
避免将 "undefined behaviour" 视为特定行为也很重要。如果使用 UB 的计算具有特定行为,则它不会是 undefined。即使计算有一系列可能的特定行为,也只是未指定。
"Undefined behaviour" 不是规范或属性。您无法检测到 "undefined behaviour",因为缺少定义意味着任何行为都是可能的,包括作为其他构造的定义结果的行为。
特别是,"undefined behaviour" 与检测到的错误不同,因为实施没有义务检测它。
在标准 C99 和 C11 中,如下所示的表达式是 U.B。 (未定义的行为):
int x = 2;
int ans = x++ + x++;
在Bash中定义了increment/decrement运算符,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 代码 (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 */
}
如您所见,post 增量未使用 C post-增量实现:
v2 = val + ((stok == POSTINC) ? 1 : -1);
恕我直言,使用该代码,以及从左到右逐个标记处理一行的事实,我可以说行为定义明确。
您似乎正在寻求为 bash 找到定义的标准,就像 C 一样。不幸的是,没有。
真的只有两个指南:
-
Open Group Base Specifications 的
Volume 3 (Shell and Utilities),俗称"Posix",故意未指定。它确实声明了算术评估 "be equivalent to that described in Section 6.5, Expressions, of the ISO C standard,",但该标准没有为包含增变符和同一变量的另一种使用(例如
x + x++
或x++ + x++
)的表达式指定任何值。bash特定版本的实际行为和手册,两者都不符合正式规范,也都不是以任何方式获得任何标准组织的认证或认可。
因此,我不得不说,没有文档定义 bash 中对 x++ + x++
等表达式的算术求值结果。即使当前 bash 版本的 bash 手册指定了它(它没有),即使有可能从当前 [=124] 的源代码检查中推断出行为=] 版本(确实如此,但不一定容易),从任何意义上讲都不是正式规范。
这使得结果 "undefined" 在单词的直观意义上:没有定义结果。
当然没有法律要求完全指定每种编程语言,而且很多都没有。事实上,虽然 C 和 C++ 都享有 ISO 标准形式的详尽定义,但标准有意未定义许多用法,部分原因是这样做需要各种形式的错误检测,这会影响性能。这些决定一直存在并将继续存在争议,我无意偏袒任何一方。
我会简单地观察一下,在正式规范的上下文中,以下是不相同的:
要求对特定程序构造的评估结果被检测为错误。
明确声明特定构造的值是实现定义的。
未能指定特定构造的值,或未指定的明确声明。
明确声明对特定构造求值的结果是未定义。
前两个绝对是正式规范,因为它们暗示评估结果是明确定义的(在第二种情况下,定义 should/must 出现在实施手册中)。这样的构造肯定是可用的,尽管特定于实现的构造当然会使程序不可移植。
第三个没有准确定义它所描述的构造的值,尽管它通常会提供一个可能性列表。第四个是 C/C++ 特长,指定构造无效,程序员有责任避免它,因为标准对 没有任何要求一个实现。永远不应使用此类结构。
具体规范中的四种情况示例:
错误检测。 (Java)"if the value of the divisor in an integer division is 0, then an ArithmeticException is thrown."
特定于实现。 (Posix shell) "Open files are represented by decimal numbers starting with zero. The largest possible value is implementation-defined; however, all implementations shall support at least 0 to 9, inclusive, for use by the application."
未指定的行为。 (C/C++) "An example of unspecified behavior is the order in which the arguments to a function are evaluated."(来自 C99 标准的定义部分)。
未定义的行为 (C) "In both operations [/ and %], if the value of the second operand is zero, the behavior is undefined."(与上面的 Java 对比。)
最后两个类别似乎在某些程序员中引发了一种愤怒;不相信规范可能会留下未指定的行为,甚至没有规范是某种隐藏真相的阴谋(因此必须释放)。这反过来又会导致对特定语言实现的随机实验,这肯定是徒劳的,因为标准不会绑定所有实现来做同样的事情,甚至不会让特定的实现始终如一地做同样的事情。
避免将 "undefined behaviour" 视为特定行为也很重要。如果使用 UB 的计算具有特定行为,则它不会是 undefined。即使计算有一系列可能的特定行为,也只是未指定。
"Undefined behaviour" 不是规范或属性。您无法检测到 "undefined behaviour",因为缺少定义意味着任何行为都是可能的,包括作为其他构造的定义结果的行为。
特别是,"undefined behaviour" 与检测到的错误不同,因为实施没有义务检测它。