为什么在C中允许未定义的行为

jus*_*urv 6 c undefined-behavior

我最近一直在努力学习C.来自Java,它让我感到惊讶,你可以执行声明为"未定义"的某些操作.

这对我来说似乎非常不安全.我理解程序员不负责执行未定义的操作,但为什么它甚至可以从一开始就被允许?例如,为什么编译器不能捕获超出范围的数组索引,甚至悬挂指针?你最终只能访问你永远不应该访问的内存块,没有明显的理由.

作为比较,Java 更加确定你不会做任何愚蠢的事情,像热饼一样抛出异常.

当然,必须有一个理由允许这样做吗?它是什么?

答案:据我了解,主要原因是表现.此外,Java确实有未定义的行为,尽管没有这样标记.

编辑:限制问题到C

sup*_*cat 6

最初,大多数形式的未定义行为表示某些实现可能捕获的事物,但其他实现可能不会。因为标准的作者无法预测平台在陷入陷阱时可能会做的所有事情(包括,从字面上看,系统发出警报并锁定直到操作员手动清除故障的可能性) ,陷阱的后果超出了 C 标准的管辖范围,因此,从标准的角度来看,几乎每个平台可能导致陷阱的行为都被视为“未定义行为”。

这不应被视为暗示该标准的作者不认为实现在实际情况下应该尝试对此类事情采取明智的行为。例如,C89 标准的作者指出,那个时代的大多数当前系统都会定义以下行为:

/* Assume USmall is half the size of "int" */
unsigned mult(USmall x, USmall y) { return x*y; }
Run Code Online (Sandbox Code Playgroud)

在所有情况下,包括 x 和 y 的数学乘积在 INT_MAX+1 和 UINT_MAX 之间的情况,都等于 (unsigned)x*y;。我认为没有理由相信他们不会预料到这种趋势会持续下去。

不幸的是,一种新的哲学已经变得流行,它基于修正主义的观点,即编译器编写者只在标准未强制要求的情况下支持有用的行为,因为他们太简单而无法做任何其他事情。例如,在 gcc 中,使用优化级别 2 但没有其他非默认选项,上述“mult”例程有时会在乘积介于 0x80000000u 和 0xFFFFFFFFu 之间的情况下生成虚假代码,即使在此类计算将在平台上运行时也是如此。历史上曾起作用。据说这是以“优化”的名义完成的;了解此类技术最终执行的“优化”中有多少实际上是有用的并且无法通过更安全的方式实现,这将是很有趣的。

从历史上看,未定义行为是 C 编译器公开底层平台行为的许可证;在底层平台的行为适合程序员的需求的情况下,这允许程序员的需求以机器代码更有效地表达,而不是一切都必须以标准定义的方式完成。然而,最近它被解释为编译器实现行为的许可证,这些行为不仅与底层平台中的任何内容或与任何合理的程序员期望无关,而且甚至不受时间和因果律的约束。


das*_*ght 5

不允许出现未定义的行为,只是编译器不会捕获未定义的行为。

这里要权衡的是速度和安全性。可以通过花费一些额外的CPU周期来防止多种不确定的行为。

例如,通过使已编译的代码向其中写入零,可以防止在读取已分配但未初始化的内存时发生UB。但是,这会花费您额外的全部写入内存的时间,这完全没有必要。

同样,可以通过检查[]运算符内部的边界来防止读取/写入数组末尾。但是,这将使您在每次访问阵列时花费一些额外的CPU周期。

C ++设计人员认为,拥有速度并允许潜在的UB比强迫每个人为不需要的东西付费要好。但是,这种方法与Java的“编写一次,随处运行”的要求不兼容,因此Java语言的设计人员在几乎所有情况下都坚持要求完全定义的行为。

  • 并非在所有情况下都完全定义行为有点强吗? (2认同)
  • @FISOCPP您能否在我的回答中指出导致您在评论中得出结论的语言,例如,现代编译器不够聪明?我的答案是关于编译器*不愿意*阻止UB的,而不是关于他们*不能*阻止UB的。 (2认同)