เหตุใดคลาส Java จึงคอมไพล์แตกต่างกับบรรทัดว่าง


207

ฉันมีคลาส Java ต่อไปนี้

public class HelloWorld {
  public static void main(String []args) {
  }
}

เมื่อฉันรวบรวมไฟล์นี้และเรียกใช้ sha256 กับไฟล์คลาสที่ได้รับ

9c8d09e27ea78319ddb85fcf4f8085aa7762b0ab36dc5ba5fd000dccb63960ff  HelloWorld.class

ต่อไปฉันแก้ไขคลาสและเพิ่มบรรทัดว่างแบบนี้:

public class HelloWorld {

  public static void main(String []args) {
  }
}

อีกครั้งฉันรัน sha256 บนผลลัพธ์ที่คาดหวังว่าจะได้ผลลัพธ์เดียวกัน แต่กลับได้

11f7ad3ad03eb9e0bb7bfa3b97bbe0f17d31194d8d92cc683cfbd7852e2d189f  HelloWorld.class

ฉันได้อ่านบทความ TutorialsPoint นี้แล้วว่า:

บรรทัดที่มีช่องว่างเท่านั้นอาจมีความคิดเห็นเป็นที่รู้จักกันเป็นบรรทัดว่างและ Java ละเว้นทั้งหมด

ดังนั้นคำถามของฉันคือเนื่องจาก Java ละเว้นบรรทัดว่างเหตุใด bytecode ที่คอมไพล์จึงแตกต่างกันสำหรับทั้งสองโปรแกรม

คือความแตกต่างในว่าในไบต์จะถูกแทนที่ด้วยไบต์HelloWorld.class0x030x04


45
โปรดทราบว่าคอมไพเลอร์ไม่จำเป็นต้องกำหนดไว้ล่วงหน้าในการผลิตไฟล์คลาสแม้ว่าโดยปกติจะเป็น ดูคำถามนี้ ไฟล์ Jar โดยค่าเริ่มต้นจะไม่สามารถทำซ้ำได้เช่นการคอมไพล์รหัสเดียวกันจะส่งผลให้ JAR ที่แตกต่างกันสองอัน นั่นเป็นเพราะลำดับของไฟล์และการประทับเวลาจะไม่ตรงกัน การสร้างซ้ำที่เป็นไปได้ด้วยการกำหนดค่าเฉพาะ
Giacomo Alzetta

22
TutorialsPoint อ้างว่า"Java ละเว้นทั้งหมด"บรรทัดว่าง ส่วนที่ 3.4 ของข้อกำหนดภาษา Java ระบุไว้เป็นอย่างอื่น จะเชื่อแบบไหนดี ...
skomisa

37
@skomisa สเปค
wizzwizz4

4
@GiacomoAlzetta ยังไม่มีรูปแบบ bytecode ที่ระบุสำหรับไฟล์ bytecode ไฟล์เดียว ตัวอย่างเช่นลำดับของสมาชิกไม่ได้ระบุดังนั้นหากคอมไพเลอร์ใช้Sets ไม่เปลี่ยนรูปแบบใหม่ที่มีการสุ่มภายในก็สามารถสร้างคำสั่งที่แตกต่างกันในการทำงานแต่ละครั้ง นอกจากนี้ยังสามารถเพิ่มแอตทริบิวต์ที่กำหนดเองที่มีเวลารวบรวม และอื่น ๆ ...
Holger

15
@DioPhung อีกบทเรียนหนึ่งที่ได้รับ: tutorialspoint ไม่ได้เป็นแหล่งที่เชื่อถือได้สำหรับบทเรียนที่ดี
jwenting

คำตอบ:


331

โดยทั่วไปหมายเลขบรรทัดจะถูกเก็บไว้เพื่อการดีบั๊กดังนั้นหากคุณเปลี่ยนซอร์สโค้ดแบบที่คุณทำวิธีการของคุณจะเริ่มที่บรรทัดอื่นและคลาสที่คอมไพล์จะแสดงถึงความแตกต่าง


11
นั่นยังอธิบายว่าทำไมมันถึงแตกต่างในไบต์ที่รายงานโดย OP: end-of-transmissionย่อมาจากรหัส ASCII 4 และend-of-textย่อมาจากรหัส ASCII 3
Ferrybig

160
เพื่อพิสูจน์การทดลองนี้ฉันเปรียบเทียบแฮชของไฟล์คลาสของแหล่งที่มาของ OP โดยใช้-g:noneแฟล็กเมื่อรวบรวม (ซึ่งลบข้อมูลการดีบักทั้งหมดดูที่นี่ ) และได้รับแฮชเดียวกันในทั้งสองสถานการณ์
Captain Man

14
ในการสนับสนุนอย่างเป็นทางการของคำตอบของคุณจากส่วน 3.4 ( "สาย Terminators" ) ของJava Language ข้อกำหนดสำหรับ Java SE 11 : "เป็น Java คอมไพเลอร์แบ่งต่อไปลำดับของการป้อนอักขระ Unicode ในสายด้วยการตระหนักถึงจุดสิ้นสุดบรรทัด ... ความเส้นที่กำหนดไว้ โดยสาย Terminators อาจกำหนดหมายเลขบรรทัดที่ผลิตโดยเรียบเรียง Java "
skomisa

4
การใช้งานที่สำคัญอย่างหนึ่งของหมายเลขบรรทัดเหล่านี้คือหากมีการโยนข้อยกเว้น สามารถบอกหมายเลขบรรทัดของข้อยกเว้นในการติดตามสแต็กได้
gparyani

114

คุณสามารถเห็นการเปลี่ยนแปลงโดยใช้javap -vซึ่งจะส่งออกข้อมูล verbose เช่นเดียวกับคนอื่น ๆ ที่กล่าวถึงแล้วความแตกต่างจะอยู่ในหมายเลขบรรทัด:

$ javap -v HelloWorld.class > with-line.txt
$ javap -v HelloWorld.class > no-line.txt
$ diff -C 1 no-line.txt with-line.txt
*** no-line.txt 2018-10-03 11:43:32.719400000 +0100
--- with-line.txt       2018-10-03 11:43:04.378500000 +0100
***************
*** 2,4 ****
    Last modified 03-Oct-2018; size 373 bytes
!   MD5 checksum 058baea07fb787bdd81c3fb3f9c586bc
    Compiled from "HelloWorld.java"
--- 2,4 ----
    Last modified 03-Oct-2018; size 373 bytes
!   MD5 checksum 435dbce605c21f84dda48de1a76e961f
    Compiled from "HelloWorld.java"
***************
*** 50,52 ****
        LineNumberTable:
!         line 3: 0
        LocalVariableTable:
--- 50,52 ----
        LineNumberTable:
!         line 4: 0
        LocalVariableTable:

ไฟล์คลาสที่แม่นยำยิ่งขึ้นแตกต่างในLineNumberTableส่วน:

แอททริบิว LineNumberTable เป็นแอททริบิวต์ที่มีความยาวผันแปรได้ในตารางแอตทริบิวต์ของแอททริบิวต์ Code (§4.7.3) มันอาจจะถูกใช้โดย debuggers เพื่อตรวจสอบว่าส่วนหนึ่งของรหัสอาร์เรย์สอดคล้องกับหมายเลขบรรทัดที่กำหนดในไฟล์ต้นฉบับ

หากมีแอตทริบิวต์ LineNumberTable หลายรายการอยู่ในตารางคุณลักษณะของแอตทริบิวต์รหัสรายการเหล่านั้นอาจปรากฏในลำดับใดก็ได้

อาจมีมากกว่าหนึ่งแอตทริบิวต์ LineNumberTable ต่อบรรทัดของไฟล์ต้นฉบับในตารางคุณลักษณะของแอตทริบิวต์รหัส นั่นคือแอ็ตทริบิวต์ LineNumberTable อาจรวมกันเป็นตัวแทนบรรทัดที่กำหนดของไฟล์ต้นฉบับและไม่จำเป็นต้องเป็นแบบหนึ่งต่อหนึ่งที่มีบรรทัดซอร์ส


57

สมมติฐานที่ว่า"Java ละเว้นบรรทัดว่าง"ผิด นี่คือข้อมูลโค้ดที่มีลักษณะการทำงานแตกต่างกันไปขึ้นอยู่กับจำนวนของบรรทัดว่างก่อนวิธีmain:

class NewlineDependent {

  public static void main(String[] args) {
    int i = Thread.currentThread().getStackTrace()[1].getLineNumber();
    System.out.println((new String[]{"foo", "bar"})[((i % 2) + 2) % 2]);
  }
}

หากไม่มีสายว่างเปล่าก่อนที่mainจะพิมพ์"foo"แต่มีบรรทัดว่างหนึ่งก่อนที่จะพิมพ์main"bar"

เนื่องจากลักษณะการทำงานแบบรันไทม์แตกต่างกัน.classไฟล์ต้องแตกต่างกันไม่ว่าจะมีการประทับเวลาหรือข้อมูลเมตาอื่นใดก็ตาม

สิ่งนี้มีไว้สำหรับทุกภาษาที่มีสิทธิ์เข้าถึงกรอบสแต็กพร้อมหมายเลขบรรทัดไม่ใช่เฉพาะสำหรับ Java

หมายเหตุ: ถ้ามันถูกคอมไพล์ด้วย-g:none(ไม่มีข้อมูลการดีบักใด ๆ ) ดังนั้นหมายเลขบรรทัดจะไม่ถูกรวมอยู่getLineNumber()เสมอส่งคืน-1และโปรแกรมจะพิมพ์เสมอ"bar"โดยไม่คำนึงถึงจำนวนของตัวแบ่งบรรทัด


11
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: -1นอกจากนี้ยังสามารถพิมพ์
xehpuk

1
@ xehpuk วิธีเดียวที่ฉันจะได้รับ-1คือการใช้-g:noneธง มีวิธีอื่นในการรับข้อยกเว้นนี้โดยใช้สามัญjavacหรือไม่
Andrey Tyukin

3
ฉันเดาได้เฉพาะกับ-gตัวเลือก นอกจากนี้ยังมี-g:varsและซึ่งจะช่วยป้องกันการสร้างของ-g:source LineNumberTable
xehpuk

14

เช่นเดียวกับรายละเอียดหมายเลขบรรทัดใด ๆ สำหรับการดีบักรายการของคุณอาจเก็บเวลาและวันที่สร้าง ซึ่งจะแตกต่างกันโดยธรรมชาติทุกครั้งที่คุณรวบรวม


14
C # มีปัญหานี้เช่นกัน จนกระทั่งเมื่อไม่นานมานี้คอมไพเลอร์ฝัง GUID ใหม่ในแอสเซมบลีที่สร้างขึ้นเพื่อให้คุณมั่นใจได้ว่าสองบิลด์จะไม่เหมือนกันแบบไบนารีเพื่อให้คุณสามารถแยกพวกมันออกจากกัน!
Eric Lippert

3
@EricLippert หากทั้งสองบิลด์แตกต่างกันไปตามเวลาที่สร้างขึ้น (เช่นรหัสฐานที่เหมือนกัน) เราไม่ควรปฏิบัติกับมันเหมือนกันหรือไม่ ด้วย CI / CD build ที่ทันสมัย ​​(Jenkins, TeamCity, CircleCI) เราจะมีวิธีแยกความแตกต่างระหว่าง builds แต่จากมุมมองของแอปพลิเคชันการปรับใช้ไบนารีใหม่ที่มีฐานรหัสเหมือนกันดูเหมือนจะไม่เป็นประโยชน์
Dio Phung

2
@DioPhung เป็นวิธีอื่น ๆ คุณไม่ต้องการให้บิลด์ที่ต่างกันสองรายการมี GUID เดียวกันเนื่องจากเป็นวิธีที่ระบบสามารถตัดสินใจว่าจะใช้บิลด์ใด ดังนั้นจึงง่ายที่สุดในการสร้าง GUID ใหม่ในแต่ละครั้ง แล้วคุณจะได้รับผลข้างเคียงที่ Eric อธิบายว่าเป็นผลลัพธ์ที่ไม่ตั้งใจ
เกรแฮม

3
@ vikingsteve อย่างที่ฉันบอกว่ามันจะมีประโยชน์น้อยลงถ้าบิลด์ที่แตกต่างกันสองอันจะถูกรายงานด้วย GUID เดียวกันซึ่งจะถูกรายงานไปยังระบบว่าเป็นซอฟต์แวร์เดียวกัน สิ่งนี้จะทำให้เกิดความล้มเหลวโดยรวมของรูปแบบการจัดสรรใด ๆ ดังนั้นจึงเป็นภารกิจที่สำคัญที่ GUID จะไม่ซ้ำกัน (ภายในความน่าจะเป็นที่สมเหตุสมผล!) การมี GUID ที่แตกต่างกันสำหรับสองบิลด์ที่แยกกันของซอร์สโค้ดเดียวกันนั้นน่ารำคาญที่สุด ดังนั้นเมื่อเผชิญกับสถานการณ์ความล้มเหลวที่สำคัญต่อภารกิจสิ่งที่คุณคิดว่าไม่มีประโยชน์เล็กน้อยจริง ๆ ไม่ได้คิด
เกรแฮม

4
@vikingsteve ส่วนรหัสของไบนารียังคงเหมือนเดิม (ถ้าฉันเข้าใจฉันไม่ใช่ C # dev) มันเป็นแค่ข้อมูลเมตาที่แนบมากับไบนารี
Captain Man
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.