我在SO上阅读了这个问题,讨论了C ++中一些常见的未定义行为,我想知道:Java是否也具有未定义的行为?
如果是这样,那么Java中未定义行为的一些常见原因是什么?
如果不是,那么Java的哪些功能使其摆脱了此类行为,为什么没有使用这些属性实现最新版本的C和C ++?
我在SO上阅读了这个问题,讨论了C ++中一些常见的未定义行为,我想知道:Java是否也具有未定义的行为?
如果是这样,那么Java中未定义行为的一些常见原因是什么?
如果不是,那么Java的哪些功能使其摆脱了此类行为,为什么没有使用这些属性实现最新版本的C和C ++?
Answers:
在Java中,您可以考虑未正确同步程序的行为未定义。
Java 7 JLS在17.4.8中曾经使用单词“ undefined” 。执行和因果要求:
我们
f|d
用来表示通过限制f
to 的域而给出的功能d
。对于所有x
在中的d
,f|d(x) = f(x)
对于所有x
不在中的d
,f|d(x)
都未定义 ...
Java API文档在某些情况下指定了结果未定义的情况-例如,在(不建议使用的)构造函数Date(int年,int月,int日)中:
如果给定参数超出范围,则结果不确定。
ExecutorService.invokeAll(Collection)的 Javadocs 状态:
如果在进行此操作时修改了给定的集合,则此方法的结果不确定。
可以在ConcurrentModificationException中找到不太正式的“未定义”行为,其中API文档使用术语“尽力而为”:
请注意,不能保证快速故障行为,因为通常来说,在存在不同步的并发修改的情况下,不可能做出任何严格的保证。快速失败的操作
ConcurrentModificationException
是尽力而为的。因此,编写依赖于此异常的程序的正确性是错误的...
问题注释之一涉及Eric Lippert的一篇文章,该文章为主题提供了有益的介绍:实现定义的行为。
我建议您将本文用于与语言无关的推理,尽管应牢记作者针对的是C#,而不是Java。
传统上,我们说编程语言惯用法具有不确定的行为,如果使用该惯用法会产生任何影响;它可以按您期望的方式工作,也可以擦除硬盘或使计算机崩溃。此外,编译器作者没有义务警告您未定义的行为。(实际上,在某些语言中,语言规范允许使用“未定义行为”惯用语的程序使编译器崩溃!)...
相比之下,具有实现定义的行为的习惯用法就是这样的行为:编译器作者对如何实现功能有多种选择,必须选择一种。顾名思义,至少定义了实现定义的行为。例如,C#允许实现在整数除法溢出时引发异常或产生值,但是实现必须选择一个。它无法擦除硬盘...
导致语言设计委员会将某些语言习语保留为未定义或实现定义的行为的因素有哪些?
第一个主要因素是:市场上是否存在两种针对特定程序行为的现有语言实现?...
下一个主要因素是:该功能是否自然会带来许多不同的实现可能性,其中某些明显优于其他可能性?...
第三个因素是:该功能是否如此复杂,以致于难以对其详细行为进行详细分类或指定?...
第四个因素是:该功能是否给编译器带来了沉重的分析负担?...
第五个因素是:该功能是否给运行时环境带来了沉重负担?...
第六个因素是:定义行为是否排除了一些重大的优化?...
这些只是想到的几个因素。当然,在设计功能“实现定义”或“未定义”之前,语言设计委员会会讨论许多其他因素。
以上只是非常简短的介绍;全文中包含本节中提到的要点的解释和示例;这是很多值得一读。例如,为“第六个因素”提供的详细信息可以使人们深入了解Java内存模型(JSR 133)中许多语句的动机,有助于理解为什么允许某些优化,导致未定义行为,而禁止其他优化,从而导致限制,例如事前发生和因果关系要求。
这篇文章的材料对我来说都不是特别新鲜,但是如果我看到它以如此优雅,简洁和可理解的方式呈现,我将是该死的。惊人。
在我脑海中,我认为Java中没有任何未定义的行为,至少在某种意义上与C ++中没有相同。
原因是Java背后的哲学与C ++背后的哲学不同。Java的核心设计目标是允许程序在各种平台上不变运行,因此规范非常明确地定义了所有内容。
相反,C和C ++的核心设计目标是效率:即使您不需要它们,也不应有任何会影响性能的功能(包括平台独立性)。为此,该规范故意未定义某些行为,因为定义行为会在某些平台上引起额外的工作,从而降低性能,即使对于专门为一个平台编写程序并了解其所有特性的人而言。
甚至有一个例子,正是出于这个原因,Java被迫追溯引入一种有限形式的未定义行为:Java 1.2中引入了strictfp关键字,以允许浮点计算偏离规范之前所要求的完全遵循IEEE 754标准,因为这样做需要额外的工作,并使某些常见CPU上的所有浮点计算速度变慢,而实际上在某些情况下却产生较差的结果。
int x=-1; foo(); x<<=1;
超现代哲学将倾向于重写,foo
因此任何不存在的路径都必须是无法到达的。如果foo
是if (should_launch_missiles) { launch_missiles(); exit(1); }
编译器,则可以(并且根据某些人的看法)将其简化为simple launch_missiles(); exit(1);
。传统的UB是随机代码执行,但以前受时间和因果关系法则的约束。新改进的UB不受任何约束。
正是由于早期语言的教训,Java竭尽全力消除未定义的行为。例如,类级变量会自动初始化。出于性能原因,局部变量不会自动初始化,但是存在复杂的数据流分析功能,以防止任何人编写能够检测到此问题的程序。引用不是指针,因此无效的引用不能存在,并且取消引用null
会导致特定的异常。
当然,还有一些行为没有完全指定,如果您认为它们是不正确的,则可以编写不可靠的程序。例如,如果您遍历一个普通的(未排序的)对象Set
,则该语言保证您将只看到一次每个元素,但不能以顺序看到它们。顺序可能在连续运行中相同,或者可能会更改;或者,只要没有其他分配发生,或者只要您不更新JDK等,它就可以保持不变。要消除所有这些影响几乎是不可能的;例如,您将必须对所有 Collections操作进行显式排序或随机化,这根本不值得再加上一些小的un-undefined-ness。
您必须了解“未定义行为”及其来源。
未定义行为是指标准未定义的行为。C / C ++具有太多不同的编译器实现和其他功能。这些附加功能将代码绑定到编译器。这是因为没有集中的语言开发。因此,某些编译器的某些高级功能成为“未定义的行为”。
在Java中,语言规范是由Sun-Oracle控制的,没有其他人试图制定规范,因此没有未定义的行为。
编辑专门回答问题
Java基本上消除了C / C ++中发现的所有未定义行为。(例如:有符号整数溢出,零除,未初始化的变量,空指针取消引用,移位超过位宽,双倍释放,甚至“源代码末尾没有换行符。”)但是Java具有一些模糊的未定义行为,这些行为程序员很少遇到。
Java本机接口(JNI),Java调用C或C ++代码的一种方式。有很多方法可以弄乱JNI,例如弄错函数签名,对JVM服务进行无效调用,破坏内存,不正确地分配/释放东西等等。我之前犯过这些错误,并且通常任何执行JNI代码的线程提交错误都会导致整个JVM崩溃。
Thread.stop()
,已弃用。引用:
为什么
Thread.stop
不推荐使用?因为它本质上是不安全的。停止线程会使它解锁它已锁定的所有监视器。(当
ThreadDeath
异常在堆栈中传播时,监视器将被解锁。)如果以前由这些监视器保护的任何对象处于不一致状态,则其他线程现在可能会以不一致状态查看这些对象。据说这些物体已损坏。当线程对损坏的对象进行操作时,可能会导致任意行为。此行为可能是微妙的,难以检测,或者可能是明显的。与其他未经检查的异常不同,它会ThreadDeath
无声地杀死线程。因此,用户没有警告其程序可能已损坏。在实际损坏发生后的任何时间,甚至在未来数小时或数天,腐败都可能显示出来。
https://docs.oracle.com/javase/8/docs/technotes/guides/concurrency/threadPrimitiveDeprecation.html