เหตุใด C ++ จึงมี 'พฤติกรรมที่ไม่ได้กำหนด' (UB) และภาษาอื่น ๆ เช่น C # หรือ Java ไม่


51

โพสต์สแต็คโอเวอร์โฟลว์นี้แสดงรายการสถานการณ์ที่ค่อนข้างครอบคลุมซึ่งข้อกำหนดภาษา C / C ++ ประกาศว่าเป็น 'พฤติกรรมที่ไม่ได้กำหนด' อย่างไรก็ตามฉันต้องการเข้าใจว่าทำไมภาษาสมัยใหม่อื่น ๆ เช่น C # หรือ Java ไม่มีแนวคิดของ 'พฤติกรรมที่ไม่ได้กำหนด' มันหมายความว่าผู้ออกแบบคอมไพเลอร์สามารถควบคุมสถานการณ์ที่เป็นไปได้ทั้งหมด (C # และ Java) หรือไม่ (C และ C ++)?




3
และโพสต์ดังนั้นนี้หมายถึงพฤติกรรมที่ไม่ได้กำหนดแม้ในสเป็ค Java
gbjbaanb

"เหตุใด C ++ จึงมี 'พฤติกรรมที่ไม่ได้กำหนด'"น่าเสียดายที่นี่เป็นหนึ่งในคำถามที่ยากที่จะตอบอย่างเป็นกลางนอกเหนือจากข้อความ "เพราะเหตุผล X, Y และ / หรือ Z (ซึ่งอาจเป็นnullptr) คนหนึ่งใส่ใจที่จะกำหนดพฤติกรรมโดยการเขียนและ / หรือการใช้ข้อกำหนดที่เสนอ " : c
code_dredd

ฉันจะท้าทายหลักฐาน อย่างน้อย C # มีรหัส "ไม่ปลอดภัย" Microsoft เขียนว่า "ในแง่หนึ่งการเขียนโค้ดที่ไม่ปลอดภัยนั้นเหมือนกับการเขียนโค้ด C ภายในโปรแกรม C #" และให้เหตุผลตัวอย่างว่าทำไมจึงต้องการทำเช่นนั้น: เพื่อเข้าถึงฮาร์ดแวร์หรือระบบปฏิบัติการและเพื่อความรวดเร็ว นี่คือสิ่งที่ C คิดค้นขึ้นมา (นรกพวกเขาเขียนระบบปฏิบัติการใน C!) ดังนั้นคุณก็มี
ปีเตอร์ - Reinstate Monica

คำตอบ:


73

พฤติกรรมที่ไม่ได้กำหนดเป็นหนึ่งในสิ่งเหล่านั้นที่ได้รับการยอมรับว่าเป็นความคิดที่ไม่ดีมากเฉพาะในการหวนกลับ

คอมไพเลอร์ตัวแรกคือความสำเร็จที่ยอดเยี่ยมและยินดีต้อนรับการปรับปรุงทางเลือก - ภาษาเครื่องหรือการเขียนโปรแกรมภาษาแอสเซมบลี ปัญหาเกี่ยวกับสิ่งนั้นเป็นที่รู้จักกันดีและภาษาระดับสูงถูกคิดค้นขึ้นมาโดยเฉพาะเพื่อแก้ไขปัญหาที่รู้จัก (ความกระตือรือร้นในเวลานั้นยอดเยี่ยมมากจน HLL ได้รับการยกย่องว่าเป็น "จุดจบของการเขียนโปรแกรม" - ราวกับว่าต่อจากนี้ไปเราจะต้องเขียนสิ่งที่เราต้องการเพียงเล็กน้อยและผู้เรียบเรียงจะทำงานจริงทั้งหมด)

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

ปรากฎว่าด้วยฐานรหัสขนาดใหญ่และโครงสร้างที่ซับซ้อนมากขึ้นซึ่งเป็นไปได้ด้วยระบบการเขียนโปรแกรมที่มีประสิทธิภาพยิ่งขึ้นใช่โปรแกรมเมอร์หลายคนจะทำหน้าที่กำกับดูแลเช่นนี้เป็นครั้งคราวและพฤติกรรมที่ไม่ได้กำหนดกลายเป็นปัญหาสำคัญ แม้กระทั่งทุกวันนี้ความปลอดภัยส่วนใหญ่ที่รั่วไหลจากเล็กไปจนน่ากลัวเป็นผลมาจากพฤติกรรมที่ไม่ได้กำหนดในรูปแบบเดียวหรืออีกรูปแบบหนึ่ง (เหตุผลก็คือโดยทั่วไปพฤติกรรมที่ไม่ได้กำหนดนั้นจริง ๆ แล้วถูกกำหนดโดยสิ่งต่าง ๆ ในระดับที่ต่ำกว่าถัดไปในการคำนวณและผู้โจมตีที่เข้าใจระดับนั้นสามารถใช้ห้องเลื้อยในการทำให้โปรแกรมไม่เพียง แต่สิ่งที่ไม่ได้ตั้งใจเท่านั้นพวกเขาตั้งใจ.)

เนื่องจากเรารู้จักสิ่งนี้จึงมีไดรฟ์ทั่วไปในการกำจัดพฤติกรรมที่ไม่ได้กำหนดจากภาษาระดับสูงและ Java นั้นละเอียดมากโดยเฉพาะ (ซึ่งค่อนข้างง่ายเมื่อเทียบกับที่มันถูกออกแบบมาให้ทำงานด้วยเครื่องเสมือนจริงที่ออกแบบเป็นพิเศษของตัวเอง) ภาษาที่เก่ากว่าอย่าง C ไม่สามารถดัดแปลงได้อย่างง่ายดายเช่นนั้นโดยไม่สูญเสียความสามารถในการทำงานร่วมกับโค้ดที่มีอยู่จำนวนมาก

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


56
ฉันไม่คิดว่าผู้คนในชุมชน C จำนวนมากจะเห็นด้วยกับข้อความนี้ หากคุณจะติดตั้งเพิ่มเติม C และกำหนดพฤติกรรมที่ไม่ได้กำหนด (เช่นเริ่มต้นทุกอย่างเลือกลำดับของการประเมินผลสำหรับพารามิเตอร์ฟังก์ชั่น ฯลฯ ) ฐานขนาดใหญ่ของรหัสที่ทำงานได้ดีจะยังคงทำงานได้อย่างสมบูรณ์ เฉพาะรหัสที่ไม่ชัดเจนในวันนี้เท่านั้นที่จะถูกรบกวน ในอีกด้านหนึ่งถ้าคุณไม่ได้กำหนดไว้ในวันนี้คอมไพเลอร์จะยังคงมีอิสระในการใช้ประโยชน์จากความก้าวหน้าใหม่ในสถาปัตยกรรมซีพียูและการปรับโค้ดให้เหมาะสม
Christophe

13
ส่วนหลักของคำตอบนั้นฟังดูไม่น่าเชื่อสำหรับฉัน ฉันหมายความว่ามันเป็นไปไม่ได้เลยที่จะเขียนฟังก์ชั่นที่เพิ่มตัวเลขสองหลัก (เช่นint32_t add(int32_t x, int32_t y)) ใน C ++ ได้อย่างปลอดภัย อาร์กิวเมนต์ปกติรอบ ๆ นั้นเกี่ยวข้องกับประสิทธิภาพ แต่มักจะสลับกับข้อโต้แย้งที่พกพาได้บางส่วน (เช่นใน "เขียนครั้งเดียวรัน ... บนแพลตฟอร์มที่คุณเขียนมัน ... และไม่มีที่ไหนอื่น ;-)") ประมาณหนึ่งอาร์กิวเมนต์สามารถจึงได้รับ: บางสิ่งบางอย่างจะไม่ได้กำหนดเพราะคุณไม่ทราบว่าคุณอยู่ใน microcontoller 16bit หรือเซิร์ฟเวอร์ 64 บิต (อ่อนแอหนึ่ง แต่ยังคงโต้แย้ง)
Marco13

12
@ Marco13 เห็นด้วย - และกำจัดปัญหา "พฤติกรรมที่ไม่ได้กำหนด" โดยการทำบางสิ่งบางอย่าง "พฤติกรรมที่กำหนดไว้ แต่ไม่จำเป็นต้องเป็นสิ่งที่ผู้ใช้ต้องการและไม่มีการเตือนเมื่อมันเกิดขึ้น" แทนที่จะเป็น "พฤติกรรมที่ไม่ได้กำหนด" .
alephzero

9
แม้กระทั่งทุกวันนี้ความปลอดภัยส่วนใหญ่ที่รั่วไหลจากเล็กไปจนน่ากลัวเป็นผลมาจากพฤติกรรมที่ไม่ได้กำหนดในรูปแบบเดียวหรืออย่างอื่น " ต้องการการอ้างอิง ฉันคิดว่าส่วนใหญ่ของพวกเขาคือการฉีด XYZ ในขณะนี้
โจชัว

34
"พฤติกรรมที่ไม่ได้กำหนดเป็นหนึ่งในสิ่งเหล่านั้นที่ได้รับการยอมรับว่าเป็นความคิดที่แย่มากในการหวนกลับ" นั่นคือความคิดเห็นของคุณ หลายคน (รวมตัวเอง) ไม่แชร์
การแข่งขัน Lightness กับโมนิก้า

104

โดยพื้นฐานแล้วเนื่องจากนักออกแบบของ Java และภาษาที่คล้ายกันไม่ต้องการพฤติกรรมที่ไม่ได้กำหนดในภาษาของพวกเขา นี่เป็นการแลกเปลี่ยนที่ไม่อนุญาตให้พฤติกรรมที่ไม่ได้กำหนดมีศักยภาพในการปรับปรุงประสิทธิภาพ แต่นักออกแบบภาษาให้ความสำคัญกับความปลอดภัยและการคาดการณ์ที่สูงขึ้น

ตัวอย่างเช่นถ้าคุณจัดสรรอาร์เรย์ใน C ข้อมูลจะไม่ถูกกำหนด ใน Java ไบต์ทั้งหมดต้องเริ่มต้นเป็น 0 (หรือค่าที่ระบุอื่น ๆ ) ซึ่งหมายความว่ารันไทม์จะต้องผ่านการดำเนินการอาร์เรย์ (O (n)) ในขณะที่ C สามารถดำเนินการจัดสรรในทันที ดังนั้น C จะเร็วขึ้นสำหรับการดำเนินการดังกล่าว

หากโค้ดที่ใช้อาร์เรย์กำลังเติมข้อมูลอยู่ดีก่อนที่จะอ่านนี่เป็นการสูญเสียความพยายามของ Java ไปแล้ว แต่ในกรณีที่โค้ดอ่านก่อนคุณจะได้ผลลัพธ์ที่คาดเดาได้ใน Java แต่ผลลัพธ์ที่คาดเดาไม่ได้ใน C


19
การนำเสนอสุดยอดของภาวะที่กลืนไม่เข้าคายไม่ออกของ HLL: ความปลอดภัยและความสะดวกในการใช้งานเทียบกับประสิทธิภาพ ไม่มีกระสุนเงิน: มีกรณีการใช้งานสำหรับแต่ละด้าน
Christophe

5
@Christophe เพื่อความยุติธรรมมีวิธีการที่ดีกว่าในการแก้ปัญหามากกว่าการปล่อยให้ UB ไปโดยไม่มีใครโต้แย้งอย่างสิ้นเชิงเช่น C และ C ++ คุณสามารถใช้ภาษาที่ปลอดภัยและได้รับการจัดการโดยมีช่องโหว่ให้หลบหนีเข้าไปในดินแดนที่ไม่ปลอดภัย TBH มันจะดีจริงๆที่จะสามารถรวบรวมโปรแกรม C / C ++ ของฉันด้วยการตั้งค่าสถานะว่า "ใส่เครื่องจักรรันไทม์ราคาแพงที่คุณต้องการฉันไม่สนหรอก แต่แค่บอกฉันเกี่ยวกับ UB ทั้งหมดที่เกิดขึ้น ."
Alexander

4
ตัวอย่างที่ดีของโครงสร้างข้อมูลที่จงใจอ่านตำแหน่งที่ไม่ได้กำหนดค่าเริ่มต้นคือ Briggs และการแสดงชุดกระจัดกระจายของ Torczon (เช่นดู codingplayground.blogspot.com/2009/03/ เป็นต้น ) การเริ่มต้นชุดดังกล่าวคือ O (1) ใน C แต่ O ( n) ด้วยการเริ่มต้นบังคับของ Java
Arch D. Robison

9
แม้ว่าจะเป็นความจริงที่ว่าการบังคับให้ข้อมูลเริ่มต้นทำให้โปรแกรมที่เสียหายสามารถคาดการณ์ได้มากขึ้น แต่ก็ไม่ได้รับประกันพฤติกรรมที่ตั้งใจ: หากอัลกอริทึมคาดว่าจะอ่านข้อมูลที่มีความหมายในขณะที่อ่านศูนย์เริ่มต้นโดยปริยาย อ่านขยะ ด้วยโปรแกรม C / C ++ ข้อผิดพลาดดังกล่าวจะสามารถมองเห็นได้โดยการเรียกใช้กระบวนการภายใต้valgrindซึ่งจะแสดงให้เห็นอย่างชัดเจนว่าที่ใดที่มีการใช้ค่าเริ่มต้น คุณไม่สามารถใช้valgrindกับรหัสจาวาได้เนื่องจากรันไทม์ทำการเริ่มต้นทำให้การvalgrindตรวจสอบไร้ประโยชน์
cmaster

5
@cmaster ซึ่งเป็นสาเหตุที่คอมไพเลอร์ C # ไม่อนุญาตให้คุณอ่านจากคนในพื้นที่ที่ไม่ได้กำหนดค่าเริ่มต้น ไม่จำเป็นต้องมีการตรวจสอบรันไทม์ไม่จำเป็นต้องเริ่มต้นเพียงการวิเคราะห์เวลารวบรวม แม้ว่าจะยังเป็นการแลกเปลี่ยนอยู่ดี แต่ก็มีบางกรณีที่คุณไม่มีวิธีที่ดีในการจัดการการแตกกิ่งก้านสาขารอบ ๆ คนในพื้นที่ที่อาจไม่ได้รับมอบหมาย ในทางปฏิบัติฉันไม่พบกรณีใด ๆ ที่นี่ไม่ใช่การออกแบบที่ไม่ดีในตอนแรกและแก้ไขได้ดีกว่าด้วยการทบทวนโค้ดใหม่เพื่อหลีกเลี่ยงการแยกย่อยที่ซับซ้อน (ซึ่งยากสำหรับมนุษย์ในการแยกวิเคราะห์) แต่อย่างน้อยก็เป็นไปได้
Luaan

42

พฤติกรรมที่ไม่ได้กำหนดเปิดใช้งานการปรับให้เหมาะสมอย่างมีนัยสำคัญโดยให้ละติจูดคอมไพเลอร์ทำสิ่งที่แปลกหรือไม่คาดคิด (หรือแม้แต่ปกติ) ที่ขอบเขตหรือเงื่อนไขอื่น ๆ

ดูhttp://blog.llvm.org/2011/05/what-every-c-programmer-should-know.htmlดู

การใช้ตัวแปร uninitialized: นี่เป็นที่รู้กันทั่วไปว่าเป็นแหล่งที่มาของปัญหาในโปรแกรม C และมีเครื่องมือมากมายที่จะจับสิ่งเหล่านี้: จากคำเตือนของคอมไพเลอร์ถึงการวิเคราะห์แบบคงที่และแบบไดนามิก สิ่งนี้ช่วยปรับปรุงประสิทธิภาพโดยไม่ต้องการให้ตัวแปรทั้งหมดเป็นศูนย์เริ่มต้นเมื่อพวกเขาเข้ามาในขอบเขต (เช่น Java) สำหรับตัวแปรสเกลาร์ส่วนใหญ่สิ่งนี้จะทำให้เกิดค่าใช้จ่ายเพียงเล็กน้อย แต่หน่วยความจำสแต็กอาร์เรย์และหน่วยความจำ malloc จะต้องมีหน่วยความจำของหน่วยเก็บข้อมูลซึ่งอาจมีค่าใช้จ่ายค่อนข้างสูง


จำนวนเต็มมากเกินที่ลงนามแล้ว: หากการคำนวณทางคณิตศาสตร์เกี่ยวกับการพิมพ์ 'int' (ตัวอย่าง) ล้นผลลัพธ์จะไม่ได้กำหนด ตัวอย่างหนึ่งคือ "INT_MAX + 1" ไม่รับประกันว่าจะเป็น INT_MIN ลักษณะการทำงานนี้ช่วยให้คลาสของการปรับให้เหมาะสมบางอย่างที่มีความสำคัญสำหรับรหัสบางอย่าง ตัวอย่างเช่นการรู้ว่า INT_MAX + 1 ไม่ได้ถูกกำหนดอนุญาตให้ปรับ "X + 1> X" เป็น "true" ได้อย่างเหมาะสม การรู้การคูณ "ไม่สามารถ" ล้น (เพราะการทำเช่นนั้นจะไม่ได้กำหนด) ช่วยให้การเพิ่มประสิทธิภาพ "X * 2/2" เป็น "X" แม้ว่าสิ่งเหล่านี้อาจดูไม่สำคัญ แต่สิ่งต่าง ๆ เหล่านี้มักถูกเปิดเผยโดยการขยายตัวแบบอินไลน์และมาโคร การปรับให้เหมาะสมที่สำคัญยิ่งขึ้นที่สิ่งนี้อนุญาตสำหรับลูป "<=" เช่นนี้:

for (i = 0; i <= N; ++i) { ... }

ในลูปนี้คอมไพเลอร์สามารถสันนิษฐานได้ว่าลูปจะวนซ้ำ N + 1 เท่าหาก "i" ไม่ได้ถูกกำหนดบนโอเวอร์โฟลว์ซึ่งอนุญาตให้ลูปการปรับการลูปในวงกว้างให้ตรงกันในทางกลับกันหากตัวแปรถูกกำหนด ล้อมรอบโอเวอร์โฟลว์แล้วคอมไพเลอร์จะต้องสมมติว่าลูปอาจไม่มีที่สิ้นสุด (ซึ่งเกิดขึ้นหาก N คือ INT_MAX) - ซึ่งจะปิดใช้งานการปรับลูปที่สำคัญเหล่านี้ โดยเฉพาะอย่างยิ่งสิ่งนี้มีผลต่อแพลตฟอร์ม 64 บิตเนื่องจากรหัสจำนวนมากใช้ "int" เป็นตัวแปรเหนี่ยวนำ


27
แน่นอนว่าเหตุผลที่แท้จริงที่ว่าทำไมจำนวนเต็มของจำนวนเต็มที่ลงนามไม่ได้ถูกกำหนดไว้คือเมื่อ C ได้รับการพัฒนามีอย่างน้อยสามตัวแทนที่แตกต่างกันของจำนวนเต็มที่ลงนามในการใช้งาน และแต่ละรายการให้ผลลัพธ์ที่แตกต่างกันสำหรับ INT_MAX + 1 การทำโอเวอร์โฟลว์ที่ไม่ได้กำหนดอนุญาตa + bให้รวบรวมadd b aคำสั่งพื้นฐานในทุกสถานการณ์แทนที่จะต้องใช้คอมไพเลอร์เพื่อจำลองรูปแบบเลขคณิตจำนวนเต็มแบบอื่นที่ลงนามแล้ว
ทำเครื่องหมาย

2
การปล่อยให้ล้นจำนวนเต็มการปฏิบัติตนในการกำหนดหลวมแฟชั่นช่วยเพิ่มประสิทธิภาพอย่างมีนัยสำคัญในกรณีที่มีพฤติกรรมที่เป็นไปได้ทั้งหมดจะตอบสนองความต้องการใช้งาน การปรับให้เหมาะสมส่วนใหญ่จะถูกริบอย่างไรก็ตามถ้าจำเป็นต้องมีโปรแกรมเมอร์เพื่อหลีกเลี่ยงการล้นจำนวนเต็มในค่าใช้จ่ายทั้งหมด
supercat

5
@supercat ซึ่งเป็นอีกสาเหตุหนึ่งที่ทำให้การหลีกเลี่ยงพฤติกรรมที่ไม่ได้กำหนดนั้นเป็นเรื่องปกติในภาษาที่ใหม่กว่า - เวลาโปรแกรมเมอร์มีค่ามากกว่าเวลา CPU มาก การเพิ่มประสิทธิภาพประเภท C ได้รับอนุญาตให้ทำโดย UB นั้นไร้ประโยชน์ในคอมพิวเตอร์เดสก์ท็อปสมัยใหม่และให้เหตุผลเกี่ยวกับรหัสที่ยากขึ้นมาก (ไม่ต้องพูดถึงความปลอดภัย) แม้ในรหัสประสิทธิภาพที่สำคัญคุณสามารถได้รับประโยชน์จากการเพิ่มประสิทธิภาพระดับสูงที่ค่อนข้างยาก (หรือยากยิ่งกว่า) ใน C ฉันมีซอฟต์แวร์ 3D renderer ของตัวเองใน C # และสามารถใช้งานHashSetได้อย่างยอดเยี่ยม
Luaan

2
@supercat: Wrt_loosely define_ ตัวเลือกเชิงตรรกะสำหรับการล้นจำนวนเต็มจะต้องใช้พฤติกรรมที่กำหนดไว้ นั่นเป็นแนวคิดที่มีอยู่และไม่ใช่ภาระที่ไม่เหมาะสมสำหรับการนำไปใช้งาน ส่วนใหญ่จะหนีด้วย "มันเป็นส่วนประกอบ 2 ของด้วยการพันรอบ" ฉันสงสัย <<อาจเป็นกรณีที่ยาก
MSalters

@MSalters มีวิธีแก้ปัญหาที่เรียบง่ายและได้รับการศึกษาเป็นอย่างดีซึ่งไม่ได้เป็นพฤติกรรมที่ไม่ได้กำหนดหรือพฤติกรรมที่กำหนดไว้ นั่นคือคุณสามารถพูดว่า " x << yประเมินค่าที่ถูกต้องของประเภทint32_tแต่เราจะไม่พูดว่า" สิ่งนี้ช่วยให้ผู้ใช้งานใช้วิธีแก้ปัญหาอย่างรวดเร็ว แต่ไม่ได้ทำหน้าที่เป็นเงื่อนไขที่ผิดพลาดทำให้การปรับเปลี่ยนรูปแบบการเดินทางข้ามเวลาเกิดขึ้นเนื่องจาก nondeterminism ถูก จำกัด การส่งออกของการดำเนินการนี้ - ข้อมูลจำเพาะรับประกันว่าหน่วยความจำ โดยการประเมินผลการแสดงออก ...
Mario Carneiro

20

ในช่วงแรกของ C มีความสับสนวุ่นวายมากมาย คอมไพเลอร์ต่างกันใช้ภาษาต่างกัน เมื่อมีความสนใจที่จะเขียนสเปคสำหรับภาษานั้นสเปคนั้นจะต้องย้อนกลับได้ค่อนข้างเข้ากันได้กับซีที่โปรแกรมเมอร์นั้นต้องพึ่งพาคอมไพเลอร์ แต่รายละเอียดเหล่านั้นบางส่วนนั้นไม่สามารถพกพาได้และไม่สมเหตุสมผลเช่นสมมติว่าเป็น endianess หรือเค้าโครงข้อมูล มาตรฐาน C จึงขอสงวนรายละเอียดจำนวนมากเป็นพฤติกรรมที่ไม่ได้กำหนดหรือการใช้งานที่ระบุซึ่งทำให้มีความยืดหยุ่นอย่างมากสำหรับนักเขียนคอมไพเลอร์ C ++ สร้างตาม C และยังมีลักษณะการทำงานที่ไม่ได้กำหนด

Java พยายามเป็นภาษาที่ปลอดภัยและง่ายกว่า C ++ มาก Java กำหนดความหมายของภาษาในแง่ของเครื่องเสมือนอย่างละเอียด สิ่งนี้ทำให้พื้นที่เล็ก ๆ น้อย ๆ สำหรับพฤติกรรมที่ไม่ได้กำหนดในทางกลับกันมันทำให้ข้อกำหนดที่อาจเป็นเรื่องยากสำหรับการใช้งานจาวาที่จะทำ (เช่นการมอบหมายการอ้างอิงนั้นต้องเป็นอะตอมมิก ที่ Java รองรับการดำเนินการที่อาจไม่ปลอดภัยพวกเขามักจะถูกตรวจสอบโดยเครื่องเสมือนที่รันไทม์ (ตัวอย่างเช่นบาง cast)


คุณกำลังพูดว่าการเข้ากันได้แบบย้อนหลังเป็นเหตุผลเดียวที่ทำให้ C และ C ++ ไม่ได้รับพฤติกรรมที่ไม่ได้กำหนด?
Sisir

3
มันเป็นหนึ่งในเกมที่ใหญ่กว่าอย่างแน่นอน @Sisir แม้ในหมู่โปรแกรมเมอร์ผู้มีประสบการณ์คุณจะประหลาดใจกับสิ่งที่ไม่ควรหยุดพักที่จะทำลายเมื่อคอมไพเลอร์เปลี่ยนวิธีการจัดการพฤติกรรมที่ไม่ได้กำหนด (ในกรณีที่จุดมีบิตของความสับสนวุ่นวายเมื่อ GCC เริ่มต้นการเพิ่มประสิทธิภาพออก "คือthisการตรวจสอบในขณะที่กลับในบริเวณที่ null?" thisเป็นอยู่nullptrคือ UB และทำให้ไม่สามารถเกิดขึ้นจริง.)
จัสตินเวลา

9
@Sisir อีกหนึ่งคนที่ยิ่งใหญ่คือความเร็ว ในยุคแรก ๆ ของ C ฮาร์ดแวร์ต่างกันมากกว่าในทุกวันนี้ เพียงแค่ไม่ระบุว่าจะเกิดอะไรขึ้นเมื่อคุณเพิ่ม 1 ลงใน INT_MAX คุณสามารถปล่อยให้คอมไพเลอร์ทำสิ่งที่เร็วที่สุดสำหรับสถาปัตยกรรม (เช่นระบบเติมเต็มจะสร้าง -INT_MAX ในขณะที่ระบบเติมเต็มสองรายการจะสร้าง INT_MIN) ในทำนองเดียวกันการไม่ระบุสิ่งที่เกิดขึ้นเมื่อคุณอ่านตอนจบของอาเรย์คุณสามารถมีระบบที่มีการป้องกันหน่วยความจำสิ้นสุดโปรแกรมในขณะที่อีกระบบหนึ่งไม่จำเป็นต้องใช้การตรวจสอบขอบเขตรันไทม์ราคาแพง
ทำเครื่องหมาย

14

ภาษา JVM และ. NET มีความง่าย:

  1. พวกเขาไม่จำเป็นต้องทำงานกับฮาร์ดแวร์โดยตรง
  2. พวกเขาจะต้องทำงานกับระบบเดสก์ท็อปและเซิร์ฟเวอร์ที่ทันสมัยหรืออุปกรณ์ที่คล้ายกันอย่างสมเหตุสมผลหรืออย่างน้อยอุปกรณ์ที่ออกแบบมาสำหรับพวกเขา
  3. พวกเขาสามารถกำหนดเก็บขยะสำหรับหน่วยความจำทั้งหมดและบังคับให้เริ่มต้นจึงได้รับความปลอดภัยของตัวชี้
  4. พวกเขาได้รับการระบุโดยนักแสดงคนเดียวซึ่งให้การปฏิบัติที่ชัดเจนเพียงครั้งเดียว
  5. พวกเขาเลือกความปลอดภัยมากกว่าประสิทธิภาพ

มีจุดที่ดีสำหรับการเลือกว่า:

  1. การเขียนโปรแกรมระบบเป็นเกมที่แตกต่างกันอย่างสิ้นเชิงและการเพิ่มประสิทธิภาพของการเขียนโปรแกรมแอพพลิเคชั่นอย่างไม่น่าเชื่อแทนนั้นสมเหตุสมผล
  2. เป็นที่ยอมรับว่ามีฮาร์ดแวร์ที่แปลกใหม่น้อยกว่าตลอดเวลา แต่ระบบฝังตัวเล็ก ๆ อยู่ที่นี่เพื่ออยู่ต่อ
  3. GC เหมาะอย่างยิ่งสำหรับทรัพยากรที่ไม่สามารถเข้ากันได้และซื้อขายพื้นที่เพิ่มเติมเพื่อประสิทธิภาพที่ดี และการกำหนดค่าเริ่มต้นบังคับส่วนใหญ่ (แต่ไม่เกือบทั้งหมด) สามารถปรับให้เหมาะสมได้
  4. มีข้อได้เปรียบในการแข่งขันที่มากขึ้น แต่คณะกรรมการหมายถึงการประนีประนอม
  5. ทุกคนขอบเขตการตรวจสอบ- ทำเพิ่มขึ้นถึงแม้ว่าส่วนใหญ่สามารถที่ดีที่สุดออกไป การตรวจสอบตัวชี้ Null ส่วนใหญ่สามารถทำได้โดยการดักจับการเข้าถึงสำหรับค่าใช้จ่ายเป็นศูนย์ด้วยพื้นที่ที่อยู่เสมือนแม้ว่าการเพิ่มประสิทธิภาพยังคงถูกยับยั้ง

เมื่อมีการระบุ escape-hatches ผู้ที่เชิญพฤติกรรมที่ไม่ได้กำหนดเต็มรูปแบบกลับมา แต่อย่างน้อยพวกมันมักใช้ในการเหยียดสั้น ๆ ไม่กี่ครั้งเท่านั้นซึ่งง่ายต่อการตรวจสอบด้วยตนเอง


3
จริง ฉันโปรแกรมใน C # สำหรับงานของฉัน ทุกครั้งที่ฉันไปถึงหนึ่งในค้อนที่ไม่ปลอดภัย ( unsafeคำหลักหรือคุณลักษณะในSystem.Runtime.InteropServices) โดยการเก็บสิ่งนี้ไว้กับโปรแกรมเมอร์ไม่กี่คนที่รู้วิธีการแก้ไขข้อบกพร่องของสิ่งที่ไม่ได้รับการจัดการและอีกครั้งที่สิ่งเหล่านั้นเป็นเรื่องที่ปฏิบัติได้ เป็นเวลามากกว่า 10 ปีแล้วที่ค้อนไม่ปลอดภัยที่เกี่ยวข้องกับการปฏิบัติงานครั้งล่าสุด แต่บางครั้งคุณต้องทำเพราะไม่มีวิธีอื่นที่แท้จริง
โจชัว

19
ฉันมักจะทำงานบนแพลตฟอร์มจากอุปกรณ์อะนาล็อกที่ sizeof (ถ่าน) == sizeof (สั้น) == sizeof (int) == sizeof (ลอย) == 1 นอกจากนี้ยังเพิ่ม saturating (ดังนั้น INT_MAX + 1 == INT_MAX) และสิ่งที่ดีเกี่ยวกับ C คือฉันสามารถมีคอมไพเลอร์ที่สอดคล้องซึ่งสร้างรหัสที่สมเหตุสมผล หากภาษาที่ได้รับคำสั่งว่า twos เติมเต็มด้วยการพันรอบแล้วทุก ๆ การทดสอบก็จะจบลงด้วยการทดสอบและสาขาสิ่งที่ไม่ใช่ผู้เริ่มต้นในส่วนที่เน้น DSP นี่คือส่วนการผลิตปัจจุบัน
Dan Mills

5
@BenVoigt พวกเราบางคนอาศัยอยู่ในโลกที่คอมพิวเตอร์ขนาดเล็กอาจมีรหัสพื้นที่ 4k, สแต็กโทร / ส่งคืนระดับ 8 คงที่, 64 ไบต์ของ RAM, นาฬิกา 1MHz และค่าใช้จ่าย <$ 0.20 ในปริมาณ 1,000 โทรศัพท์มือถือที่ทันสมัยเป็นพีซีขนาดเล็กที่มีพื้นที่เก็บข้อมูลไม่ จำกัด สำหรับทุกความต้องการและสามารถนำมาใช้เป็นพีซีได้ ไม่ใช่ทุกคนในโลกที่มีหลายคอร์และไม่มีข้อ จำกัด แบบเรียลไทม์อย่างหนัก
Dan Mills

2
@DanMills: ไม่ได้พูดถึงโทรศัพท์มือถือที่ทันสมัยกับโปรเซสเซอร์ Arm Cortex A พูดถึง "ฟีเจอร์โฟน" ประมาณปี 2002 ใช่ 192kB ของ SRAM มีมากกว่า 64 ไบต์ (ซึ่งไม่ใช่ "เล็ก" แต่ "เล็ก") แต่ 192kB นั้นไม่ได้ถูกเรียกอย่างแม่นยำว่าเดสก์ท็อปหรือเซิร์ฟเวอร์ "ทันสมัย" เป็นเวลา 30 ปี นอกจากนี้ในวันนี้ 20 เซ็นต์คุณจะได้รับ MSP430 ที่มี SRAM มากกว่า 64 ไบต์
Ben Voigt

2
@ BenVoigt 192kB อาจไม่ใช่เดสก์ท็อปในช่วง 30 ปีที่ผ่านมา แต่ฉันสามารถรับรองได้ว่ามันเพียงพอที่จะให้บริการหน้าเว็บทั้งหมดซึ่งฉันจะโต้แย้งทำให้สิ่งนี้เป็นเซิร์ฟเวอร์โดยคำจำกัดความของคำว่า ความจริงก็คือว่าเป็นจำนวนที่สมเหตุสมผล (ใจกว้าง) แม้กระทั่งจำนวน ram สำหรับแอปพลิเคชันที่ฝังตัวจำนวนมากซึ่งมักจะมีเว็บเซิร์ฟเวอร์การกำหนดค่า แน่นอนว่าฉันอาจไม่ได้ใช้ amazon กับมัน แต่ฉันก็อาจจะใช้ตู้เย็นที่มี Crapware ของ IOT ในแกนกลาง (ด้วยเวลาและพื้นที่ว่าง) ไม่จำเป็นต้องมีใครตีความหรือภาษา JIT!
Dan Mills

8

Java และ C # โดดเด่นด้วยผู้ขายที่โดดเด่นอย่างน้อยในช่วงต้นของการพัฒนาของพวกเขา (Sun และ Microsoft ตามลำดับ) C และ C ++ แตกต่างกัน พวกเขามีการใช้งานการแข่งขันหลายครั้งตั้งแต่ต้น C โดยเฉพาะวิ่งบนแพลตฟอร์มฮาร์ดแวร์ที่แปลกใหม่เช่นกัน เป็นผลให้มีการเปลี่ยนแปลงระหว่างการใช้งาน คณะกรรมการ ISO ที่มาตรฐาน C และ C ++ สามารถเห็นด้วยกับตัวหารร่วมขนาดใหญ่ แต่ที่ขอบที่การใช้งานแตกต่างมาตรฐานออกจากห้องสำหรับการดำเนินงาน

นี่ก็เป็นเพราะการเลือกพฤติกรรมหนึ่งอาจมีราคาแพงในสถาปัตยกรรมฮาร์ดแวร์ที่มีอคติต่อตัวเลือกอื่น - endianness เป็นตัวเลือกที่ชัดเจน


อะไร“ส่วนร่วมใหญ่” หมายถึงตัวอักษร ? คุณกำลังพูดถึงชุดย่อยหรือ supersets? คุณจริงๆหมายถึงปัจจัยที่เพียงพอในการร่วมกัน? นี่เป็นตัวคูณร่วมที่น้อยที่สุดหรือปัจจัยร่วมที่ยิ่งใหญ่ที่สุดหรือไม่? นี่เป็นสิ่งที่สับสนสำหรับเราหุ่นยนต์ที่ไม่พูดภาษาต่างด้าวเพียงคณิตศาสตร์ :)
tchrist

@tchrist: พฤติกรรมที่พบบ่อยคือเซตย่อย แต่เซ็ตย่อยนี้ค่อนข้างเป็นนามธรรม ในหลายพื้นที่ที่ไม่ได้ระบุโดยมาตรฐานทั่วไปการใช้งานจริงจะต้องเลือก ตอนนี้ตัวเลือกบางอย่างนั้นค่อนข้างชัดเจนและดังนั้นจึงมีการกำหนดการใช้งาน แต่ตัวเลือกอื่น ๆ นั้นคลุมเครือกว่า ตัวอย่างโครงร่างหน่วยความจำขณะใช้งานจริง: จะต้องมีตัวเลือก แต่ยังไม่ชัดเจนว่าคุณจะจัดทำเอกสารอย่างไร
MSalters

2
C ดั้งเดิมทำโดยผู้ชายคนหนึ่ง มันมี UB มากมายโดยการออกแบบ สิ่งต่าง ๆ แย่ลงอย่างมากเมื่อ C กลายเป็นที่นิยม แต่ UB อยู่ที่นั่นตั้งแต่เริ่มต้น Pascal และ Smalltalk มี UB น้อยกว่ามากและได้รับการพัฒนาในเวลาเดียวกัน ข้อได้เปรียบหลักที่ C มีก็คือมันง่ายมากที่จะพอร์ต - ปัญหาการพกพาทั้งหมดได้รับการมอบหมายให้โปรแกรมเมอร์โปรแกรม: P ฉันได้ ported คอมไพเลอร์ C ง่าย ๆ กับ CPU ของฉัน (เสมือน); ทำอะไรเช่น LISP หรือ Smalltalk จะได้รับความพยายามมากขึ้น (แม้ว่าฉันจะมีต้นแบบที่ จำกัด สำหรับ. NET runtime :)
Luaan

@ Luaan: นั่นจะเป็น Kernighan หรือ Ritchie ไหม และไม่มันไม่ได้มีพฤติกรรมที่ไม่ได้กำหนด ฉันรู้ว่าฉันมีเอกสารแปล AT & T stenciled compiler ดั้งเดิมบนโต๊ะทำงานของฉัน การใช้งานทำสิ่งที่มันทำ ไม่มีความแตกต่างระหว่างพฤติกรรมที่ไม่ระบุและไม่ได้กำหนด
MSalters

4
@MStersters Ritchie เป็นคนแรก Kernighan เข้าร่วมเท่านั้น (ไม่มาก) ในภายหลัง มันไม่ได้มี "พฤติกรรมที่ไม่ได้กำหนด" เพราะคำนั้นยังไม่มีอยู่ แต่มันก็มีพฤติกรรมแบบเดียวกันกับที่ในปัจจุบันเรียกว่าไม่ได้กำหนด เนื่องจาก C ไม่มีสเปคแม้แต่ "unspecified" ก็ยังยืดอีก :) มันเป็นเพียงบางสิ่งที่คอมไพเลอร์ไม่สนใจและรายละเอียดก็ขึ้นอยู่กับโปรแกรมเมอร์แอปพลิเคชัน มันไม่ได้ออกแบบมาเพื่อผลิตแอพพลิเคชั่นแบบพกพามีเพียงคอมไพเลอร์เท่านั้นที่ง่ายต่อการต่อพอร์ต
Luaan

6

เหตุผลที่แท้จริงเกิดขึ้นจากความแตกต่างพื้นฐานในเจตนาระหว่าง C และ C ++ ในมือข้างหนึ่งและ Java และ C # (สำหรับตัวอย่างเพียงสองสาม) ที่อื่น ๆ ด้วยเหตุผลทางประวัติศาสตร์การสนทนาส่วนใหญ่ที่นี่พูดถึง C มากกว่า C ++ แต่ (อย่างที่คุณอาจทราบแล้ว) C ++ เป็นผู้สืบทอดโดยตรงของ C ดังนั้นสิ่งที่บอกว่าเกี่ยวกับ C ใช้กับ C ++ อย่างเท่าเทียมกัน

แม้ว่าพวกเขาจะลืมไปแล้ว (และบางครั้งการดำรงอยู่ของพวกเขาก็ถูกปฏิเสธ) แต่รุ่นแรก ๆ ของ UNIX ถูกเขียนด้วยภาษาแอสเซมบลี จุดประสงค์ดั้งเดิมของ C ส่วนใหญ่ (ถ้าไม่ใช่เพียงอย่างเดียว) คือพอร์ต UNIX จากภาษาแอสเซมบลีไปยังภาษาระดับสูงกว่า จุดประสงค์ส่วนหนึ่งคือการเขียนระบบปฏิบัติการให้มากที่สุดเท่าที่จะเป็นไปได้ในภาษาระดับที่สูงขึ้น - หรือมองจากอีกด้านหนึ่งเพื่อลดจำนวนที่ต้องเขียนด้วยภาษาแอสเซมบลี

เพื่อให้บรรลุผลนั้น C จำเป็นต้องให้การเข้าถึงฮาร์ดแวร์ในระดับใกล้เคียงกับภาษาแอสเซมบลี PDP-11 (ตัวอย่างหนึ่ง) แมป I / O ที่ลงทะเบียนไปยังที่อยู่ที่เฉพาะเจาะจง ตัวอย่างเช่นคุณจะอ่านตำแหน่งหน่วยความจำหนึ่งแห่งเพื่อตรวจสอบว่ามีการกดคีย์บนคอนโซลระบบหรือไม่ มีการตั้งค่าบิตหนึ่งในตำแหน่งนั้นเมื่อมีข้อมูลรอการอ่าน จากนั้นคุณจะอ่านไบต์จากตำแหน่งอื่นที่ระบุเพื่อดึงรหัส ASCII ของคีย์ที่ถูกกด

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

เพื่อรองรับการเขียนไดรเวอร์สำหรับอุปกรณ์ดังกล่าว C อนุญาตให้คุณระบุตำแหน่งที่กำหนดเองโดยใช้ชนิดจำนวนเต็มแปลงเป็นตัวชี้และอ่านหรือเขียนตำแหน่งนั้นในหน่วยความจำ

แน่นอนว่านี่เป็นปัญหาที่ค่อนข้างร้ายแรง: ไม่ใช่ว่าทุกเครื่องในโลกจะมีหน่วยความจำวางเหมือนกับ PDP-11 ตั้งแต่ต้นปี 1970 ดังนั้นเมื่อคุณใช้จำนวนเต็มนั้นให้แปลงเป็นตัวชี้แล้วอ่านหรือเขียนผ่านตัวชี้นั้นไม่มีใครสามารถรับประกันได้อย่างสมเหตุสมผลเกี่ยวกับสิ่งที่คุณจะได้รับ สำหรับตัวอย่างที่ชัดเจนการอ่านและการเขียนอาจแมปเพื่อแยกการลงทะเบียนในฮาร์ดแวร์ดังนั้นคุณ (ตรงกันข้ามกับหน่วยความจำปกติ) ถ้าคุณเขียนอะไรแล้วลองอ่านกลับสิ่งที่คุณอ่านอาจไม่ตรงกับสิ่งที่คุณเขียน

ฉันเห็นความเป็นไปได้สองสามอย่างที่เหลืออยู่:

  1. กำหนดอินเทอร์เฟซให้กับฮาร์ดแวร์ที่เป็นไปได้ทั้งหมด - ระบุที่อยู่ที่แน่นอนของสถานที่ทั้งหมดที่คุณอาจต้องการอ่านหรือเขียนเพื่อโต้ตอบกับฮาร์ดแวร์ในทางใดทางหนึ่ง
  2. ห้ามการเข้าถึงในระดับนั้นและประกาศว่าใครก็ตามที่ต้องการทำสิ่งนั้นจำเป็นต้องใช้ภาษาแอสเซมบลี
  3. อนุญาตให้ผู้อื่นทำเช่นนั้น แต่ปล่อยให้พวกเขาอ่าน (เช่น) คู่มือสำหรับฮาร์ดแวร์ที่พวกเขากำลังกำหนดเป้าหมายและเขียนรหัสเพื่อให้พอดีกับฮาร์ดแวร์ที่พวกเขากำลังใช้

ในบรรดาสิ่งเหล่านี้ 1 ดูเหมือนว่าผิดปกติมากพอที่จะไม่คุ้มค่ากับการอภิปรายเพิ่มเติม 2 เป็นการทิ้งความตั้งใจพื้นฐานของภาษาออกไป นั่นทำให้ตัวเลือกที่สามเป็นหลักอย่างเดียวเท่านั้นที่พวกเขาสามารถพิจารณาได้อย่างสมเหตุสมผล

จุดอื่นที่เกิดขึ้นค่อนข้างบ่อยคือขนาดของประเภทจำนวนเต็ม C รับตำแหน่ง "" ที่intควรเป็นขนาดธรรมชาติที่แนะนำโดยสถาปัตยกรรม ดังนั้นถ้าฉันเขียนโปรแกรม VAX แบบint32 บิตน่าจะเป็น 32 บิต แต่ถ้าฉันเขียนโปรแกรม Univac แบบint36 บิตน่าจะเป็น 36 บิต (เป็นต้น) อาจไม่สมเหตุสมผล (และอาจไม่สามารถทำได้) ในการเขียนระบบปฏิบัติการสำหรับคอมพิวเตอร์ 36 บิตโดยใช้ชนิดเท่านั้นที่รับประกันว่าจะมีขนาดทวีคูณ 8 บิต บางทีฉันอาจเป็นเพียงผิวเผิน แต่ดูเหมือนว่าถ้าฉันกำลังเขียนระบบปฏิบัติการสำหรับเครื่อง 36 บิตฉันอาจต้องการใช้ภาษาที่รองรับชนิด 36 บิต

จากมุมมองของภาษาสิ่งนี้นำไปสู่พฤติกรรมที่ไม่ได้กำหนดเพิ่มเติม ถ้าฉันใช้ค่ามากที่สุดที่พอดีกับ 32 บิตจะเกิดอะไรขึ้นเมื่อฉันเพิ่ม 1 สำหรับฮาร์ดแวร์แบบ 32 บิตโดยทั่วไปมันจะพลิก (หรืออาจจะเป็นความผิดพลาดของฮาร์ดแวร์บางอย่าง) ในทางตรงกันข้ามถ้ามันทำงานบนฮาร์ดแวร์ 36 บิตมันจะ ... เพิ่มอีกอัน หากภาษากำลังจะสนับสนุนการเขียนระบบปฏิบัติการคุณไม่สามารถรับประกันได้ว่าพฤติกรรมใด - คุณต้องยอมให้ทั้งขนาดของประเภทและพฤติกรรมของการไหลล้นแตกต่างกันไปในแต่ละแบบ

Java และ C # สามารถเพิกเฉยได้ทั้งหมด พวกเขาไม่ได้มีวัตถุประสงค์เพื่อสนับสนุนการเขียนระบบปฏิบัติการ คุณมีทางเลือกสองทาง หนึ่งคือการทำให้ฮาร์ดแวร์สนับสนุนสิ่งที่พวกเขาต้องการ - เนื่องจากพวกเขาต้องการประเภทที่ 8, 16, 32 และ 64 บิตเพียงแค่สร้างฮาร์ดแวร์ที่รองรับขนาดเหล่านั้น ความเป็นไปได้อื่น ๆ ที่ชัดเจนคือภาษานั้นจะทำงานบนซอฟต์แวร์อื่นที่มีสภาพแวดล้อมที่พวกเขาต้องการโดยไม่คำนึงถึงสิ่งที่ฮาร์ดแวร์พื้นฐานต้องการ

ในกรณีส่วนใหญ่นี่ไม่ใช่ตัวเลือก / หรือตัวเลือกจริงๆ แต่การใช้งานจำนวนมากทำทั้งสองอย่างเล็กน้อย ปกติแล้วคุณจะรัน Java บน JVM ที่รันบนระบบปฏิบัติการ บ่อยครั้งที่ระบบปฏิบัติการเขียนใน C และ JVM ใน C ++ หาก JVM ทำงานบนซีพียู ARM มีโอกาสค่อนข้างดีที่ซีพียูจะรวมส่วนขยาย Jazelle ของ ARM เพื่อปรับแต่งฮาร์ดแวร์ให้ใกล้เคียงกับความต้องการของจาวามากขึ้นดังนั้นความต้องการซอฟต์แวร์ในจาวาน้อยลงและโค้ดจาวาทำงานได้เร็วขึ้น ช้าๆ)

สรุป

C และ C ++ มีพฤติกรรมที่ไม่ได้กำหนดเนื่องจากไม่มีใครกำหนดทางเลือกที่ยอมรับได้ซึ่งอนุญาตให้พวกเขาทำสิ่งที่พวกเขาตั้งใจจะทำ C # และ Java ใช้แนวทางที่แตกต่างกัน แต่วิธีการนั้นเหมาะสมไม่ดี (ถ้าเลย) กับเป้าหมายของ C และ C ++ โดยเฉพาะอย่างยิ่งดูเหมือนจะไม่มีวิธีที่เหมาะสมในการเขียนซอฟต์แวร์ระบบ (เช่นระบบปฏิบัติการ) บนฮาร์ดแวร์ที่เลือกโดยพลการส่วนใหญ่ ทั้งสองมักจะขึ้นอยู่กับสิ่งอำนวยความสะดวกที่มีให้โดยซอฟต์แวร์ระบบที่มีอยู่ (มักเขียนใน C หรือ C ++) เพื่อทำงานของพวกเขา


4

ผู้เขียน C Standard คาดหวังว่าผู้อ่านของพวกเขาจะรับรู้บางสิ่งที่พวกเขาคิดว่าชัดเจนและเป็นเหตุให้ตีพิมพ์ในเหตุผลที่ตีพิมพ์ แต่ไม่ได้พูดอย่างตรงไปตรงมา: คณะกรรมการไม่จำเป็นต้องสั่งนักเขียนคอมไพเลอร์ เนื่องจากลูกค้าควรรู้ดีกว่าคณะกรรมการว่าสิ่งที่พวกเขาต้องการคืออะไร ถ้าเห็นได้ชัดว่าคอมไพเลอร์สำหรับบางประเภทของ plaforms คาดว่าจะดำเนินการสร้างวิธีการบางอย่างไม่มีใครควรใส่ใจว่ามาตรฐานกล่าวว่าสร้างสร้างเรียกพฤติกรรมที่ไม่ได้กำหนด ความล้มเหลวของมาตรฐานในการมอบอำนาจให้ผู้รวบรวมคอมไพเลอร์ประมวลผลส่วนหนึ่งของโค้ดที่เป็นประโยชน์โดยไม่ได้หมายความว่าโปรแกรมเมอร์ควรเต็มใจที่จะซื้อคอมไพเลอร์ที่ไม่มี

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


ฉันรู้สึกว่าคุณได้อธิบายบางสิ่งบางอย่างที่สำคัญที่นี่ แต่มันหนีฉันไป คุณช่วยชี้แจงคำตอบของคุณได้ไหม? โดยเฉพาะย่อหน้าที่สอง: มันบอกว่าเงื่อนไขตอนนี้และเงื่อนไขก่อนหน้านั้นแตกต่างกัน แต่ฉันไม่เข้าใจ สิ่งที่เปลี่ยนแปลงไปอย่างแน่นอน? นอกจากนี้ "วิธี" ก็แตกต่างไปจากก่อนหน้านี้ อาจอธิบายด้วยหรือไม่
anatolyg

4
ดูเหมือนว่าแคมเปญของคุณจะแทนที่พฤติกรรมที่ไม่ได้กำหนดทั้งหมดด้วยพฤติกรรมที่ไม่ระบุหรือสิ่งที่มีข้อ จำกัด มากกว่านั้นยังคงแข็งแกร่งอยู่
Deduplicator

1
@anatolyg: หากคุณยังไม่ได้อ่านเอกสาร C Rationale (ประเภท C99 Rationale ใน Google) หน้า 11 บรรทัด 23-29 พูดคุยเกี่ยวกับ "ตลาด" และหน้า 13 บรรทัด 5-8 พูดคุยเกี่ยวกับสิ่งที่มีวัตถุประสงค์เกี่ยวกับการพกพา คุณคิดว่าเจ้านายของ บริษัท คอมไพเลอร์เชิงพาณิชย์จะตอบสนองอย่างไรถ้าผู้เขียนคอมไพเลอร์บอกโปรแกรมเมอร์ที่บ่นว่าเครื่องมือเพิ่มประสิทธิภาพยากจนรหัสที่คอมไพเลอร์อื่นทุกคนจัดการอย่างมีประโยชน์ ปฏิเสธที่จะให้การสนับสนุนเพราะนั่นจะเป็นการส่งเสริมอย่างต่อเนื่อง ...
supercat

1
... ใช้โครงสร้างเช่นนี้หรือไม่? มุมมองดังกล่าวปรากฏชัดเจนบนบอร์ดสนับสนุนของเสียงดังกราวและ gcc และทำหน้าที่ขัดขวางการพัฒนาอินทรินที่สามารถเอื้อต่อการเพิ่มประสิทธิภาพได้ง่ายและปลอดภัยกว่าภาษา gcc และเสียงดังกราวที่ต้องการสนับสนุน
supercat

1
@supercat: คุณกำลังหายใจไม่ออกบ่นกับผู้ขายคอมไพเลอร์ ทำไมไม่ส่งข้อกังวลของคุณไปที่คณะกรรมการด้านภาษา? หากพวกเขาเห็นด้วยกับคุณจะมีการออก errata ซึ่งคุณสามารถใช้เพื่อเอาชนะทีมคอมไพเลอร์เหนือหัว และกระบวนการนั้นเร็วกว่าการพัฒนาภาษาเวอร์ชันใหม่ แต่ถ้าพวกเขาไม่เห็นด้วยคุณอย่างน้อยก็จะได้รับเหตุผลที่แท้จริงในขณะที่ผู้เขียนคอมไพเลอร์กำลังจะทำซ้ำ (ซ้ำไปซ้ำมา) "เราไม่ได้กำหนดรหัสนั้นเสียการตัดสินใจของคณะกรรมการภาษาและเรา ติดตามการตัดสินใจของพวกเขา "
Ben Voigt

3

C ++ และ c ทั้งสองมีมาตรฐานอธิบาย (รุ่น ISO อยู่แล้ว)

ซึ่งมีอยู่เพียงเพื่ออธิบายวิธีการทำงานของภาษาและเพื่อให้การอ้างอิงเดียวเกี่ยวกับสิ่งที่เป็นภาษา โดยทั่วไปผู้จำหน่ายคอมไพเลอร์และผู้เขียนไลบรารีจะเป็นผู้นำและคำแนะนำบางอย่างจะรวมอยู่ในมาตรฐาน ISO หลัก

Java และ C # (หรือ Visual C # ซึ่งผมถือว่าคุณหมายถึง) มีกำหนดมาตรฐาน พวกเขาบอกคุณว่ามีอะไรในภาษาที่แน่นอนล่วงหน้าวิธีการทำงานและสิ่งที่ถือว่าเป็นพฤติกรรมที่ได้รับอนุญาต

สำคัญกว่านั้น Java จริง ๆ แล้วมี "การใช้งานอ้างอิง" ใน Open-JDK (ฉันคิดว่าRoslynนับเป็นการนำการอ้างอิง Visual C # ไปใช้ แต่ไม่พบแหล่งที่มาสำหรับสิ่งนั้น)

ในกรณีของ Java หากมีความกำกวมในมาตรฐานและ Open-JDK จะเป็นวิธีที่แน่นอน วิธีที่ Open-JDK เป็นมาตรฐาน


สถานการณ์เลวร้ายยิ่งกว่านั้น: ฉันไม่คิดว่าคณะกรรมการจะได้รับฉันทามติเป็นเอกฉันท์เกี่ยวกับว่ามันควรจะเป็นคำอธิบายหรือกำหนด
supercat

1

พฤติกรรมที่ไม่ได้กำหนดช่วยให้คอมไพเลอร์สามารถสร้างโค้ดที่มีประสิทธิภาพมากในสถาปัตยกรรมที่หลากหลาย คำตอบของ Erik กล่าวถึงการปรับให้เหมาะสม แต่มันไปไกลกว่านั้น

ตัวอย่างเช่นการโอเวอร์โฟลว์ที่ลงนามแล้วเป็นพฤติกรรมที่ไม่ได้กำหนดใน C ในทางปฏิบัติคอมไพเลอร์คาดว่าจะสร้าง opcode ที่เพิ่มการลงชื่อแบบง่ายเพื่อให้ซีพียูสามารถดำเนินการได้

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

นั่นเป็นเหตุผลสำหรับพฤติกรรมที่ไม่ได้กำหนดใน C และทำไมสิ่งต่าง ๆ เช่นขนาดที่intแตกต่างกันระหว่างระบบ จะขึ้นอยู่กับสถาปัตยกรรมและโดยทั่วไปเลือกให้เป็นที่เร็วที่สุดชนิดข้อมูลที่มีประสิทธิภาพมากที่สุดที่มีขนาดใหญ่กว่าIntchar

ย้อนกลับไปเมื่อ C เป็นสิ่งใหม่ข้อควรพิจารณาเหล่านี้มีความสำคัญ คอมพิวเตอร์มีพลังน้อยมักมีความเร็วในการประมวลผลและหน่วยความจำ จำกัด C ถูกใช้ในที่ที่มีความสำคัญต่อประสิทธิภาพการทำงานและนักพัฒนาคาดว่าจะเข้าใจว่าคอมพิวเตอร์ทำงานได้ดีพอที่จะรู้ว่าพฤติกรรมที่ไม่ได้กำหนดเหล่านี้จะเกิดขึ้นจริงในระบบใดระบบหนึ่งของพวกเขา

ภาษาที่ใหม่กว่าเช่น Java และ C # ที่ต้องการกำจัดพฤติกรรมที่ไม่ได้กำหนดเหนือประสิทธิภาพดิบ


-5

เรียกได้ว่า Java ก็มีเช่นกัน สมมติว่าคุณให้ผู้เปรียบเทียบไม่ถูกต้องกับ Arrays.sort มันสามารถโยนยกเว้นมันตรวจพบมัน มิฉะนั้นจะเรียงลำดับอาร์เรย์ด้วยวิธีที่ไม่รับประกันว่าจะเป็นแบบใดโดยเฉพาะ

ในทำนองเดียวกันถ้าคุณปรับเปลี่ยนตัวแปรจากผลลัพธ์ของเธรดต่างๆก็ไม่แน่นอนเช่นกัน

C ++ เป็นไปต่อไปเพื่อให้สถานการณ์ที่ไม่ได้กำหนดเพิ่มเติม (หรือมากกว่า Java ตัดสินใจที่จะกำหนดการดำเนินการเพิ่มเติม) และมีชื่อสำหรับมัน


4
นั่นไม่ใช่พฤติกรรมที่ไม่ได้กำหนดของประเภทที่เรากำลังพูดถึงที่นี่ "ตัวเปรียบเทียบที่ไม่ถูกต้อง" มีสองประเภท: ประเภทที่กำหนดการเรียงลำดับทั้งหมดและประเภทที่ไม่เรียงลำดับ หากคุณให้ตัวเปรียบเทียบที่กำหนดลำดับสัมพันธ์ของรายการอย่างสม่ำเสมอพฤติกรรมนั้นได้รับการกำหนดไว้อย่างดีไม่ใช่เพียงพฤติกรรมที่โปรแกรมเมอร์ต้องการ หากคุณให้ตัวเปรียบเทียบที่ไม่สอดคล้องกับการเรียงลำดับสัมพัทธ์พฤติกรรมยังคงถูกกำหนดไว้อย่างดี: ฟังก์ชันการเรียงลำดับจะส่งผลให้เกิดข้อยกเว้น (ซึ่งอาจเป็นพฤติกรรมที่โปรแกรมเมอร์ไม่ต้องการ)
มาร์ค

2
สำหรับการปรับเปลี่ยนตัวแปรสภาพการแข่งขันโดยทั่วไปจะไม่ถือว่าเป็นพฤติกรรมที่ไม่ได้กำหนด ฉันไม่ทราบรายละเอียดว่า Java จัดการการมอบหมายให้กับข้อมูลที่ใช้ร่วมกันได้อย่างไร แต่เมื่อรู้ถึงปรัชญาทั่วไปของภาษาฉันค่อนข้างแน่ใจว่ามันต้องเป็นอะตอม การกำหนด 53 และ 71 พร้อมกันเพื่อให้aเป็นพฤติกรรมที่ไม่ได้กำหนดหากคุณได้รับ 51 หรือ 73 จากนั้น แต่ถ้าคุณได้ 53 หรือ 71 เท่านั้นมันจะถูกกำหนดอย่างดี
ทำเครื่องหมาย

@ Mark ด้วยข้อมูลจำนวนหนึ่งที่มีขนาดใหญ่กว่าขนาดดั้งเดิมของระบบ (ตัวอย่างเช่นตัวแปร 32 บิตในระบบขนาด word 16 บิต) จึงเป็นไปได้ที่จะมีสถาปัตยกรรมที่ต้องการจัดเก็บแต่ละส่วนแบบ 16 บิตแยกต่างหาก (SIMD เป็นอีกสถานการณ์ที่อาจเกิดขึ้นได้) ในกรณีนี้แม้แต่การกำหนดระดับซอร์สโค้ดอย่างง่ายก็ไม่จำเป็นต้องเป็นอะตอมเว้นแต่คอมไพเลอร์จะได้รับการดูแลเป็นพิเศษเพื่อให้แน่ใจว่าจะดำเนินการตามปกติ
CVN
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.