Closed glenn-syj closed 3 months ago
이클립스는 자체적인 컴파일러를 이용하는 줄은 전혀 모르고 있었는데, 앞으로 컴파일 과정에서 다른 결과가 나타난다면 테스트 과정이 동일했는지 확인하기 전에 어떤 환경(컴파일러)에서 컴파일 했는지를 먼저 확인해봐도 좋을 것 같다는 생각이 들었습니다.
증분 컴파일러라는 특징을 이해하고 첫 컴파일 과정과 이후 코드의 수정과정에서 컴파일되는 원리만 이해한다면 이번 아이템에 대해서도 좀 더 깊게 이해할 수 있을 것 같습니다. 저 역시 컴파일러별로 동작 과정에 대한 자세한 원리들에 대해서는 개념적인 참고자료를 찾지 못해서 그저 실험적으로 접근할 수 밖에 없었는데, 변수명을 바꿔가며 합리적인 추측과정을 찾아가주셔서 좀 더 원리를 추측해보는데 좋은 길잡이가 되었던 것 같습니다!!!
Based on : #125 by @FickleBoBo
톱레벨 클래스 중복 시 동작에 관해서 (eclipse 컴파일러)
들어가며
이번 이슈는 사실 정확한 원인은 모르지만, 최대한 현상을 실험하고 원인을 추측하는 것에 목적을 두었습니다. 우선, 해당 절에 서술된 대로
Utensil.java
,Dessert.java
,Main.java
를 한 패키지에 두고 이용했습니다. (Java 버전은 JDK 17이며, ecilipse 버전은 2023-12 (4.30.0) 빌드는 20231201-2043입니다.)자바 코드와 바이트코드
첫 번째 컴파일
Dessert.java
에서 컴파일되어Dessert.NAME
의 값은 pie가 되었습니다.Utensil.java
에 쓰인 코드와 달리, 사실Dessert.java
에서 컴파일된Utensil.NAME
의 값인 pot입니다.Main.java
에서 출력되는 값은 potpie입니다. 이는 바이트코드에서 이미 정해진 값입니다. 즉, 동적으로 런타임에서 어떠한 파일의 어떠한 톱레벨 클래스를 결정하지는 않습니다.두 번째 컴파일
Dessert.java
의 내용을 바꾸고 저장한 뒤 실행하였습니다.Dessert.class
바이트코드에서는Utensil.NAME
의 값이 jar로 변환되었습니다. 이는 어쩌면 JVM이Dessert.NAME
을 tart로 업데이트하는 데에 착오를 일으키고 있음을 추측할 수도 있습니다.Utensil.class
바이트코드에서도Utensil.NAME
만을 나타내고 있습니다.Main.java
의 출력값은 pancake입니다. 이는 사실 첫 번째 컴파일에서,Utensil.java
에서 정의되었던Dessert
클래스와Utensil
클래스의 값입니다. 즉, 컴파일1 (숫자는 Main기준) 이 앞선 첫 번째 컴파일에서Dessert.java
에 기반한다면, 컴파일2는 첫 번째 컴파일에서Utensil.java
에 기반합니다. 아직 두 번째 컴파일은 이용되지 않은 것으로 보입니다.세 번째 컴파일 이번에는
Utensil.java
의 코드를 바꾸고 실행해보았습니다. 어떤 결과를 가져올까요?Dessert.java
가 컴파일의 기준이었다는 점을 생각해보면 이해에 도움이 될 지도 모릅니다. 즉, 세 번째 컴파일에서는Main.class
의 바이트코드가 변화하지 않았습니다.네 번째 컴파일 이제 'Dessert.java'의 코드를 바꾸고 실행해보겠습니다.
Utensil.java
소스코드대로, bottlejelly가 출력됩니다. 즉, 컴파일3은 아직 반영되지 않은 세 번째 컴파일에서의Utensil.java
를 반영한 바이트코드를 이용하고 있습니다.이클립스 컴파일러
그렇다면 왜 이클립스 컴파일러에서는 오류가 나지 않을까요? 이는 바로 이클립스가 javac와 달리 자체적인 컴파일러를 이용하는 까닭입니다.
증분 컴파일러 이클립스 컴파일러는 프로그램의 모든 모듈을 클린 빌드하지 않고, 지난 컴파일에서 변경된 부분만을 다시 컴파일하는 증분 컴파일러(incremental compiler) 입니다.
추측
위 예시에서, 이클립스의 증분 컴파일에서 다음과 같은 작동이 일어날지도 모릅니다. 이는 단순 추측에 불과하며, 아직 큰 의미를 발견하지는 못했습니다.
Dessert.class
내 톱레벨 클래스가 우선 사용됨 (potpie)Dessert.java
에 기반해 다시 컴파일되어 톱레벨 클래스 내NAME
값이 정해졌으나 (jartart), 이는 기존 컴파일에서의 쓰이지 않은 후보(pancake)가 실은 기존값이기에 휘발됨Utensil.java
가 다시 컴파일되었고(bottlejelly), 이는 프로그램 변경사항의 중점인Dessert.java
가 아니므로 톱레벨 클래스 정의를 읽어들이나 후보군에 오름Dessert.java
에 기반해 다시 컴파되어 톱레벨 클래스 내NAME
값이 정해졌으나 (siruddeok), 이는 기존 컴파일에서의 쓰이지 않은 후보(bottlejelly)가 실은 기존값이기에 휘발됨실험에 추가적인 단계로
Utensil.java
를 bagchocopie로 바꾸어 실행했을 때의 결과값은 여전히 bottlejelly였습니다. 이후Dessert.java
를 jeolpyeonddeok으로 바꾸고 실행했으나, 1~4의 단계와 마찬가지로 bagchocopie가 출력되었습니다. 자세한 작동과정은 접근할 수 없지만, 대략의 추측을 해보았습니다.References
https://www.baeldung.com/javac-vs-eclipse-compiler https://en.wikipedia.org/wiki/Incremental_compiler