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

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

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

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

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

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

编辑:限制问题到C

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

这不应该被认为暗示标准的作者不相信实施应该在实际中尝试对这些事情做出合理的行为。 例如,C89标准的作者指出,那个时代的大多数现有系统将定义以下行为:

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

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

遗憾的是,基于修正主义的观点,新的哲学已经变得时髦,编者编写者只支持标准未强制要求的有用行为,因为它们太过简单,无法做任何其他事情。 例如,在gcc中,使用优化级别2但没有其他非默认选项,上述“mult”例程有时会在产品介于0x80000000u和0xFFFFFFFFu之间的情况下生成伪代码,即使在运行此类计算的平台上也是如此历史上一直有效。 据说这是以“优化”的名义进行的; 有趣的是,知道有多少“优化”这样的技术最终表现实际上是有用的,而且不可能通过更安全的手段来实现。

从历史上看,Undefined Behavior是C编译器公开底层平台行为的许可证; 在底层平台的行为符合程序员需求的情况下,这使得程序员的要求比机器代码更有效地表达,而不是必须以标准定义的方式完成。 然而,最近,它被解释为编制者实施行为的许可,这些行为不仅与基础平台中的任何事物无关,也与任何合理的程序员期望无关,但甚至不受时间和因果关系法则的约束。

不允许使用未定义的行为,它只是没有被编译器捕获。

这里的权衡取决于速度和安全性。 可以通过几个额外的CPU周期来防止多种未定义的行为。

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

类似地,通过检查[]运算符内的边界,可以防止读/写超过数组的末尾。 但是,这会在每次访问arrays时花费额外的CPU周期。

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

Java有一个运行时环境来照顾你。 这就是为什么在越界时抛出exception的原因 – 这是在编译时无法解决的问题。

当对向量使用at()方法时,在C ++中有运行时边界检查。 它是()与[]运算符的区别