มีเคล็ดลับทั่วไปอะไรบ้างเพื่อให้แน่ใจว่าหน่วยความจำในโปรแกรม C ++ ไม่รั่วไหล ฉันจะทราบได้อย่างไรว่าใครควรเพิ่มหน่วยความจำที่ได้รับการจัดสรรแบบไดนามิก
มีเคล็ดลับทั่วไปอะไรบ้างเพื่อให้แน่ใจว่าหน่วยความจำในโปรแกรม C ++ ไม่รั่วไหล ฉันจะทราบได้อย่างไรว่าใครควรเพิ่มหน่วยความจำที่ได้รับการจัดสรรแบบไดนามิก
คำตอบ:
แทนที่จะจัดการหน่วยความจำด้วยตนเองให้พยายามใช้ตัวชี้อัจฉริยะหากทำได้
ลองดูที่ที่lib Boost , TR1และตัวชี้สมาร์ท
นอกจากนี้ยังชี้สมาร์ทตอนนี้เป็นส่วนหนึ่งของมาตรฐาน C ++ ที่เรียกว่าC ++ 11
ฉันรับรองคำแนะนำทั้งหมดเกี่ยวกับ RAII และตัวชี้อัจฉริยะอย่างละเอียด แต่ฉันต้องการเพิ่มเคล็ดลับระดับสูงขึ้นเล็กน้อย: หน่วยความจำที่ง่ายที่สุดในการจัดการคือหน่วยความจำที่คุณไม่เคยจัดสรร ซึ่งแตกต่างจากภาษาเช่น C # และ Java ที่ทุกอย่างเป็นข้อมูลอ้างอิงใน C ++ คุณควรวางวัตถุบนสแต็กทุกครั้งที่ทำได้ ดังที่ฉันได้เห็นหลายคน (รวมถึง Dr Stroustrup) ชี้ให้เห็นว่าสาเหตุหลักที่การเก็บขยะไม่เคยได้รับความนิยมใน C ++ คือ C ++ ที่เขียนอย่างดีนั้นไม่ได้สร้างขยะมากนักในตอนแรก
ไม่ต้องเขียน
Object* x = new Object;
หรือแม้กระทั่ง
shared_ptr<Object> x(new Object);
เมื่อคุณสามารถเขียนได้
Object x;
โพสต์นี้ดูเหมือนจะซ้ำ แต่ใน C ++ รูปแบบพื้นฐานที่สุดที่จะทราบว่าเป็นRAII
เรียนรู้การใช้ตัวชี้อัจฉริยะทั้งจาก boost, TR1 หรือแม้แต่ auto_ptr ที่ต่ำ (แต่มักจะมีประสิทธิภาพเพียงพอ) (แต่คุณต้องรู้ข้อ จำกัด )
RAII เป็นพื้นฐานของความปลอดภัยข้อยกเว้นและการกำจัดทรัพยากรใน C ++ และไม่มีรูปแบบอื่นใด (แซนวิช ฯลฯ ) จะให้ทั้งสองอย่าง (และโดยส่วนใหญ่แล้วจะไม่ให้คุณเลย)
ดูการเปรียบเทียบรหัส RAII และไม่ใช่ RAII ด้านล่าง:
void doSandwich()
{
T * p = new T() ;
// do something with p
delete p ; // leak if the p processing throws or return
}
void doRAIIDynamic()
{
std::auto_ptr<T> p(new T()) ; // you can use other smart pointers, too
// do something with p
// WON'T EVER LEAK, even in case of exceptions, returns, breaks, etc.
}
void doRAIIStatic()
{
T p ;
// do something with p
// WON'T EVER LEAK, even in case of exceptions, returns, breaks, etc.
}
เพื่อสรุป (หลังจากความคิดเห็นจากOgre Psalm33 ) RAII อาศัยแนวคิดสามประการ:
ซึ่งหมายความว่าในรหัส C ++ ที่ถูกต้องวัตถุส่วนใหญ่จะไม่ถูกสร้างขึ้นด้วยnew
และจะประกาศบนสแต็กแทน และสำหรับผู้ที่สร้างโดยใช้new
ทั้งหมดจะถูกกำหนดขอบเขตไว้อย่างใดอย่างหนึ่ง(เช่นแนบกับตัวชี้อัจฉริยะ)
ในฐานะนักพัฒนาสิ่งนี้มีประสิทธิภาพมากเนื่องจากคุณไม่จำเป็นต้องสนใจเกี่ยวกับการจัดการทรัพยากรด้วยตนเอง (เช่นเดียวกับที่ทำใน C หรือสำหรับวัตถุบางอย่างใน Java ซึ่งใช้try
/ finally
สำหรับกรณีนั้นอย่างเข้มข้น) ...
"วัตถุที่ถูกกำหนดขอบเขต ... จะถูกทำลาย ... ไม่ว่าทางออก" นั้นไม่เป็นความจริงทั้งหมด มีวิธีโกง RAII รสชาติใด ๆ ของการยุติ () จะข้ามการล้างข้อมูล exit (EXIT_SUCCESS) เป็น oxymoron ในเรื่องนี้
Wilhelmtellค่อนข้างถูกต้องเกี่ยวกับเรื่องนี้: มีวิธีพิเศษในการโกง RAII ซึ่งทั้งหมดนี้นำไปสู่การหยุดชะงักทันที
สิ่งเหล่านี้เป็นวิธีที่ยอดเยี่ยมเนื่องจากโค้ด C ++ ไม่ทิ้งเกลื่อนไปด้วยการยุติการออก ฯลฯ หรือในกรณีที่มีข้อยกเว้นเราต้องการให้มีข้อยกเว้นที่ไม่สามารถจัดการได้เพื่อให้กระบวนการทำงานผิดพลาดและคอร์ถ่ายโอนอิมเมจหน่วยความจำตามที่เป็นอยู่และไม่ใช่หลังจากทำความสะอาดแล้ว
แต่เรายังต้องรู้เกี่ยวกับกรณีเหล่านั้นเพราะแม้ว่าจะไม่ค่อยเกิดขึ้น แต่ก็ยังสามารถเกิดขึ้นได้
(ใครโทรมาterminate
หรือใช้exit
รหัส C ++ แบบสบาย ๆ ... ฉันจำได้ว่าต้องจัดการกับปัญหานั้นเมื่อเล่นกับGLUT : ไลบรารีนี้เน้น C มากไปจนถึงการออกแบบอย่างแข็งขันเพื่อให้สิ่งที่ยากสำหรับนักพัฒนา C ++ เช่นไม่ใส่ใจ เกี่ยวกับสแต็คการจัดสรรข้อมูลหรือมีการตัดสินใจที่ "น่าสนใจ" เกี่ยวกับการไม่เคยกลับมาจากวงหลักของพวกเขา ... ฉันจะไม่แสดงความคิดเห็นเกี่ยวกับที่)
คุณจะต้องดูตัวชี้อัจฉริยะเช่นตัวชี้อัจฉริยะของบูสต์
แทน
int main()
{
Object* obj = new Object();
//...
delete obj;
}
boost :: shared_ptr จะลบโดยอัตโนมัติเมื่อจำนวนอ้างอิงเป็นศูนย์:
int main()
{
boost::shared_ptr<Object> obj(new Object());
//...
// destructor destroys when reference count is zero
}
โปรดสังเกตบันทึกสุดท้ายของฉัน "เมื่อจำนวนการอ้างอิงเป็นศูนย์ซึ่งเป็นส่วนที่เจ๋งที่สุดดังนั้นหากคุณมีผู้ใช้วัตถุหลายคนคุณจะไม่ต้องติดตามว่าวัตถุนั้นยังคงใช้งานอยู่หรือไม่เมื่อไม่มีใครอ้างถึง ตัวชี้ที่ใช้ร่วมกันมันถูกทำลาย
อย่างไรก็ตามนี่ไม่ใช่ยาครอบจักรวาล แม้ว่าคุณจะสามารถเข้าถึงตัวชี้พื้นฐานได้ แต่คุณก็ไม่ต้องการส่งต่อไปยัง API ของบุคคลที่สามเว้นแต่คุณจะมั่นใจในสิ่งที่ทำ หลายครั้งการ "โพสต์" ของคุณไปยังชุดข้อความอื่นเพื่อให้งานเสร็จหลังจากที่ขอบเขตการสร้างเสร็จสิ้น ซึ่งเป็นเรื่องปกติของ PostThreadMessage ใน Win32:
void foo()
{
boost::shared_ptr<Object> obj(new Object());
// Simplified here
PostThreadMessage(...., (LPARAM)ob.get());
// Destructor destroys! pointer sent to PostThreadMessage is invalid! Zohnoes!
}
เช่นเคยใช้ความคิดของคุณกับเครื่องมือใด ๆ ...
อ่านข้อมูลเกี่ยวกับRAIIและตรวจสอบให้แน่ใจว่าคุณเข้าใจ
การรั่วไหลของหน่วยความจำส่วนใหญ่เป็นผลมาจากความไม่ชัดเจนเกี่ยวกับความเป็นเจ้าของวัตถุและอายุการใช้งาน
สิ่งแรกที่ต้องทำคือจัดสรรในกองเมื่อใดก็ตามที่คุณทำได้ สิ่งนี้เกี่ยวข้องกับกรณีส่วนใหญ่ที่คุณต้องจัดสรรออบเจ็กต์เดียวเพื่อวัตถุประสงค์บางอย่าง
หากคุณต้องการ "ใหม่" วัตถุส่วนใหญ่แล้ววัตถุนั้นจะมีเจ้าของที่ชัดเจนเพียงรายเดียวตลอดอายุการใช้งานที่เหลือ สำหรับสถานการณ์นี้ฉันมักจะใช้เทมเพลตคอลเลกชันจำนวนมากที่ออกแบบมาสำหรับ "การเป็นเจ้าของ" วัตถุที่จัดเก็บโดยตัวชี้ ใช้กับเวกเตอร์ STL และคอนเทนเนอร์แผนที่ แต่มีความแตกต่างบางประการ:
บีฟของฉันกับ STL คือมันเน้นไปที่อ็อบเจ็กต์ Value ในขณะที่อ็อบเจ็กต์แอ็พพลิเคชันส่วนใหญ่เป็นเอนทิตีเฉพาะที่ไม่มีความหมายการคัดลอกที่มีความหมายที่จำเป็นสำหรับการใช้งานในคอนเทนเนอร์เหล่านั้น
เอ๊ยเด็กน้อยกับคนเก็บขยะมือใหม่ ...
กฎที่เข้มงวดมากเกี่ยวกับ "ความเป็นเจ้าของ" - วัตถุหรือส่วนใดของซอฟต์แวร์ที่มีสิทธิ์ลบวัตถุนั้น ล้างความคิดเห็นและชื่อตัวแปรที่ชาญฉลาดเพื่อให้ชัดเจนว่าตัวชี้ "เป็นเจ้าของ" หรือเป็น "เพียงแค่มองไม่ต้องแตะ" เพื่อช่วยในการตัดสินใจว่าใครเป็นเจ้าของอะไรให้ทำตามรูปแบบ "แซนวิช" ภายในรูทีนย่อยหรือวิธีการทั้งหมดให้มากที่สุด
create a thing
use that thing
destroy that thing
บางครั้งจำเป็นต้องสร้างและทำลายในสถานที่ต่างๆ ฉันคิดอย่างหนักที่จะหลีกเลี่ยงสิ่งนั้น
ในโปรแกรมใด ๆ ที่ต้องการโครงสร้างข้อมูลที่ซับซ้อนฉันจะสร้างโครงสร้างที่ชัดเจนของออบเจ็กต์ที่มีวัตถุอื่นโดยใช้พอยน์เตอร์ "เจ้าของ" โครงสร้างนี้จำลองลำดับชั้นพื้นฐานของแนวคิดโดเมนแอ็พพลิเคชัน ตัวอย่างฉาก 3 มิติเป็นเจ้าของวัตถุแสงพื้นผิว ในตอนท้ายของการแสดงผลเมื่อโปรแกรมหยุดทำงานมีวิธีที่ชัดเจนในการทำลายทุกอย่าง
พอยน์เตอร์อื่น ๆ อีกมากมายถูกกำหนดตามความจำเป็นเมื่อใดก็ตามที่เอนทิตีหนึ่งต้องการเข้าถึงอีกเอนทิตีหนึ่งเพื่อสแกนหาเรย์หรืออะไรก็ตาม นี่คือ "เพียงแค่มอง" สำหรับตัวอย่างฉาก 3 มิติ - วัตถุใช้พื้นผิว แต่ไม่ได้เป็นเจ้าของ วัตถุอื่น ๆ อาจใช้พื้นผิวเดียวกันนั้น การทำลายวัตถุไม่ได้ทำให้เกิดการทำลายพื้นผิวใด ๆ
ใช่มันเสียเวลา แต่นั่นคือสิ่งที่ฉันทำ ฉันไม่ค่อยมีความจำรั่วหรือปัญหาอื่น ๆ แต่แล้วฉันก็ทำงานในพื้นที่ จำกัด ของซอฟต์แวร์ทางวิทยาศาสตร์การเก็บข้อมูลและกราฟิกที่มีประสิทธิภาพสูง ฉันมักจะไม่ทำธุรกรรมเช่นในธนาคารและอีคอมเมิร์ซ GUI ที่ขับเคลื่อนด้วยเหตุการณ์หรือความสับสนวุ่นวายแบบอะซิงโครนัสบนเครือข่าย บางทีวิธีใหม่ ๆ อาจมีข้อได้เปรียบที่นั่น!
คำถามดีมาก!
หากคุณใช้ c ++ และคุณกำลังพัฒนาแอปพลิเคชั่น boud CPU และหน่วยความจำแบบเรียลไทม์ (เช่นเกม) คุณต้องเขียน Memory Manager ของคุณเอง
ฉันคิดว่าสิ่งที่ดีกว่าที่คุณทำได้คือการรวมผลงานที่น่าสนใจของผู้เขียนหลาย ๆ คนฉันสามารถให้คำแนะนำแก่คุณได้:
ตัวจัดสรรขนาดคงที่ถูกกล่าวถึงอย่างมากทุกที่ในเน็ต
Alexandrescu นำเสนอ Small Object Allocation ในปี 2544 ในหนังสือ "Modern c ++ design" ที่สมบูรณ์แบบของเขา
ความก้าวหน้าที่ยิ่งใหญ่ (พร้อมด้วยซอร์สโค้ดแจกจ่าย) สามารถพบได้ในบทความที่น่าทึ่งใน Game Programming Gem 7 (2008) ชื่อ "ตัวจัดสรรฮีปประสิทธิภาพสูง" ที่เขียนโดย Dimitar Lazarov
รายการที่ดีของทรัพยากรที่สามารถพบได้ในนี้บทความ
อย่าเริ่มเขียน noob ตัวจัดสรรที่ไม่เป็นประโยชน์ด้วยตัวเอง ... ทำเอกสารด้วยตัวคุณเองก่อน
เทคนิคหนึ่งที่ได้กลายเป็นที่นิยมกับการจัดการหน่วยความจำใน C ++ เป็นRAII โดยทั่วไปคุณใช้ตัวสร้าง / ตัวทำลายเพื่อจัดการการจัดสรรทรัพยากร แน่นอนว่ามีรายละเอียดที่น่ารังเกียจอื่น ๆ ใน C ++ เนื่องจากข้อยกเว้นด้านความปลอดภัย แต่แนวคิดพื้นฐานนั้นค่อนข้างง่าย
โดยทั่วไปปัญหาจะขึ้นอยู่กับความเป็นเจ้าของอย่างใดอย่างหนึ่ง ฉันขอแนะนำให้อ่านซีรีส์ C ++ ที่มีประสิทธิภาพโดย Scott Meyers และ Modern C ++ Design โดย Andrei Alexandrescu
มีหลายวิธีที่จะไม่รั่วไหล แต่หากคุณต้องการเครื่องมือที่จะช่วยติดตามการรั่วไหลให้ดูที่:
ตัวชี้อัจฉริยะของผู้ใช้ทุกที่ที่คุณทำได้! การรั่วไหลของหน่วยความจำทั้งคลาสจะหายไป
แบ่งปันและรู้กฎการเป็นเจ้าของหน่วยความจำในโครงการของคุณ การใช้กฎ COM ทำให้เกิดความสอดคล้องที่ดีที่สุด (พารามิเตอร์ [ใน] เป็นของผู้โทรผู้โทรต้องคัดลอก [out] พารามิเตอร์เป็นของผู้โทรผู้โทรต้องทำสำเนาหากมีการอ้างอิงเป็นต้น)
valgrindเป็นเครื่องมือที่ดีในการตรวจสอบการรั่วไหลของหน่วยความจำโปรแกรมของคุณในขณะรันไทม์ด้วย
มีให้บริการใน Linux เกือบทุกรสชาติ (รวมถึง Android) และในดาร์วิน
หากคุณใช้ในการเขียนการทดสอบหน่วยสำหรับโปรแกรมของคุณคุณควรติดนิสัยในการรัน valgrind ในการทดสอบอย่างเป็นระบบ อาจหลีกเลี่ยงการรั่วไหลของหน่วยความจำจำนวนมากในระยะเริ่มต้น นอกจากนี้ยังง่ายกว่าที่จะระบุพวกเขาในการทดสอบอย่างง่ายในซอฟต์แวร์ตัวเต็ม
แน่นอนคำแนะนำนี้ใช้ได้กับเครื่องมือตรวจสอบหน่วยความจำอื่น ๆ
นอกจากนี้อย่าใช้หน่วยความจำที่จัดสรรด้วยตนเองหากมีคลาสไลบรารี std (เช่นเวกเตอร์) ตรวจสอบให้แน่ใจว่าคุณละเมิดกฎนั้นหรือไม่ว่าคุณมีตัวทำลายเสมือน
หากคุณไม่สามารถ / ไม่ใช้ตัวชี้อัจฉริยะสำหรับบางสิ่งบางอย่าง (แม้ว่าจะเป็นธงสีแดงขนาดใหญ่) ให้พิมพ์รหัสของคุณด้วย:
allocate
if allocation succeeded:
{ //scope)
deallocate()
}
เห็นได้ชัด แต่อย่าลืมพิมพ์ก่อนพิมพ์รหัสใด ๆ ในขอบเขต
แหล่งที่มาของจุดบกพร่องเหล่านี้บ่อยครั้งคือเมื่อคุณมีวิธีการที่ยอมรับการอ้างอิงหรือตัวชี้ไปยังวัตถุ แต่ทำให้ความเป็นเจ้าของไม่ชัดเจน รูปแบบและรูปแบบการแสดงความคิดเห็นอาจทำให้มีโอกาสน้อยลง
ให้กรณีที่ฟังก์ชันเป็นเจ้าของออบเจ็กต์เป็นกรณีพิเศษ ในทุกสถานการณ์ที่เกิดเหตุการณ์นี้อย่าลืมเขียนความคิดเห็นถัดจากฟังก์ชันในไฟล์ส่วนหัวที่ระบุสิ่งนี้ คุณควรพยายามตรวจสอบให้แน่ใจว่าในกรณีส่วนใหญ่โมดูลหรือคลาสที่จัดสรรอ็อบเจ็กต์จะต้องรับผิดชอบในการจัดสรรมันด้วย
การใช้ const ช่วยได้มากในบางกรณี หากฟังก์ชันจะไม่แก้ไขอ็อบเจ็กต์และไม่จัดเก็บการอ้างอิงที่ยังคงอยู่หลังจากส่งคืนแล้วให้ยอมรับการอ้างอิง const จากการอ่านรหัสของผู้โทรจะเห็นได้ชัดว่าฟังก์ชันของคุณไม่ยอมรับการเป็นเจ้าของวัตถุ คุณอาจมีฟังก์ชันเดียวกันยอมรับตัวชี้ที่ไม่ใช่ const และผู้โทรอาจสันนิษฐานหรือไม่ก็ได้ว่าผู้เรียกนั้นยอมรับความเป็นเจ้าของ แต่ด้วยการอ้างอิง const ก็ไม่มีคำถาม
อย่าใช้การอ้างอิงที่ไม่ใช่ const ในรายการอาร์กิวเมนต์ ไม่มีความชัดเจนมากเมื่ออ่านรหัสผู้โทรซึ่งผู้โทรอาจเก็บไว้อ้างอิงถึงพารามิเตอร์
ฉันไม่เห็นด้วยกับความคิดเห็นที่แนะนำตัวชี้การอ้างอิงที่นับ โดยปกติจะใช้งานได้ดี แต่เมื่อคุณมีจุดบกพร่องและไม่ได้ผลโดยเฉพาะอย่างยิ่งหากผู้ทำลายของคุณทำสิ่งที่ไม่สำคัญเช่นในโปรแกรมมัลติเธรด พยายามปรับการออกแบบของคุณอย่างแน่นอนโดยไม่ต้องมีการนับอ้างอิงหากไม่ยากเกินไป
คำแนะนำตามลำดับความสำคัญ:
- เคล็ดลับ # 1 อย่าลืมประกาศผู้ทำลายของคุณ "เสมือน" เสมอ
- เคล็ดลับ # 2 ใช้ RAII
-Tip # 3 ใช้ smartpointers ของ boost
- เคล็ดลับ # 4 อย่าเขียน Smartpointers รถของคุณเองใช้บูสต์ (ในโปรเจ็กต์ที่ฉันอยู่ตอนนี้ฉันไม่สามารถใช้บูสต์ได้และฉันต้องทนทุกข์ทรมานกับการดีบักตัวชี้อัจฉริยะของตัวเองฉันจะไม่ทำอย่างแน่นอน เส้นทางเดิมอีกครั้ง แต่อีกครั้งในตอนนี้ฉันไม่สามารถเพิ่มบูสต์ให้กับการอ้างอิงของเราได้)
- เคล็ดลับ # 5 หากบางส่วนที่ไม่เป็นทางการ / ไม่มีประสิทธิภาพที่สำคัญ (เช่นเดียวกับในเกมที่มีวัตถุหลายพันชิ้น) ให้ดูที่คอนเทนเนอร์ตัวชี้เพิ่มของ Thorsten Ottosen
- เคล็ดลับ # 6 ค้นหาส่วนหัวการตรวจจับการรั่วไหลสำหรับแพลตฟอร์มที่คุณเลือกเช่นส่วนหัว "vld" ของ Visual Leak Detection
ถ้าทำได้ให้ใช้ boost shared_ptr และ standard C ++ auto_ptr สิ่งเหล่านี้สื่อถึงความหมายของความเป็นเจ้าของ
เมื่อคุณส่งคืน auto_ptr คุณกำลังบอกผู้โทรว่าคุณกำลังให้พวกเขาเป็นเจ้าของหน่วยความจำ
เมื่อคุณส่งคืน shared_ptr คุณกำลังบอกผู้โทรว่าคุณมีข้อมูลอ้างอิงและพวกเขามีส่วนในการเป็นเจ้าของ แต่ไม่ใช่ความรับผิดชอบของพวกเขา แต่เพียงผู้เดียว
ความหมายเหล่านี้ใช้กับพารามิเตอร์ด้วย หากผู้โทรส่งผ่าน auto_ptr ให้คุณผู้โทรจะให้คุณเป็นเจ้าของ
คนอื่น ๆ ได้กล่าวถึงวิธีการหลีกเลี่ยงการรั่วไหลของหน่วยความจำในตอนแรก (เช่นตัวชี้อัจฉริยะ) แต่เครื่องมือวิเคราะห์โปรไฟล์และหน่วยความจำมักเป็นวิธีเดียวในการติดตามปัญหาหน่วยความจำเมื่อคุณมี
Valgrind memcheckเป็นฟรีที่ยอดเยี่ยม
สำหรับ MSVC เท่านั้นให้เพิ่มสิ่งต่อไปนี้ที่ด้านบนของไฟล์. cpp แต่ละไฟล์:
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
จากนั้นเมื่อทำการดีบักด้วย VS2003 ขึ้นไปคุณจะได้รับแจ้งเกี่ยวกับการรั่วไหลเมื่อโปรแกรมของคุณออก (ติดตามใหม่ / ลบ) มันเป็นเรื่องพื้นฐาน แต่ที่ผ่านมามันช่วยฉันได้
valgrind (ประโยชน์เฉพาะสำหรับแพลตฟอร์ม * nix) เป็นตัวตรวจสอบหน่วยความจำที่ดีมาก
หากคุณจะจัดการหน่วยความจำด้วยตนเองคุณมีสองกรณี:
หากคุณต้องการฝ่าฝืนกฎเหล่านี้โปรดบันทึกไว้
เป็นข้อมูลเกี่ยวกับความเป็นเจ้าของตัวชี้
คุณสามารถสกัดกั้นฟังก์ชั่นการจัดสรรหน่วยความจำและดูว่ามีโซนหน่วยความจำบางส่วนที่ไม่ได้รับการปลดปล่อยเมื่อออกจากโปรแกรมหรือไม่ (แม้ว่าจะไม่เหมาะสำหรับแอปพลิเคชันทั้งหมด )
นอกจากนี้ยังสามารถทำได้ในเวลาคอมไพล์โดยการแทนที่ตัวดำเนินการใหม่และลบและฟังก์ชันการจัดสรรหน่วยความจำอื่น ๆ
ตัวอย่างเช่นตรวจสอบในไซต์นี้[การดีบักการจัดสรรหน่วยความจำใน C ++] หมายเหตุ: มีเคล็ดลับสำหรับการลบตัวดำเนินการเช่นนี้:
#define DEBUG_DELETE PrepareDelete(__LINE__,__FILE__); delete
#define delete DEBUG_DELETE
คุณสามารถเก็บชื่อไฟล์ไว้ในตัวแปรบางตัวและเมื่อตัวดำเนินการลบที่โอเวอร์โหลดจะรู้ว่ามันถูกเรียกมาจากที่ใด ด้วยวิธีนี้คุณสามารถมีร่องรอยของการลบและ malloc ทั้งหมดจากโปรแกรมของคุณ ในตอนท้ายของลำดับการตรวจสอบหน่วยความจำคุณควรจะรายงานได้ว่าบล็อกหน่วยความจำที่จัดสรรไว้นั้นไม่ได้ถูก 'ลบ' โดยระบุชื่อไฟล์และหมายเลขบรรทัดซึ่งฉันเดาว่าคุณต้องการอะไร
นอกจากนี้คุณยังสามารถลองอะไรบางอย่างเช่นBoundsCheckerภายใต้ Visual Studio ซึ่งค่อนข้างน่าสนใจและใช้งานง่าย
เรารวมฟังก์ชันการจัดสรรทั้งหมดของเราด้วยเลเยอร์ที่ต่อท้ายสตริงสั้น ๆ ที่ด้านหน้าและแฟล็กทหารรักษาการณ์ที่ส่วนท้าย ตัวอย่างเช่นคุณมีสายเรียกไปที่ "myalloc (pszSomeString, iSize, iAlignment); หรือ new (" description ", iSize) MyObject (); ซึ่งจะจัดสรรขนาดที่ระบุไว้ภายในและมีพื้นที่เพียงพอสำหรับส่วนหัวและ Sentinel ของคุณแน่นอน อย่าลืมแสดงความคิดเห็นสำหรับบิวด์ที่ไม่มีการดีบัก! ต้องใช้หน่วยความจำมากกว่านี้เล็กน้อย แต่ประโยชน์ที่ได้รับนั้นมีมากกว่าค่าใช้จ่าย
สิ่งนี้มีประโยชน์สามประการประการแรกช่วยให้คุณติดตามได้อย่างง่ายดายและรวดเร็วว่าโค้ดใดรั่วไหลโดยทำการค้นหาอย่างรวดเร็วสำหรับรหัสที่จัดสรรในบาง 'โซน' แต่จะไม่ถูกล้างเมื่อโซนเหล่านั้นควรจะเป็นอิสระ นอกจากนี้ยังมีประโยชน์ในการตรวจจับเมื่อเขตแดนถูกเขียนทับโดยการตรวจสอบเพื่อให้แน่ใจว่าทหารรักษาการณ์ทั้งหมดยังคงอยู่ สิ่งนี้ช่วยเราได้หลายครั้งเมื่อพยายามค้นหาข้อขัดข้องที่ซ่อนไว้อย่างดีหรือขั้นตอนผิดพลาดของอาร์เรย์ ประโยชน์ประการที่สามคือการติดตามการใช้หน่วยความจำเพื่อดูว่าใครคือผู้เล่นรายใหญ่ - การเปรียบเทียบคำอธิบายบางอย่างใน MemDump จะบอกคุณเมื่อ 'เสียง' ใช้พื้นที่มากกว่าที่คุณคาดไว้ตัวอย่างเช่น
C ++ ได้รับการออกแบบโดยคำนึงถึง RAII ฉันคิดว่าไม่มีวิธีใดที่ดีกว่าในการจัดการหน่วยความจำใน C ++ แต่ระวังอย่าจัดสรรชิ้นใหญ่มาก (เช่นวัตถุบัฟเฟอร์) ในขอบเขตเฉพาะที่ อาจทำให้เกิดสแต็กล้นและหากมีข้อบกพร่องในการตรวจสอบขอบเขตในขณะที่ใช้ชิ้นส่วนนั้นคุณสามารถเขียนทับตัวแปรอื่นหรือที่อยู่ส่งคืนซึ่งนำไปสู่ช่องโหว่ด้านความปลอดภัยทุกชนิด
หนึ่งในตัวอย่างเดียวเกี่ยวกับการจัดสรรและทำลายในที่ต่างๆคือการสร้างเธรด (พารามิเตอร์ที่คุณส่งผ่าน) แต่แม้ในกรณีนี้จะง่าย นี่คือฟังก์ชั่น / วิธีการสร้างเธรด:
struct myparams {
int x;
std::vector<double> z;
}
std::auto_ptr<myparams> param(new myparams(x, ...));
// Release the ownership in case thread creation is successfull
if (0 == pthread_create(&th, NULL, th_func, param.get()) param.release();
...
ที่นี่แทนฟังก์ชันเธรด
extern "C" void* th_func(void* p) {
try {
std::auto_ptr<myparams> param((myparams*)p);
...
} catch(...) {
}
return 0;
}
ค่อนข้างง่ายไม่ใช่เหรอ? ในกรณีที่การสร้างเธรดล้มเหลวทรัพยากรจะถูกปล่อย (ลบ) โดย auto_ptr มิฉะนั้นความเป็นเจ้าของจะถูกส่งต่อไปยังเธรด จะเกิดอะไรขึ้นถ้าเธรดเร็วมากจนหลังจากสร้างแล้วมันจะปล่อยรีซอร์สก่อนไฟล์
param.release();
ถูกเรียกในฟังก์ชันหลัก / วิธีการ? ไม่มีอะไร! เพราะเราจะ 'บอก' ให้ auto_ptr เพิกเฉยต่อการยกเลิกการจัดสรร การจัดการหน่วยความจำ C ++ เป็นเรื่องง่ายใช่หรือไม่? ไชโย
Ema!
จัดการหน่วยความจำแบบเดียวกับที่คุณจัดการทรัพยากรอื่น ๆ (จัดการไฟล์การเชื่อมต่อฐานข้อมูลซ็อกเก็ต ... ) GC ก็ไม่ช่วยคุณเช่นกัน
หนึ่งผลตอบแทนจากฟังก์ชันใด ๆ ด้วยวิธีนี้คุณสามารถทำการจัดสรรที่นั่นและไม่พลาด
เป็นเรื่องง่ายเกินไปที่จะทำผิดเป็นอย่างอื่น:
new a()
if (Bad()) {delete a; return;}
new b()
if (Bad()) {delete a; delete b; return;}
... // etc.