【问题标题】:Which languages have "documented undefined behavior"?哪些语言“记录了未定义的行为”?
【发布时间】:2019-10-23 00:31:55
【问题描述】:

标签的detailed explanation 开头非常合理:

在计算机编程中,未定义的行为(非正式地“UB”)是指 其行为未由程序指定的计算机代码 特定条件下的语言标准。

然后说(强调我的):

关于各种形式的记录的未定义行为的问题 在给定的编程语言中。

如何“记录”“未指定”的“行为”?

什么是“记录在案的未定义行为”以及哪些语言拥有这种野兽

[补充说明:

不同的人对前面引用的文本有完全不同的理解,甚至是相反的意思,所以这个问题本质上变成了对文本的澄清和重写的请求,这自然适合元.但是问题不是主要是要求更改标签解释,而是编程语言问题,因此故意不在meta上发布

(注意:我已经多次在问题和答案中提到过该文本的问题,但每次我的 cmets 都以非常严厉的方式被删除。)]

编辑

请编辑以详细解释您的问题部分 独一无二的

独特的部分又是:

  • 如何同时“记录”“未定义的行为”?
  • 什么是“记录在案的未定义行为”?
  • 哪些语言“记录了未定义的行为”?

链接的问题都没有回答这些问题; 记录的未定义行为这些词甚至没有一起出现

如果我遗漏了什么,具体指出解释这些内容的答案会很有帮助

如果关于 UB 的另一个问题、答案或讨论将被删除,我会感到难过,因为它指向标签描述中的不一致。

【问题讨论】:

标签: undefined-behavior language-agnostic language-lawyer undefined-behavior


【解决方案1】:

我是在 wiki 中编写该文本的人。我所说的“记录在案的未定义行为”是指语言标准中形式上未定义的行为,但在现实世界中却得到了完美的定义。没有一种语言“记录了未定义的行为”,但现实世界并不总是关心语言标准的内容。

一个更好的术语可能是非标准语言扩展,或者如果您将“就编程语言标准而言未定义”。

在语言标准中某些东西可以被视为未定义的行为有几个原因:

  1. 有些东西完全超出了标准的范围。例如内存、监视器等的行为。标准中未记录的所有行为在理论上都是未定义的。
  2. 在给定的特定硬件上,某些东西实际上是明确定义的,但标准不希望对系统/硬件施加限制,因此任何技术都不会获得不公平的市场优势。因此,它标记了一些未定义的行为,即使它不是在实践中。
  3. 即使在硬件中也确实是未定义的行为,或者在任何情况下都没有任何意义。

1)的例子
变量存储在内存中的什么位置?这超出了标准的范围,但在执行程序的任何计算机上都得到了完美的定义。

同样,如果我说“我的猫是黑色的”,这是未定义的行为,因为编程语言没有涵盖猫的颜色。这并不意味着我的猫会突然开始在神秘色彩的万花筒中闪闪发光,而是现实优先于理论编程标准。我们可以完全确定特定的猫将永远是一只黑猫,即使它是未定义的行为。

示例 2)
有符号整数溢出。整数溢出的情况在 CPU 级别上是完全明确的。在大多数情况下,值本身将被视为简单的无符号加法/减法,但会在状态寄存器中设置溢出标志。就 C 或 C++ 语言而言,这种溢出理论上可能会导致可怕的、无法解释的事件发生。但实际上,底层硬件会产生完美定义的结果。

3)的例子
被零除。访问无效地址。堆栈溢出时的行为。等等。

【讨论】:

  • 如果该类中的某些特定操作应预期行为可预测,但其他行为则不可预测,以及应预期行为可预测的情况范围,则一般行为类别也可归类为未定义行为可能至少部分取决于标准管辖范围之外的因素。当x 为负时考虑x << y。 C99 将其重新分类为 UB,但没有给出理由;唯一有意义的方法是,如果他们期望实现会像在 C89 中那样处理它,而没有充分的理由这样做,即使不是必需的。
  • 此外,该标准认为优化不得影响可观察的行为,除非程序调用 UB,而不是认识到具有可能被优化改变的松散定义的行为的价值,但不要完全跳过铁轨。例如,如果整数溢出定义为换行,x=y*30/15; 替换为x=y*2; 将是可观察到的。如果代码在溢出场景中不关心 x 的值,则允许在没有副作用的情况下计算该值可以避免需要其他不必要的逻辑来阻止计算。
  • 在任何情况下,描述您的意思的最清楚的方式可能是观察标准明确指出,当它将动作描述为未定义的行为时,实现可能会“以记录的方式特征处理它环境”,这将使此类行为真正成为“记录在案的未定义行为”。
【解决方案2】:

C 和 C++ 相当独特,因为“官方”C 标准是在该语言已经投入使用很久之后编写的,甚至在出版的书籍中都有描述。有许多情况,例如整数溢出,一些实现会以记录在案的可预测方式处理,但其他实现则不能便宜地做到这一点。该标准将此类事物视为“未定义的行为”,明确指出实现可以(但不是必需)以环境的文档化方式特征处理它们。请注意,这可能会导致在某些环境中,保证任何类型的一致行为可能会很昂贵,并且尽管成本很高,但许多程序可能无法提供此类保证。

考虑一下,例如:

extern volatile int someFlag;
void test(int x, int y)
{
  int z;
  someFlag = 1;
  z=x+y;
  someFlag = 0;
  if (f2())
    f3(x,y,z);        
}

是否应允许溢出引发信号的实现将代码更改为:

extern volatile sig_atomic_t someFlag;
void test(int x, int y)
{
  someFlag = 1;
  someFlag = 0;
  if (f2())
    f3(x,y,x+y);
}

这样做可以避免在调用f2() 时将x+y 的值保存在内存中,并且可能避免完全计算它的需要。 除非someFlag 会以代码所依赖的方式影响整数溢出信号的行为。如果标准将整数溢出描述为“定义的实现”,那么实现在没有上述优化的情况下按照标准的要求记录溢出行为会很尴尬,即使出于许多目的保证添加会在之前执行调用f2 会增加成本,但不会增加任何价值。

标准的作者没有试图担心是否应该允许或禁止此类优化,而是选择将整数溢出描述为未定义行为,允许记录其行为的实现继续这样做,但不要求实现悲观地假设任何可能的副作用都可能以他们不知道的方式观察到。在编写标准之前,实现记录的任何行为都将是记录的行为,而标准将行为描述为未定义这一事实并不打算改变这一点。

从那时起,有许多缺陷报告错误地将各种符合但严格符合的构造描述为“不符合”,这导致错误地认为标准中的术语“X 未定义”等同于“X 被禁止”。其他语言规范在区分被禁止但必须诊断的构造、被禁止但可能不总是被诊断的构造、预期行为部分但不完全一致的构造以及行为将在不同的实现有不同的一致方式,但原始 C 和 C++ 标准的作者将这些事情留给实现者判断。

【讨论】:

  • “从那时起,有许多缺陷报告错误地将各种符合但不严格符合的构造描述为“不符合”,这导致错误地认为该术语标准中的“X 未定义”等价于“X 被禁止”。 -- 你有这样的缺陷报告的例子吗?
【解决方案3】:

当我阅读它时,“记录的未定义行为”并不意味着“既是(未定义的又记录的)行为”。它的意思是“记录在案的(未定义的行为)”。他甚至举了一个例子:

例如,如果在编译期间知道数组索引,则编译器可能会诊断出超出 C 中数组的最后一个元素的访问,或者可能会从未初始化的内存中返回垃圾值,或者返回明显合理的值,或者导致程序通过访问进程数据地址空间之外的内存而崩溃。

未定义的行为是“访问 C 中数组的最后一个元素”。 C 语言说,这是未定义的。然而,当你进入语言的这个“未定义”区域时,他和其他人已经记录了实际发生的事情。

所以有两个级别记录了这种未定义的行为。

1) 已识别。 “C 没有定义当你越过数组末尾时会发生什么”。现在您知道这是未定义的行为。

2) 它被探索。 “当你这样做时,会发生一些事情。”

也许作者的意思是 1 或意思 2。或者其他的意思。但我认为你所追求的意思可能是一个与我不同的阅读短语的人工制品。

【讨论】:

  • 然后有一个问题,即“工件”将被某些人识别为模棱两可,而其他人只会将其读取为(2)甚至“定义未定义”并尝试理解它.或者有些人会读到识别的UB有点不同甚至比未识别的UB重要的潜台词。 或者 diff ppl 会同意它是清晰明确的并采取不同的方式。
  • 还有第三种更常见的可能性:标准的部分内容,结合实现的文档和/或 K&R 的 C 编程语言,描述某些操作的行为,但其他一些部分将一组重叠的动作描述为调用未定义的行为。请注意,该标准没有努力区分在某些系统上通常应该可预测但在其他系统上不能预测的操作与那些不应该在任何系统上表现有用的操作。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-01-21
  • 1970-01-01
  • 1970-01-01
  • 2010-12-14
  • 2016-02-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多