ความซับซ้อนของการเขียนโปรแกรมที่ไม่มีการจัดการหน่วยความจำคืออะไร?


24

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

ชนิดของข้อบกพร่องที่ที่อยู่ของ GC ดูเหมือนจะ (อย่างน้อยก็ต่อผู้สังเกตการณ์ภายนอก) ซึ่งเป็นสิ่งที่โปรแกรมเมอร์ที่รู้ภาษาไลบรารีแนวคิดแนวความคิดและสำนวนอื่น ๆ แต่ฉันอาจผิด: การจัดการหน่วยความจำด้วยตนเองมีความซับซ้อนหรือไม่


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

ประโยชน์อีกประการหนึ่งคือความปลอดภัยเช่นบัฟเฟอร์โอเวอร์รันมีประโยชน์สูงและช่องโหว่ความปลอดภัยอื่น ๆ เกิดขึ้นจากการจัดการหน่วยความจำ (mis)
StuperUser

7
@StartUser: นั่นไม่เกี่ยวข้องกับต้นกำเนิดของหน่วยความจำ คุณสามารถบัฟเฟอร์หน่วยความจำล้นที่มาจาก GC เพียงแค่ปรับ ความจริงที่ว่าภาษา GC มักจะป้องกันไม่ให้สิ่งนี้เป็นแบบฉากและภาษาที่อยู่เบื้องหลังเทคโนโลยี GC น้อยกว่าสามสิบปีที่คุณกำลังเปรียบเทียบเพื่อให้การป้องกันการบุกรุกเกินบัฟเฟอร์
DeadMG

คำตอบ:


29

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

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

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

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

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

โชคดีที่การจัดการกับปัญหานี้เป็นปัญหาที่แก้ไขได้ คุณไม่จำเป็นต้องมีตัวรวบรวมขยะคุณต้องมีตัวจัดการหน่วยความจำในการดีบัก ตัวอย่างเช่นฉันใช้ Delphi และด้วยไลบรารีภายนอกเดียวและคอมไพเลอร์คำสั่งง่ายๆฉันสามารถตั้งตัวจัดสรรให้เป็น "โหมดการดีบักแบบเต็ม" สิ่งนี้จะเพิ่มค่าใช้จ่ายประสิทธิภาพเล็กน้อย (น้อยกว่า 5%) เป็นการตอบแทนสำหรับการเปิดใช้งานคุณสมบัติบางอย่างที่ติดตามหน่วยความจำที่ใช้ ถ้าฉันปล่อยวัตถุมันจะเติมหน่วยความจำด้วย0x80bytes (รู้จักได้ง่ายในตัวดีบักเกอร์) และถ้าฉันเคยพยายามเรียกวิธีเสมือน (รวมถึง destructor) บนวัตถุที่เป็นอิสระมันจะแจ้งและขัดจังหวะโปรแกรมด้วยกล่องข้อผิดพลาดที่มีร่องรอยสแต็กสาม - เมื่อสร้างวัตถุ เมื่อมันถูกปล่อยให้เป็นอิสระและตอนนี้ฉันอยู่ที่ไหน - บวกกับข้อมูลที่มีประโยชน์อื่น ๆ แล้วทำให้เกิดข้อยกเว้น เห็นได้ชัดว่านี่ไม่เหมาะสำหรับการสร้างรีลีส แต่จะทำการติดตามและแก้ไขปัญหาการอ้างอิงห้อยเล็กน้อย

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

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

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

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

น่าเสียดายที่ฟีดนี้เข้าสู่ปัญหาที่สอง: หน่วยความจำไม่ได้รับการปลดปล่อยเมื่อคุณทำเสร็จแล้ว หากไม่มีการติดตามการทำงานทันทีหลังจากที่คุณเสร็จสิ้นวัตถุก็จะติดไปรอบ ๆ จนกว่าการติดตามต่อไปหรือนานกว่านั้นถ้ามันทำให้มันผ่านรุ่นแรก ในความเป็นจริงหนึ่งในคำอธิบายที่ดีที่สุดของ. NET garbage collector ที่ฉันได้เห็นอธิบายว่าเพื่อให้กระบวนการเร็วที่สุดเท่าที่จะเป็นไปได้ GC ต้องเลื่อนการรวบรวมให้นานที่สุด! ดังนั้นปัญหาของการรั่วไหลของหน่วยความจำคือ "แก้ไข" ค่อนข้างแปลกประหลาดโดยการรั่วไหลของหน่วยความจำให้มากที่สุดเท่าที่จะทำได้! นี่คือสิ่งที่ฉันหมายถึงเมื่อฉันพูดว่า GC เปลี่ยนทุกการจัดสรรให้เป็นความจำรั่ว ในความเป็นจริงไม่มีการรับประกันว่าวัตถุใด ๆ ที่ได้รับจะไม่เคยได้รับการเก็บรวบรวม

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

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

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

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

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


5
ฉันรักคำตอบนี้ - ฉันอ่านมันเรื่อย ๆ ไม่สามารถออกมาพร้อมกับคำพูดที่เกี่ยวข้องดังนั้นสิ่งที่ฉันสามารถพูดได้คือ - ขอบคุณ
vemv

3
ฉันต้องการจะชี้ให้เห็นว่าใช่ GCs มักจะมีหน่วยความจำ "รั่วไหล" (อย่างน้อยก็ซักพัก) แต่นี่ไม่ใช่ปัญหาเพราะมันจะรวบรวมหน่วยความจำเมื่อตัวจัดสรรหน่วยความจำไม่สามารถจัดสรรหน่วยความจำก่อนการรวบรวมได้ ด้วยภาษาที่ไม่ใช่ GC การรั่วไหลจะยังคงรั่วอยู่เสมอซึ่งหมายความว่าคุณสามารถมีหน่วยความจำไม่เพียงพอเนื่องจากหน่วยความจำที่ไม่ได้เก็บไว้มากเกินไป "การรวบรวมขยะคือการปรับให้เหมาะสมก่อนกำหนด" ... GC ไม่ใช่การเพิ่มประสิทธิภาพและไม่ได้ออกแบบโดยคำนึงถึงสิ่งนั้น มิฉะนั้นคำตอบที่ดี
โทมัส Eding

7
@ThomasEding: GC เป็นการเพิ่มประสิทธิภาพอย่างแน่นอน มันปรับให้เหมาะสมสำหรับความพยายามโปรแกรมเมอร์น้อยที่สุดที่ค่าใช้จ่ายของประสิทธิภาพและตัวชี้วัดคุณภาพของโปรแกรมอื่น ๆ
Mason Wheeler

5
ตลกที่คุณชี้ไปที่ตัวติดตามบั๊กของ Mozilla ณ จุดหนึ่งเนื่องจาก Mozilla ได้ข้อสรุปที่แตกต่างออกไป Firefox มีและยังคงมีปัญหาด้านความปลอดภัยมากมายที่มาจากข้อผิดพลาดในการจัดการหน่วยความจำ โปรดทราบว่านี่ไม่เกี่ยวกับความง่ายในการแก้ไขข้อผิดพลาดเมื่อตรวจพบ - โดยปกติแล้วความเสียหายจะเกิดขึ้นตามเวลาที่นักพัฒนาซอฟต์แวร์ทราบถึงปัญหา Mozilla ให้การสนับสนุนภาษาโปรแกรม Rust อย่างแม่นยำเพื่อช่วยป้องกันข้อผิดพลาดดังกล่าวตั้งแต่แรก

1
Rust ไม่ได้ใช้การรวบรวมขยะ แต่ใช้การนับการอ้างอิงอย่างที่เมสันอธิบายไว้เพียงแค่ทำการตรวจสอบเวลาแบบคอมไพล์แทนที่จะใช้ตัวดีบักเพื่อตรวจหาข้อผิดพลาดในเวลาทำงาน ...
Sean Burton

13

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

โปรดจำไว้ว่าเมื่อมีคนคิดว่าไม่ใช่ GC พวกเขาคิดและmalloc freeแต่นี่เป็นความเข้าใจผิดอย่างใหญ่หลวง - คุณจะเปรียบเทียบการจัดการทรัพยากรที่ไม่ใช่ GC ของต้นปี 1970 กับนักสะสมขยะในช่วงปลายยุค 90 เห็นได้ชัดว่าเป็นการเปรียบเทียบที่ไม่เป็นธรรมนักสะสมขยะที่ใช้งานเมื่อใดmallocและfreeถูกออกแบบมาช้าเกินไปที่จะเรียกใช้โปรแกรมที่มีความหมายใด ๆ การเปรียบเทียบบางสิ่งบางอย่างจากช่วงเวลาที่ไม่เท่าเทียมกันเช่นunique_ptrมีความหมายมากขึ้น

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

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

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

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

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


การอภิปรายเพิ่มเติมที่นี่ได้รับการทำความสะอาดแล้ว: ถ้าทุกคนสามารถนำไปสนทนาเพื่ออภิปรายหัวข้อต่อไปได้ฉันก็ยินดีจริงๆ

@DeadMG คุณรู้หรือไม่ว่า combinator ควรจะทำอย่างไร? มันควรจะรวมกัน ตามคำจำกัดความ combinator เป็นฟังก์ชันที่ไม่มีตัวแปรอิสระใด ๆ
SK-logic

2
@ SK-logic: ฉันสามารถเลือกที่จะใช้มันอย่างหมดจดโดยแม่แบบและไม่มีตัวแปรสมาชิกใด ๆ แต่คุณจะไม่สามารถผ่านไปได้ในการปิดซึ่ง จำกัด การใช้งานอย่างมีนัยสำคัญ สนใจที่จะแชทไหม?
DeadMG

@DeadMG ความหมายที่ชัดเจน ไม่มีตัวแปรอิสระ ฉันพิจารณาภาษาใด ๆ ที่ "ทำงานได้ดี" ถ้าเป็นไปได้ที่จะให้คำจำกัดความของ Y-combinator (อย่างถูกต้องไม่ใช่วิธีการของคุณ) ตัวใหญ่ "+" คือถ้าเป็นไปได้ที่จะกำหนดมันผ่านทาง S, K และฉัน combinators ไม่อย่างนั้นภาษาจะไม่แสดงออกมาเพียงพอ
SK-logic

4
@ SK-logic: ทำไมคุณไม่มาแชทเหมือนที่ moderator ถาม นอกจากนี้ Y-combinator ยังเป็น Y-combinator มันทำงานได้หรือไม่ รุ่น H-combinator Y ของ Haskell นั้นเหมือนกับเวอร์ชั่นนี้มันเป็นเพียงแค่สถานะที่แสดงออกมานั้นถูกซ่อนไว้จากคุณ
DeadMG

11

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

ในความเป็นจริงการรั่วไหลของหน่วยความจำค่อนข้างบ่อยในโปรแกรมที่เขียนด้วยภาษา GCed เพราะภาษาเหล่านี้มีแนวโน้มที่จะทำให้โปรแกรมเมอร์ขี้เกียจและทำให้พวกเขาได้รับการรักษาความปลอดภัยที่ผิด ๆ ว่าภาษาจะดูแล (วัตถุ) ไม่ต้องการที่จะคิดอีกต่อไป

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

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

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


4

ตัวรวบรวมขยะไม่ได้ระบุ "บั๊ก" ใด ๆ มันเป็นส่วนที่จำเป็นของความหมายภาษาระดับสูง ด้วย GC มันเป็นไปได้ที่จะกำหนดระดับที่สูงขึ้นของ abstractions เช่นการปิดคำศัพท์และเหมือนกันในขณะที่มีการจัดการหน่วยความจำด้วยตนเอง abstractions เหล่านั้นจะรั่วไหลผูกพันโดยไม่จำเป็นในระดับล่างของการจัดการทรัพยากร

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


1
@Mason Wheeler แม้ C ++ จะใช้รูปแบบการปิดที่ จำกัด มาก แต่การปิดนั้นไม่เหมาะสมนัก
SK-logic

1
คุณผิด. GC ไม่สามารถปกป้องคุณจากข้อเท็จจริงที่ว่าคุณไม่สามารถอ้างถึงตัวแปรสแต็ก และมันก็เป็นเรื่องตลกใน C ++ คุณสามารถใช้ "คัดลอกตัวชี้ไปยังตัวแปรที่จัดสรรแบบไดนามิกซึ่งจะถูกวิธีที่เหมาะสมและถูกทำลายโดยอัตโนมัติ" เช่นกัน
DeadMG

1
@DeadMG คุณไม่เห็นว่ารหัสของคุณมีการรั่วไหลเอนทิตีระดับต่ำผ่านระดับอื่น ๆ ที่คุณสร้างอยู่ด้านบนหรือไม่
SK-logic

1
@ SK-Logic: ตกลงเรามีปัญหาคำศัพท์ คำจำกัดความของคุณเกี่ยวกับ "การปิดตัวจริง" คืออะไรและพวกเขาสามารถทำอะไรได้บ้างที่การปิดของ Delphi ไม่สามารถทำได้? (และรวมถึงสิ่งใดก็ตามที่เกี่ยวกับการจัดการหน่วยความจำในคำจำกัดความของคุณคือการย้ายโพสต์เป้าหมายมาพูดคุยเกี่ยวกับพฤติกรรมไม่ใช่รายละเอียดการนำไปใช้)
Mason Wheeler

1
@ SK-Logic: ... และคุณมีตัวอย่างของสิ่งที่สามารถทำได้ด้วยการปิดแลมบ์ดาที่เรียบง่ายที่ไม่เหมือนใครที่การปิดของ Delphi ไม่สามารถทำได้หรือไม่
Mason Wheeler

2

การจัดการหน่วยความจำของคุณเองนั้นเป็นแหล่งบั๊กที่มีศักยภาพอีกแหล่งหนึ่ง

หากคุณลืมสายfree(หรือสิ่งที่เทียบเท่าเป็นภาษาที่คุณใช้) โปรแกรมของคุณสามารถผ่านการทดสอบทั้งหมด แต่มีหน่วยความจำรั่ว freeและในโปรแกรมที่ซับซ้อนปานกลางมันเป็นเรื่องง่ายมากที่จะมองข้ามการเรียกไปยัง


3
พลาดfreeไม่ได้เป็นสิ่งที่เลวร้ายที่สุด ในช่วงต้นfreeจะมีความเสียหายมากขึ้น
herby

2
และสองครั้งfree!
quant_dev

ฮิฮิ! ฉันจะไปพร้อมกับความคิดเห็นทั้งสองข้อด้านบน ฉันไม่เคยกระทำการฝ่าฝืนข้อใดข้อหนึ่งเหล่านี้ด้วยตัวเอง (เท่าที่ฉันรู้) แต่ฉันเห็นได้ว่าผลกระทบอาจจะแย่ขนาดนั้น คำตอบจาก quant_dev บอกว่ามันทั้งหมด - ข้อผิดพลาดในการจัดสรรหน่วยความจำและการจัดสรรจัดสรรนั้นยากที่จะค้นหาและแก้ไข
Dawood พูดว่าคืนสถานะโมนิก้า

1
นี่คือการเข้าใจผิด คุณกำลังเปรียบเทียบ "ต้นปี 1970" กับ "ปลายปี 1990" GCs ที่มีอยู่ ณ เวลานั้นmallocและfreeเป็นวิธีที่ไม่ใช่ GC ที่จะไปช้าเกินไปที่จะเป็นประโยชน์สำหรับทุกสิ่ง คุณต้องเปรียบเทียบกับวิธีการที่ไม่ใช่ GC ที่ทันสมัยเช่น RAII
DeadMG

2
@DeadMG RAII ไม่ใช่การจัดการหน่วยความจำด้วยตนเอง
quant_dev

2

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


1

ฉันคิดว่าการรวบรวมขยะได้รับเครดิตมากมายสำหรับการปรับปรุงภาษาที่ไม่เกี่ยวข้องกับ GC นอกเหนือจากการเป็นส่วนหนึ่งของความคืบหน้าครั้งใหญ่

ข้อดีอย่างหนึ่งของ GC ที่ฉันรู้จักก็คือคุณสามารถตั้งวัตถุให้เป็นอิสระในโปรแกรมของคุณและรู้ว่ามันจะหายไปเมื่อทุกคนทำมันเสร็จ คุณสามารถส่งต่อไปยังวิธีอื่นของชั้นเรียนและไม่ต้องกังวลกับมัน คุณไม่สนใจว่ามันจะถูกส่งต่อไปยังวิธีใดหรือคลาสอื่นอ้างอิง (การรั่วไหลของหน่วยความจำเป็นความรับผิดชอบของคลาสที่อ้างอิงถึงวัตถุไม่ใช่คลาสที่สร้างขึ้น)

หากไม่มี GC คุณจะต้องติดตามวงจรชีวิตทั้งหมดของหน่วยความจำที่จัดสรร ทุกครั้งที่คุณส่งที่อยู่ขึ้นหรือลงจากรูทีนย่อยที่สร้างขึ้นคุณจะมีการอ้างอิงนอกหน่วยควบคุมไปยังหน่วยความจำนั้น ในวันที่ไม่ดีแม้จะมีเพียงเธรดเดียวการเรียกซ้ำและระบบปฏิบัติการ ornery (Windows NT) ทำให้ฉันไม่สามารถควบคุมการเข้าถึงหน่วยความจำที่จัดสรรไว้ได้ ฉันต้องใช้วิธีฟรีในระบบการจัดสรรของตัวเองเพื่อให้บล็อกหน่วยความจำอยู่ครู่หนึ่งจนกว่าการอ้างอิงทั้งหมดจะถูกล้างออก เวลาถือเป็นการคาดเดาที่บริสุทธิ์ แต่ก็ใช้งานได้

นั่นเป็นประโยชน์ GC เพียงอย่างเดียวที่ฉันรู้ แต่ก็ขาดไม่ได้ ฉันไม่คิดว่า OOP ใด ๆ จะบินไปโดยไม่มีมัน


1
เมื่อไม่นานมานี้เดลฟี่และซีพลัสพลัสต่างก็ประสบความสำเร็จในการใช้ภาษา OOP โดยที่ไม่มี GC สิ่งที่คุณต้องป้องกัน "อ้างอิงจากการควบคุม" คือวินัยเล็กน้อย หากคุณเข้าใจหลักการความเป็นเจ้าของเดียว (ดูคำตอบของฉัน) ปัญหาที่คุณกำลังพูดถึงที่นี่จะกลายเป็นปัญหาที่ไม่ใช่ทั้งหมด
Mason Wheeler

@MasonWheeler: เมื่อถึงเวลาที่วัตถุเจ้าของจะต้องได้รับการปลดปล่อยก็ต้องรู้สถานที่ทั้งหมดที่มีการอ้างอิงวัตถุของมัน การบำรุงรักษาข้อมูลนี้และใช้เพื่อลบการอ้างอิงดูเหมือนว่าเป็นงานที่แย่มากสำหรับฉัน ฉันมักจะพบว่าการอ้างอิงยังไม่สามารถล้างได้เลย ฉันต้องทำเครื่องหมายเจ้าของว่าถูกลบจากนั้นนำมันมาใช้เป็นระยะเพื่อดูว่ามันสามารถปลดปล่อยตัวเองได้อย่างปลอดภัยหรือไม่ ฉันไม่เคยใช้ Delphi มาก่อน แต่สำหรับการเสียสละเล็กน้อยในประสิทธิภาพการดำเนินการ C # / Java ทำให้ฉันมีเวลาในการพัฒนาที่สูงขึ้นด้วย C ++ (ไม่ใช่ทั้งหมดเนื่องจาก GC แต่ช่วยได้)
RalphChapin

1

การรั่วไหลทางกายภาพ

ชนิดของข้อบกพร่องที่ที่อยู่ของ GC ดูเหมือนจะ (อย่างน้อยก็ต่อผู้สังเกตการณ์ภายนอก) ซึ่งเป็นสิ่งที่โปรแกรมเมอร์ที่รู้ภาษาไลบรารีแนวคิดแนวความคิดและสำนวนอื่น ๆ แต่ฉันอาจผิด: การจัดการหน่วยความจำด้วยตนเองมีความซับซ้อนหรือไม่

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

อย่างไรก็ตามมีเครื่องมือเช่นvalgrindการตรวจจับการรั่วไหลซึ่งจะสังเกตเห็นได้ทันทีในการดำเนินการรหัสเมื่อ / ที่ไหนผิดพลาดดังกล่าวเกิดขึ้นกับสายของรหัสที่แน่นอน เมื่อรวมเข้ากับ CI มันแทบจะเป็นไปไม่ได้เลยที่จะรวมความผิดพลาดดังกล่าวเข้าด้วยกัน ดังนั้นจึงไม่เป็นเรื่องใหญ่สำหรับทีม / กระบวนการที่มีมาตรฐานที่สมเหตุสมผล

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

นอกจากนี้ยังเป็นเรื่องยากโดยไม่มีสิ่งใดที่คล้ายกับรูปแบบหลอกของ GC (การนับการอ้างอิงเช่น) เมื่ออายุการใช้งานของวัตถุต้องขยายออกไปสำหรับการประมวลผลแบบเลื่อน / อะซิงโครนัสในบางรูปแบบ

พอยน์เตอร์ชี้

ปัญหาจริงที่มีรูปแบบการจัดการหน่วยความจำแบบแมนนวลนั้นไม่รั่วไหลสำหรับฉัน มีแอปพลิเคชั่นที่เขียนด้วยภาษา C หรือ C ++ เป็นจำนวนเท่าใดที่ทราบว่ามีการรั่วไหลจริง ๆ เคอร์เนล Linux รั่วหรือไม่ MySQL? CryEngine 3 เวิร์คสเตชั่เสียงดิจิตอลและซินธิไซเซอร์? Java VM มีการรั่วไหลหรือไม่ (ใช้งานในรหัสเนทีฟ) Photoshop?

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

ปัญหาสำหรับฉันคือความปลอดภัยเสมอ แม้ว่าเราจะfreeจำหน่วยความจำผ่านพอยน์เตอร์ได้หากมีพอยน์เตอร์อื่น ๆ เข้ากับรีซอร์สพอยน์เตอร์จะกลายเป็นพอยน์เตอร์

เมื่อเราพยายามเข้าถึงพอยท์เตอร์ของพอยน์เตอร์ที่ห้อยต่องแต่งเราจบลงด้วยพฤติกรรมที่ไม่ได้กำหนดแม้ว่าเกือบจะเป็นการละเมิด segfault / access ที่นำไปสู่ความผิดพลาดที่ยากและทันที

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

... และเป็นเพราะการจัดการทรัพยากรทำได้ยากไม่ว่าคุณจะใช้ GC หรือไม่ก็ตาม ความแตกต่างในทางปฏิบัติมักเกิดจากการรั่ว (GC) หรือการหยุดทำงาน (โดยไม่มี GC) เมื่อพบข้อผิดพลาดที่นำไปสู่การจัดการทรัพยากร

การจัดการทรัพยากร: การรวบรวมขยะ

การจัดการทรัพยากรที่ซับซ้อนนั้นยากกระบวนการด้วยตนเองไม่ว่าจะเกิดอะไรขึ้น GC ไม่สามารถทำอะไรที่นี่โดยอัตโนมัติ

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

ป้อนคำอธิบายรูปภาพที่นี่

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

ป้อนคำอธิบายรูปภาพที่นี่

... แต่เดี๋ยวก่อนเรากำลังใช้การรวบรวมขยะ การอ้างอิงที่แข็งแกร่งถึง Joe ทุกคนจะทำให้เขาอยู่ใกล้ ๆ ดังนั้นเราจึงลบการอ้างอิงถึงเขาออกจากองค์กรที่เขาเป็นสมาชิกอยู่ (ยกเลิกการสมัครสมาชิก)

ป้อนคำอธิบายรูปภาพที่นี่

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

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

อย่างไรก็ตามโปรแกรมก็ไม่ผิดพลาด มันปลอดภัยอย่างสมบูรณ์แบบ มันเป็นเพียงการเพิ่มความจำและ Joe จะยังคงวนเวียนอยู่ สำหรับแอปพลิเคชั่นจำนวนมากลักษณะการรั่วไหลแบบนี้ที่เราเพิ่งโยนหน่วยความจำ / การประมวลผลมากขึ้นและมากขึ้นอาจทำให้เกิดปัญหาฮาร์ดไดรฟ์โดยเฉพาะอย่างยิ่งเนื่องจากหน่วยความจำและกำลังประมวลผลที่เครื่องของเรามี

การจัดการทรัพยากร: คู่มือ

ทีนี้ลองพิจารณาทางเลือกที่เราใช้พอยน์เตอร์กับ Joe และการจัดการหน่วยความจำด้วยตนเองเช่น:

ป้อนคำอธิบายรูปภาพที่นี่

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

ป้อนคำอธิบายรูปภาพที่นี่

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

ป้อนคำอธิบายรูปภาพที่นี่

... อ๊ะเราทำผิดพลาดเหมือนเดิมอีกครั้งและลืมยกเลิกการสมัครสมาชิกนิตยสารของ Joe!

ยกเว้นตอนนี้เรามีตัวชี้ห้อย เมื่อการสมัครสมาชิกนิตยสารพยายามที่จะประมวลผลค่าบริการรายเดือนของ Joe ทั้งโลกจะระเบิด - โดยทั่วไปเราจะได้รับความผิดพลาดอย่างหนักทันที

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

โลกแห่งความจริง

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

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

Crash vs. Leak

ตอนนี้อันไหนแย่กว่ากัน? เกิดความผิดพลาดขึ้นทันทีหรือหน่วยความจำเงียบเงียบที่โจเพียงแค่ก้องกังวาน?

ส่วนใหญ่อาจตอบกลับ แต่สมมติว่าซอฟต์แวร์นี้ออกแบบมาเพื่อให้ทำงานได้นานหลายชั่วโมงอาจเป็นวันและแต่ละวันของ Joe และ Jane ที่เราเพิ่มจะเพิ่มการใช้หน่วยความจำของซอฟต์แวร์ด้วยกิกะไบต์ ไม่ใช่ซอฟต์แวร์ที่มีความสำคัญต่อภารกิจ (เกิดปัญหาไม่ได้ฆ่าผู้ใช้จริง ๆ ) แต่เป็นซอฟต์แวร์ที่มีความสำคัญต่อประสิทธิภาพ

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

ในทางกลับกันหากเป็นซอฟต์แวร์ที่มีความสำคัญต่อภารกิจซึ่งประสิทธิภาพไม่ใช่เป้าหมาย แต่ไม่ล้มเหลวด้วยวิธีการใด ๆ ที่เป็นไปได้การรั่วไหลอาจเป็นที่นิยมมากกว่า

ข้อมูลอ้างอิงที่อ่อนแอ

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

น่าเสียดายที่การอ้างอิงที่อ่อนแอนั้นไม่ได้ใช้งานเกือบเท่าที่ควรจะใช้ดังนั้นบ่อยครั้งที่แอปพลิเคชัน GC ที่ซับซ้อนจำนวนมากอาจมีความเสี่ยงต่อการรั่วไหลแม้ว่าจะมีความผิดพลาดน้อยกว่าแอปพลิเคชัน C ที่ซับซ้อนเช่น

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

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

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


-1

นี่คือรายการปัญหาที่โปรแกรมเมอร์ C ++ เผชิญเมื่อจัดการกับหน่วยความจำ:

  1. ปัญหาการกำหนดขอบเขตเกิดขึ้นในหน่วยความจำที่จัดสรรสแต็ก: อายุการใช้งานไม่ได้ขยายออกไปนอกฟังก์ชั่นที่ได้รับการจัดสรรมีวิธีแก้ไขปัญหาหลักสามประการสำหรับปัญหานี้: หน่วยความจำฮีปและการย้ายจุดจัดสรร .
  2. ปัญหาของ Sizeofอยู่ในการจัดสรรสแต็กและการจัดสรรจากวัตถุภายในและหน่วยความจำที่จัดสรรบางส่วน: ขนาดของบล็อกหน่วยความจำไม่สามารถเปลี่ยนแปลงได้ในรันไทม์ วิธีแก้ไขคือฮีปอาร์เรย์พอยท์เตอร์และไลบรารีและคอนเทนเนอร์
  3. ลำดับของปัญหาที่เกิดขึ้นคือการจัดสรรจากวัตถุภายใน: ชั้นเรียนภายในโปรแกรมจะต้องอยู่ในลำดับที่ถูกต้อง โซลูชันกำลัง จำกัด การพึ่งพาต้นไม้และจัดลำดับชั้นใหม่และไม่ใช้การประกาศล่วงหน้าและตัวชี้และหน่วยความจำฮีปและการใช้การประกาศล่วงหน้า
  4. ปัญหา Inside-Outsideอยู่ในหน่วยความจำที่จัดสรรวัตถุ การเข้าถึงหน่วยความจำภายในวัตถุแบ่งออกเป็นสองส่วนหน่วยความจำบางส่วนอยู่ในวัตถุและหน่วยความจำอื่นอยู่ด้านนอกและโปรแกรมเมอร์จำเป็นต้องเลือกใช้องค์ประกอบหรือการอ้างอิงอย่างถูกต้องตามการตัดสินใจนี้ โซลูชันกำลังทำการตัดสินใจอย่างถูกต้องหรือตัวชี้และหน่วยความจำฮีป
  5. ปัญหาวัตถุที่เกิดซ้ำอยู่ในหน่วยความจำที่จัดสรรวัตถุ ขนาดของวัตถุจะไม่มีที่สิ้นสุดหากวางวัตถุเดียวกันไว้ในตัวของมันเองและการแก้ปัญหาคือการอ้างอิงหน่วยความจำฮีพและพอยน์เตอร์
  6. ปัญหาการติดตามความเป็นเจ้าของอยู่ในหน่วยความจำที่จัดสรรฮีพตัวชี้ที่มีที่อยู่ของหน่วยความจำที่จัดสรรฮีปจะต้องผ่านจากจุดจัดสรรไปยังจุดจัดสรรคืน โซลูชันคือหน่วยความจำที่จัดสรรโดยสแต็กหน่วยความจำที่จัดสรรวัตถุ, auto_ptr, shared_ptr, unique_ptr, คอนเทนเนอร์ stdlib
  7. ปัญหาการทำซ้ำความเป็นเจ้าของอยู่ในหน่วยความจำที่จัดสรรฮีป: การจัดสรรคืนสามารถทำได้เพียงครั้งเดียวเท่านั้น โซลูชันคือหน่วยความจำที่จัดสรรแบบกองซ้อนหน่วยความจำที่จัดสรรวัตถุ, auto_ptr, shared_ptr, unique_ptr, คอนเทนเนอร์ stdlib
  8. ปัญหาตัวชี้ Nullอยู่ในหน่วยความจำที่จัดสรรฮีป: พอยน์เตอร์ได้รับอนุญาตให้เป็นค่า NULL ซึ่งทำให้การดำเนินการล้มเหลวในรันไทม์จำนวนมาก โซลูชันคือหน่วยความจำสแต็คหน่วยความจำที่จัดสรรโดยวัตถุและการวิเคราะห์พื้นที่ฮีปและการอ้างอิงอย่างระมัดระวัง
  9. ปัญหาการรั่วไหลของหน่วยความจำอยู่ในหน่วยความจำที่จัดสรรฮีป: ลืมโทรลบสำหรับบล็อกหน่วยความจำที่จัดสรรไว้ทั้งหมด การแก้ปัญหาเป็นเครื่องมือเช่น valgrind
  10. ปัญหาล้นสแต็คสำหรับการเรียกใช้ฟังก์ชันแบบเรียกซ้ำซึ่งใช้หน่วยความจำสแต็ก ขนาดปกติของสแต็กจะถูกกำหนดอย่างสมบูรณ์ในเวลารวบรวมยกเว้นกรณีของอัลกอริทึมแบบเรียกซ้ำ การกำหนดขนาดสแต็กของระบบปฏิบัติการที่ไม่ถูกต้องมักทำให้เกิดปัญหานี้เนื่องจากไม่มีวิธีการวัดขนาดที่ต้องการของพื้นที่สแต็ก

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


1
-1: ไม่ใช่คำตอบสำหรับคำถาม
Sjoerd
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.