我最近一直在努力学习C语。来自Java,它让我感到惊讶,你可以执行声明为" undefined"的某些操作。
这对我来说似乎非常不安全。我理解程序员有责任不执行未定义的操作,但为什么甚至允许它开始?例如,为什么编译器不能捕获超出范围的数组索引,甚至悬挂指针?你最终只能访问你永远不应该访问的内存块,没有明显的理由。
作为比较,Java让更加确定你不会做任何愚蠢的事情,像热点一样抛出异常。
肯定有理由允许这样做吗?它是什么?
答案:据我了解,主要原因是表现。此外,Java确实有未定义的行为,尽管没有这样标记。编辑:限制问题到C
答案 0 :(得分:4)
不允许使用未定义的行为,它只是没有被编译器捕获。
这里的权衡取决于速度和安全性。可以通过几个额外的CPU周期来防止多种未定义的行为。
例如,您可以防止在从已分配的内存中读取但未通过编译的代码将零写入其中而初始化时发生的UB。然而,这会花费你额外的内存写入,这完全没有必要。
类似地,通过检查[]
运算符内的边界,可以防止读/写超过数组的末尾。但是,这会在每次访问阵列时花费额外的CPU周期。
C ++设计师认为,拥有速度并允许潜在的UB比强迫每个人为他们不需要的东西付费更好。然而,这种方法与Java"写一次,在任何地方运行"不兼容。要求,因此Java语言的设计者几乎在所有情况下都坚持完全定义的行为。
答案 1 :(得分:2)
最初,大多数形式的未定义行为表示某些实现可能会捕获的内容,但其他实现可能不会。因为标准的作者没有办法预测平台在陷阱的情况下可能做的所有事情(包括,字面上,系统会发出警报并锁定直到操作员手动清除故障的可能性)陷阱的后果超出了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编译器公开底层平台行为的许可证;在底层平台的行为符合程序员需求的情况下,这使得程序员的要求比机器代码更有效地表达,而不是必须以标准定义的方式完成。然而,最近,它被解释为编制者实施行为的许可,这些行为不仅与基础平台中的任何事物无关,也与任何合理的程序员期望无关,但甚至不受时间和因果关系法则的约束。
答案 2 :(得分:-2)
Java有一个运行时环境来照顾你。这就是为什么在越界时抛出异常的原因 - 这是在编译时无法解决的问题。
对向量使用at()方法时,在C ++中有运行时边界检查。它是()与[]运算符
的区别