Java에서 정의되지 않은 동작


14

나는 C ++에서 정의되지 않은 일반적인 동작을 논의 하는 SO에 대한이 질문을 읽었 으며 Java에 정의되지 않은 동작이 있습니까?

이 경우 Java에서 정의되지 않은 동작의 일반적인 원인은 무엇입니까?

그렇지 않은 경우 Java의 어떤 기능으로 인해 그러한 동작이 발생하지 않으며 왜 최신 버전의 C 및 C ++가 이러한 특성으로 구현되지 않았습니까?


4
Java는 매우 엄격하게 정의됩니다. Java 언어 사양을 확인하십시오.


4
@ user1249, "정의되지 않은 동작"은 실제로 매우 엄격하게 정의됩니다.
Pacerier


"계약"을 위반할 때 Java는 무엇에 대해 말합니까? .hashCode와 호환되지 않는 .equals를 오버로드 할 때와 같은가? docs.oracle.com/javase/7/docs/api/java/lang/… 구어체 적으로 정의되지 않았지만 기술적으로 C ++과 같은 방식으로 정의되어 있지 않습니까?
Mooing Duck

답변:


18

Java에서는 잘못 동기화 된 프로그램의 동작이 정의되지 않은 것으로 간주 할 수 있습니다.

Java 7 JLS는 17.4.8 에서 "정의되지 않은"단어를 한 번 사용합니다 . 실행 및 인과 관계 요구 사항 :

f|d의 도메인을 f로 제한하여 제공된 함수를 나타내는 데 사용 합니다 d. 모두 xd, f|d(x) = f(x)모두를위한, 그리고 x에 없습니다 d, f|d(x)되고 정의되지 않은 ...

Java API 설명서는 결과가 정의되지 않은 일부 사례를 지정합니다 (예 : (더 이상 사용되지 않는) 생성자 Date (int year, int month, int day) :

주어진 인수가 범위를 벗어난 경우 결과는 정의되지 않습니다 ...

ExecutorService.invokeAll (Collection) 상태에 대한 Javadoc :

이 작업이 진행되는 동안 지정된 컬렉션을 수정하면 이 메서드의 결과가 정의되지 않습니다 ...

API 문서에서 "최선의 노력"이라는 용어를 사용하는 ConcurrentModificationException 과 같이 덜 정의 된 "정의되지 않은"동작은 다음과 같습니다.

일반적으로 말해서 비동기식 동시 수정이있을 경우 확실한 보장을 할 수 없으므로 페일-패스트 동작을 보장 할 수 없습니다. 빠른 작업 ConcurrentModificationException최선의 노력 으로 이루어집니다. 따라서이 예외에 의존하는 프로그램을 작성하는 것은 잘못 될 것입니다 ...


부록

질문 의견 중 하나는 Eric Lippert의 기사를 참조하여 주제 문제에 대한 유용한 소개를 제공합니다. 구현 정의 동작 .

필자는 저자가 Java가 아닌 C #을 대상으로한다는 점을 명심해야 할지라도 언어 독립적 추론을 위해이 기사를 추천한다.

전통적으로 우리는 프로그래밍 언어 관용구가 관용구의 사용이 어떤 영향을 미칠 수 있다면 정의되지 않은 행동 을한다고 말합니다. 예상대로 작동하거나 하드 디스크를 지우거나 시스템을 중단시킬 수 있습니다. 또한 컴파일러 작성자는 정의되지 않은 동작에 대해 경고 할 의무가 없습니다. (사실, 언어 정의에 의해 "정의되지 않은 동작"관용구를 사용하는 프로그램이 컴파일러와 충돌하도록 허용되는 일부 언어가 있습니다!) ...

반대로 구현 정의 동작 이 있는 관용구 는 컴파일러 작성자가 기능을 구현하는 방법에 대한 몇 가지 선택 사항을 갖고 선택해야하는 동작입니다. 이름에서 알 수 있듯이 구현 정의 동작은 최소한 정의되어 있습니다. 예를 들어 C #을 사용하면 정수 나누기가 오버플로 될 때 구현에서 예외를 발생 시키거나 값을 생성 할 수 있지만 구현시 하나를 선택해야합니다. 하드 디스크를 지울 수 없습니다 ...

언어 설계위원회가 특정 언어 관용구를 정의되지 않은 또는 구현 정의 된 동작으로 남겨 두도록하는 요인은 무엇입니까?

첫 번째 주요 요인은 시장에 특정 프로그램의 행동에 동의하지 않는 기존의 두 가지 언어 구현이 있습니까? ...

다음으로 중요한 요소는 이 기능이 자연스럽게 구현할 수있는 여러 가지 가능성을 제시 하는가? ...

세 번째 요소는 다음과 같습니다 . 기능이 너무 복잡하여 정확한 동작을 자세히 분석하기 어렵거나 비용이 많이 듭니까? ...

네 번째 요소는 이 기능이 컴파일러가 분석해야하는 부담이 큰가? ...

다섯 번째 요소는 이 기능이 런타임 환경에 큰 부담을 주는가? ...

여섯 번째 요소는 다음과 같습니다. 동작을 정의하면 주요 최적화가 불가능합니까? ...

그것들은 생각 나는 몇 가지 요소 일뿐입니다. 물론 "구현 정의"또는 "미정의"기능을 만들기 전에 언어 설계위원회가 토론하는 많은 다른 요소가 있습니다.

위의 내용은 매우 간단한 내용입니다. 전체 기사에는이 발췌 부분에서 언급 한 요점에 대한 설명과 예가 들어 있습니다. 그것은이다 훨씬 가치가 독서. 예를 들어, "6 번째 요소"에 대한 세부 사항은 Java 메모리 모델 ( JSR 133 )의 많은 명령문에 대한 동기 부여에 대한 통찰력을 제공하여 일부 최적화가 허용되는 이유를 이해하고 정의되지 않은 동작을 유발하는 반면 다른 것들은 금지되어 있습니다. 발생인과 관계 요구 사항 과 같은 제한 사항 .

기사 자료 중 어느 것도 나에게 새로운 것이 아니지만 그처럼 우아하고 간결하며 이해할 수있는 방식으로 제시 된 것을 본다면 저주받을 것입니다. 놀랄 만한.


! 나는 솔라리스 대 WinIntel 말에서 JMM = 기본 하드웨어 및 동시성과 관련이있는 실행 프로그램의 최종 결과가 달라질 수 있음을 추가 할 것입니다
마티 Verburg

2
@MartijnVerburg는 꽤 좋은 지적입니다. 내가 "정의되지 않은"것으로 태그하는 것을 주저하는 이유는 메모리 모델 이 올바르게 동기화 된 프로그램의 실행에 대한 발생인과성 같은 제약 조건을 가지고 있기 때문입니다.
gnat

사실, 사양은 JMM에서 어떻게 동작해야하는지 정의하지만, 인텔 등은 항상 동의하지는 않습니다. ;-)
Martijn Verburg

@MartijnVerburg JMM의 주요 요점은 "불일치 한"프로세서 제조업체의 과도한 최적화 누수 를 방지 하는 것 입니다. 지금까지 내가 자바 5.0 전에 후드를 수행 투기 쓰기가 "허공"와 같은 프로그램에 누설 할 수 알파 프로세서, 두통 이런 종류의했다 이해 - 따라서, 인과 관계 요구 사항은 JSR 133 (JMM)로 들어갔다을
모기

9
@MartinVerburg-지원되는 모든 하드웨어 플랫폼에서 JLS / JMM 사양에 따라 JVM이 작동하는지 확인하는 것은 JVM 구현 자의 작업입니다. 다른 하드웨어가 다르게 동작하는 경우, 하드웨어 구현 및 처리가 JVM 구현자가해야합니다.
Stephen C

10

내 머리 위로, 적어도 C ++에서와 같은 의미로 Java에는 정의되지 않은 동작이 있다고 생각하지 않습니다.

그 이유는 C ++과는 다른 Java의 철학이 있기 때문입니다. Java의 핵심 디자인 목표는 프로그램이 여러 플랫폼에서 변경없이 실행될 수 있도록하는 것이 었습니다. 따라서 사양은 모든 것을 매우 명시 적으로 정의합니다.

반대로 C 및 C ++의 핵심 설계 목표는 효율성입니다. 필요하지 않더라도 성능을 저하시키는 기능 (플랫폼 독립 포함)이 없어야합니다. 이를 위해, 스펙은 일부 플랫폼에서 추가 작업을 야기 할 수 있으므로 특정 플랫폼에 대해 프로그램을 작성하고 모든 고유 한 특성을 알고있는 사람들의 경우에도 성능을 저하 시키므로 의도적으로 일부 동작을 정의하지 않습니다.

Java가 그 이유에 대해 정의되지 않은 동작의 제한된 형태를 소급해서 도입 한 예가 있습니다. strictfp 키워드는 스펙이 이전에 요구 한대로 IEEE 754 표준을 따르지 않고 부동 소수점 계산을 벗어날 수 있도록 Java 1.2에 도입되었습니다. 따라서 추가 작업이 필요하고 일부 공통 CPU에서 모든 부동 소수점 계산이 느려졌지만 실제로는 경우에 따라 더 나쁜 결과가 생성되기 때문입니다.


2
Java의 다른 주요 목표 인 보안 및 격리에 주목하는 것이 중요하다고 생각합니다. 나도 이것이 C ++에서와 같이 '정의되지 않은'행동이 부족한 이유라고 생각합니다.
K.Steff 2016 년

3
@ K.Steff : 하이퍼 모던 C / C ++는 원격 보안과 관련된 모든 것에 전혀 적합하지 않습니다. 주어진 int x=-1; foo(); x<<=1;초현대적 인 철학은 재 작성을 선호 foo하므로 빠져 나가지 않는 경로는 도달 할 수 없어야합니다. 경우에, foo이다 if (should_launch_missiles) { launch_missiles(); exit(1); }컴파일러 (그리고 어떤 사람들에 따른한다) 단순히 그것을 단순화 수 있습니다 launch_missiles(); exit(1);. 기존의 UB는 임의 코드 실행 이었지만 시간과 인과 관계에 의해 제한되었습니다. 새로운 개선 된 UB는 어느 쪽에도 구속되지 않습니다.
supercat

3

Java는 이전 언어의 교훈으로 인해 정의되지 않은 행동을 근절하기 위해 열심히 노력합니다. 예를 들어 클래스 수준 변수는 자동으로 초기화됩니다. 로컬 변수는 성능상의 이유로 자동 초기화되지 않지만,이를 감지 할 수있는 프로그램을 작성하지 못하게하는 정교한 데이터 흐름 분석이 있습니다. 참조는 포인터가 아니므로 유효하지 않은 참조가 존재할 수 없으며 역 참조로 null인해 특정 예외가 발생합니다.

물론 완전히 지정되지 않은 일부 동작이 남아 있으며 신뢰할 수없는 프로그램을 가정 할 수 있습니다. 예를 들어, 일반 (정렬되지 않은) 반복하는 Set경우 언어는 각 요소를 한 번만 볼 수 있지만 어떤 순서로 보지는 보장하지 않습니다. 연속 실행에서 순서가 동일하거나 변경 될 수 있습니다. 또는 다른 할당이 발생하지 않거나 JDK 등을 업데이트하지 않는 한 동일하게 유지 될 수 있습니다 . 이러한 모든 효과를 없애는 것은 거의 불가능합니다 . 예를 들어, 모든 Collections 작업 을 명시 적으로 주문하거나 무작위 화 해야 하며, 이는 정의되지 않은 작은 추가 가치가 없습니다.


참조는 다른 이름으로 포인터입니다
curiousguy

@curiousguy- "참조"는 일반적으로 "포인터"에 허용되는 숫자 값의 산술 조작을 허용하지 않는 것으로 가정합니다. 따라서 전자는 후자보다 안전한 구성입니다. 유효한 참조가 존재하는 동안 오브젝트의 스토리지를 재사용 할 수없는 메모리 관리 시스템과 결합하면 참조는 메모리 사용 오류를 방지합니다. 적절한 메모리 관리를 사용하더라도 포인터는 그렇게 할 수 없습니다.
Jules

@Jules 다음은 용어의 문제입니다. 포인터 또는 참조를 호출 할 수 있으며 포인터 산술 및 수동 메모리 관리를 사용할 수있는 "안전한"언어의 "참조"및 "포인터"를 사용할 수 있습니다. (AFAIK은 "포인터 연산"만 C / C ++로 이루어집니다.)
curiousguy

2

"정의되지 않은 행동"과 그 기원을 이해해야합니다.

정의되지 않은 동작 은 표준에 의해 정의 되지 않은 동작을 의미합니다. C / C ++의 컴파일러 구현 및 추가 기능이 너무 많습니다. 이러한 추가 기능은 코드를 컴파일러에 연결했습니다. 중앙 집중식 언어 개발이 없었기 때문입니다. 따라서 일부 컴파일러의 고급 기능 중 일부는 "정의되지 않은 동작"이되었습니다.

Java에서 언어 사양은 Sun-Oracle에 의해 제어되며 다른 사람은 사양을 만들려고하지 않으므로 정의되지 않은 동작이 없습니다.

편집은 특히 질문에 답변

  1. 컴파일러 이전에 표준이 작성 되었기 때문에 Java에는 정의되지 않은 동작이 없습니다.
  2. 최신 C / C ++ 컴파일러는 구현을 거의 표준화하지 않았지만 표준화 전에 구현 된 기능은 여전히 ​​"정의되지 않은 동작"으로 태그되어 있습니다.

2
Java에는 UB가 없다는 것이 맞을 수도 있지만 한 엔티티가 모든 것을 제어하더라도 UB가있는 이유가있을 수 있으므로 제공 한 이유가 결론에 이르지 않습니다.
AProgrammer

2
또한 C와 C ++는 모두 ISO로 표준화되어 있습니다. 여러 개의 컴파일러가있을 수 있지만 한 번에 하나의 표준 만 있습니다.
MSalters 2018 년

1
@SarvexJatasra, 그것이 그것이 UB의 유일한 출처라는 것에 동의하지 않습니다. 예를 들어, 하나의 UB가 매달려있는 포인터를 참조하고 있으며 지금 스펙을 시작하더라도 GC가 아닌 언어로 UB를 남겨 두어야 할 이유가 있습니다. 이러한 이유는 기존의 실습 또는 기존 컴파일러와 관련이 없습니다.
AProgrammer

2
@SarvexJatasra, 표준에 명시 적으로 명시되어 있기 때문에 부호있는 오버플로는 UB입니다 (UB의 정의와 함께 제공된 예조차도). 같은 이유로 잘못된 포인터를 참조하는 것도 UB라고 표준은 말합니다.
AProgrammer

2
@ bames53 : 인용 된 장점 중 어느 것도 UB를 사용하는 위도 하이퍼 모던 컴파일러가 필요하지 않습니다. 임의의 코드 실행을 "자연적으로"유도 할 수있는 범위를 벗어난 메모리 액세스 및 스택 오버플로를 제외하고는 대부분의 UB-ish 작업이 결정되지 않는다고 말하는 것보다 더 넓은 위도를 필요로하는 유용한 최적화를 생각할 수 없습니다. 값 ( "추가 비트"를 가지고있는 것처럼 동작 할 수 있음)을 구현의 문서가 그러한 권한을 부여 할 권리를 명시 적으로 보유한 경우에만 그 이상의 결과를 초래할 수 있습니다. 문서는 "제한되지 않은 동작"을 제공 할 수 있습니다 ...
supercat

1

Java는 C / C ++에있는 정의되지 않은 모든 동작을 근본적으로 제거합니다. (예 : 부호있는 정수 오버플로, 0으로 나누기, 초기화되지 않은 변수, 널 포인터 역 참조, 비트 폭 이상으로 이동, 이중 프리, "소스 코드 끝에 줄 바꿈 없음"). 프로그래머가 거의 접하지 않습니다.

  • Java가 C 또는 C ++ 코드를 호출하는 방법 인 JNI (Java Native Interface) 함수 서명을 잘못 받거나, JVM 서비스를 잘못 호출하거나, 메모리를 손상시키고, 물건을 잘못 할당 / 해제하는 등 JNI를 망치는 방법에는 여러 가지가 있습니다. 이전에 이러한 실수를 저지른 적이 있으며 일반적으로 JNI 코드를 실행하는 하나의 스레드에서 오류가 발생하면 전체 JVM이 충돌합니다.

  • Thread.stop()더 이상 사용되지 않습니다. 인용문:

    Thread.stop더 이상 사용되지 않는 이유는 무엇 입니까?

    본질적으로 안전하지 않기 때문입니다. 스레드를 중지하면 스레드가 잠근 모든 모니터의 잠금이 해제됩니다. ( ThreadDeath예외가 스택을 전파 함에 따라 모니터가 잠금 해제됩니다 .) 이전에 이러한 모니터로 보호 된 오브젝트 중 하나가 일관성이없는 상태 인 경우 다른 스레드가이 오브젝트를 일치하지 않는 상태로 볼 수 있습니다. 이러한 물체는 손상되었다고합니다. 스레드가 손상된 객체에서 작동하면 임의의 동작이 발생할 수 있습니다. 이 동작은 미묘하고 감지하기 어려울 수 있습니다. 확인되지 않은 다른 예외와 달리 ThreadDeath스레드를 자동으로 종료합니다. 따라서 사용자는 자신의 프로그램이 손상되었다는 경고를받지 않습니다. 실제 손상이 발생한 후 언제라도 몇 시간 또는 며칠이라도 손상이 발생할 수 있습니다.

    https://docs.oracle.com/javase/8/docs/technotes/guides/concurrency/threadPrimitiveDeprecation.html

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.