จะค้นหาหน่วยความจำรั่วในรหัส C ++ ได้อย่างไร


180

ฉันเป็นโปรแกรมเมอร์ C ++ บนแพลตฟอร์ม Windows ฉันใช้ Visual Studio 2008

ฉันมักจะจบลงในรหัสด้วยหน่วยความจำรั่ว

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

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

  1. ฉันต้องการทราบว่าโปรแกรมเมอร์สามารถค้นหาหน่วยความจำรั่วได้อย่างไร
  2. มีมาตรฐานหรือขั้นตอนใดที่ควรปฏิบัติตามเพื่อให้แน่ใจว่าไม่มีการรั่วไหลของหน่วยความจำในโปรแกรมหรือไม่?

29
"ฉันมักจะจบลงด้วยรหัสหน่วยความจำรั่ว" ถ้าคุณใช้ตัวแปรอัตโนมัติตู้คอนเทนเนอร์และพอยน์เตอร์อัจฉริยะ (และปฏิบัติตามแนวทางปฏิบัติที่ดีที่สุดสำหรับการใช้สมาร์ทพอยน์เตอร์) การรั่วไหลของหน่วยความจำควรจะหายากมาก โปรดจำไว้ว่าในเกือบทุกกรณีคุณควรใช้การจัดการทรัพยากรอัตโนมัติ
James McNellis

ปัญหาซ้ำที่ครอบคลุมโดยคำถามหลายข้อเช่น stackoverflow.com/questions/1502799/…และstackoverflow.com/questions/2820223/…
HostileFork พูดว่าอย่าไว้วางใจ SE

1
@ โฮสเทลฟอร์ค: "วิธีที่จะหลีกเลี่ยงมักจะจบลงด้วยรหัสที่มีการรั่วไหลของหน่วยความจำ" ไม่ครอบคลุมโดยคำตอบเหล่านั้น
Doc Brown

2
@Doc Brown: ไม่รู้สึกเหมือนกำลังมองหามันอยู่เหมือนกัน แต่มันครอบคลุมทุกที่เช่นstackoverflow.com/questions/45627/…
HostileFork บอกว่าอย่าไว้ใจ SE

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

คำตอบ:


270

คำแนะนำ

สิ่งที่คุณต้องการ

  • ความเชี่ยวชาญใน C ++
  • คอมไพเลอร์ C ++
  • เครื่องมือดีบั๊กและเครื่องมือสืบสวนอื่น ๆ

1

ทำความเข้าใจพื้นฐานผู้ประกอบการ ผู้ประกอบการ C ++ newจัดสรรหน่วยความจำฮีป deleteหน่วยความจำผู้ประกอบการปลดปล่อยกอง สำหรับทุก ๆ คนnewคุณควรใช้ a deleteเพื่อให้คุณมีหน่วยความจำเท่ากันกับที่คุณจัดสรร:

char* str = new char [30]; // Allocate 30 bytes to house a string.

delete [] str; // Clear those 30 bytes and make str point nowhere.

2

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

char* str = new char [30]; // Give str a memory address.

// delete [] str; // Remove the first comment marking in this line to correct.

str = new char [60]; /* Give str another memory address with
                                                    the first one gone forever.*/

delete [] str; // This deletes the 60 bytes, not just the first 30.

3

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

char* str1 = new char [30];

char* str2 = new char [40];

strcpy(str1, "Memory leak");

str2 = str1; // Bad! Now the 40 bytes are impossible to free.

delete [] str2; // This deletes the 30 bytes.

delete [] str1; // Possible access violation. What a disaster!

4

ระวังกับพอยน์เตอร์ท้องถิ่น ตัวชี้ที่คุณประกาศในฟังก์ชั่นจะถูกจัดสรรในสแต็ก แต่ตัวแปรแบบไดนามิกที่ชี้ไปถูกจัดสรรบนฮีป หากคุณไม่ลบมันจะยังคงอยู่หลังจากที่โปรแกรมออกจากฟังก์ชัน:

void Leak(int x){

char* p = new char [x];

// delete [] p; // Remove the first comment marking to correct.

}

5

ให้ความสนใจกับเครื่องหมายวงเล็บเหลี่ยมหลังจาก "ลบ" ใช้deleteโดยตัวมันเองเพื่อปล่อยวัตถุเดียว ใช้delete []กับวงเล็บเหลี่ยมเพื่อเพิ่มอาร์เรย์ฮีป อย่าทำอะไรแบบนี้:

char* one = new char;

delete [] one; // Wrong

char* many = new char [30];

delete many; // Wrong!

6

หากการรั่วไหลได้รับอนุญาต - ฉันมักจะมองหามันด้วย deleaker (ตรวจสอบได้ที่นี่: http://deleaker.com )


3
ขออภัยสำหรับคำถามแสดงความคิดเห็น แต่สิ่งที่เกี่ยวกับพารามิเตอร์ฟังก์ชั่นโดยไม่มีตัวชี้? someFunction("some parameter")ฉันต้องลบ"some parameter"ในsomeFunction, หลังจากการเรียกใช้ฟังก์ชั่น, หรือสิ่งเหล่านี้ถูกลบโดยอัตโนมัติหรือไม่?
19greg96

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

@ john smith โปรดอธิบายว่าวิธีใดที่เหมาะสมในการจัดการเคสที่คล้ายกับเคส 3; str2 = str1; // ไม่ดี! ตอนนี้ 40 ไบต์เป็นไปไม่ได้ที่จะปล่อยให้เป็นอิสระ วิธีการลบ str 1 แล้ว ??
Nihar

1
ถ้าเราใช้ประเภทค่าเช่น char *, int, float, ... และ struct เช่น Vector, CString และไม่ใช้ตัวดำเนินการ 'ใหม่' เลยมันจะไม่ทำให้หน่วยความจำรั่วใช่ไหม?
123iamking

ฉันแค่อยู่ที่นี่เพื่อบอกว่าฉันไม่ได้แตะ c ++ ในระยะเวลา 14 ปี ... แต่ฉันภูมิใจที่จะบอกว่าฉันเข้าใจและจดจำวิธีการทำทั้งหมดนี้ต้องขอบคุณหนังสือ c ++ ที่ฉันยังมีอยู่และอ่านเมื่อฉัน ' m เบื่อกับ c # หนังสือเล่มนั้นคือ C ++ ของ Scott Mitchell พระเจ้าฉันรักหนังสือเล่มนั้น ขอบคุณสก็อต!
JonH

33

คุณสามารถใช้เทคนิคบางอย่างในรหัสของคุณเพื่อตรวจสอบการรั่วไหลของหน่วยความจำ วิธีที่ง่ายที่สุดในการตรวจสอบคือกำหนดแมโครว่า DEBUG_NEW และใช้งานพร้อมกับมาโครที่กำหนดไว้ล่วงหน้าเช่น__FILE__และ__LINE__เพื่อค้นหาหน่วยความจำรั่วในรหัสของคุณ มาโครที่กำหนดไว้ล่วงหน้าเหล่านี้จะบอกไฟล์และจำนวนบรรทัดของหน่วยความจำรั่ว

DEBUG_NEW เป็นเพียง MACRO ซึ่งโดยปกติจะกำหนดเป็น:

#define DEBUG_NEW new(__FILE__, __LINE__)
#define new DEBUG_NEW

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

และ__FILE__, __LINE__มีการกำหนดไว้ล่วงหน้าแมโครซึ่งประเมินชื่อไฟล์และเส้นจำนวนตามลำดับที่คุณใช้พวกเขา!

อ่านบทความต่อไปนี้ซึ่งอธิบายเทคนิคการใช้ DEBUG_NEW กับมาโครที่น่าสนใจอื่น ๆ ได้อย่างสวยงามมาก:

เครื่องตรวจจับการรั่วไหลของหน่วยความจำข้ามแพลตฟอร์ม


จากWikpedia ,

Debug_new อ้างถึงเทคนิคใน C ++ เพื่อโอเวอร์โหลดและ / หรือกำหนดโอเปอเรเตอร์ใหม่และลบโอเปอเรเตอร์เพื่อสกัดกั้นการจัดสรรหน่วยความจำและการเรียกคืนการจัดสรรคืนและทำให้ดีบักโปรแกรมสำหรับการใช้หน่วยความจำ มันมักจะเกี่ยวข้องกับการกำหนดแมโครชื่อ DEBUG_NEW และทำให้ใหม่กลายเป็นเหมือนใหม่ (_ FILE _, _ LINE _) เพื่อบันทึกข้อมูลไฟล์ / บรรทัดในการจัดสรรMicrosoft Visual C ++ ใช้เทคนิคนี้ใน Microsoft Foundation Classes มีบางวิธีในการขยายวิธีนี้เพื่อหลีกเลี่ยงการใช้การกำหนดแมโครซ้ำในขณะที่ยังสามารถแสดงข้อมูลไฟล์ / บรรทัดในบางแพลตฟอร์ม มีข้อ จำกัด หลายประการสำหรับวิธีนี้ มันใช้เฉพาะกับ C ++ และไม่สามารถตรวจจับการรั่วไหลของหน่วยความจำด้วยฟังก์ชั่น C เช่น malloc อย่างไรก็ตามมันสามารถใช้งานได้ง่ายและรวดเร็วมากเมื่อเปรียบเทียบกับโซลูชันการดีบักเกอร์หน่วยความจำที่สมบูรณ์มากขึ้น


4
สิ่งนี้#defineจะทำให้เกิดความยุ่งเหยิงมากเกินไปoperator newและสร้างข้อผิดพลาดของคอมไพเลอร์ แม้ว่าคุณจะประสบความสำเร็จในการเอาชนะมันก็ยังไม่ได้รับฟังก์ชั่นการโอเวอร์โหลด แม้ว่าเทคนิคจะดี แต่บางครั้งก็จำเป็นต้องมีการเปลี่ยนแปลงโค้ดเป็นจำนวนมาก
iammilind

1
@iammilind: แน่นอนว่าเทคนิคนี้ไม่ได้แก้ปัญหาทุกชุดและแน่นอนไม่สามารถใช้ได้ในทุกสถานการณ์
นาวาซ

@Chris_vr: auto_ptrจะไม่ทำงานกับภาชนะมาตรฐานเช่นstd::vector, std::listฯลฯ ดูนี้: stackoverflow.com/questions/111478/...
นาวาซ

โอเคเท่ห์ อธิบายถึงไฟล์และบรรทัด operator newรุ่นใดที่คุณใช้อยู่คืออะไรและรุ่นใด

14

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

  • ถ้าคุณต้องทำการจัดสรรหน่วยความจำแบบไดนามิกของคุณเองเขียนnewและdeleteจับคู่เสมอและตรวจสอบให้แน่ใจว่ารหัสการจัดสรร / การจัดสรรคืนถูกเรียกว่า pairwise
  • หลีกเลี่ยงการจัดสรรหน่วยความจำแบบไดนามิกถ้าคุณทำได้ ตัวอย่างเช่นใช้vector<T> tที่ไหนก็ตามที่เป็นไปได้แทนT* t = new T[size]
  • ใช้ "ตัวชี้อัจฉริยะ" เช่นเพิ่มตัวชี้อัจฉริยะ ( http://www.boost.org/doc/libs/1_46_1/libs/smart_ptr/smart_ptr.htm )
  • รายการโปรดส่วนตัวของฉัน: ตรวจสอบให้แน่ใจว่าคุณเข้าใจแนวคิดเรื่องการเป็นเจ้าของตัวชี้และตรวจสอบให้แน่ใจว่าทุกที่ที่คุณใช้พอยน์เตอร์คุณรู้ว่ารหัสเอนทิตีคือเจ้าของ
  • เรียนรู้ว่าตัวดำเนินการ constructors / assignment ใดที่ถูกสร้างขึ้นโดยอัตโนมัติโดยคอมไพเลอร์ C ++ และนั่นหมายความว่าถ้าคุณมีคลาสที่เป็นเจ้าของตัวชี้ (หรือสิ่งที่หมายความว่าถ้าคุณมีคลาสที่มีตัวชี้ไปยังวัตถุที่มันไม่ได้เป็นเจ้าของ)

ฉันใช้ auto_pointer ของวัตถุหมายความว่ามันจะลบตัวชี้วัตถุคลาสอื่นทั้งหมดที่อยู่ภายใน
Chris_vr

@Chris_vr: หากคุณมีคำถามเฉพาะเกี่ยวกับ auto_pointer ฉันขอแนะนำให้คุณสร้างคำถามใหม่รวมถึงตัวอย่าง
Doc Brown

โพสต์จำนวนมากบอกฉันว่าเวกเตอร์ <> ไม่รับประกันว่าหน่วยความจำจะถูกปล่อยออกมาอย่างชัดเจน ฉันทดสอบการแลกเปลี่ยนข้อมูลส่วนตัว ฯลฯ และฉันสรุปว่าเวกเตอร์ <> รั่วไหลโดยเฉพาะเมื่อใช้แบบไดนามิก ฉันไม่เข้าใจว่าจะให้คำแนะนำเวกเตอร์ <> ผ่านการจัดสรรแบบไดนามิกในแบบตัวคุณเองได้อย่างไรโดยใช้ 'ใหม่' และล้างข้อมูลอย่างถูกต้อง ในโปรแกรมฝังตัวของฉันฉันหลีกเลี่ยงการใช้เวกเตอร์ <> สำหรับสิ่งไดนามิกเนื่องจากการรั่วไหลทั้งหมด ที่นั่นฉันใช้ new หรือ std :: list
bart s

ฉันพิมพ์คำสั่งที่สองเนื่องจากจำนวนอักขระ น่าเสียดายใน c ++ ที่ฝังตัวของฉันฉันมี c ++ เก่า (98?) ซึ่งไม่มี shrink_to_fit บนเวกเตอร์ ... อย่างไรก็ตามโปรแกรมฝังตัว 100% แน่ใจว่าจะพังทั้งหมดเมื่อหน่วยความจำหมดโดยใช้เวกเตอร์ <> แบบไดนามิก
บาร์ต


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

UMDHจะให้ข้อมูลเพิ่มเติมกับ CRT debug heap เนื่องจากคุณกำลังดูการจัดสรรหน่วยความจำในกระบวนการทั้งหมดของคุณ มันสามารถบอกคุณได้ว่าองค์ประกอบของ บริษัท อื่นรั่วไหลหรือไม่


1
ฉันชอบ Deleaker และ Valgrind แทน profiler มาตรฐาน
z0r1fan

8

การเรียกใช้ "Valgrind" สามารถ:

1) ช่วยระบุการรั่วไหลของหน่วยความจำ - แสดงจำนวนการรั่วไหลของหน่วยความจำที่คุณมีและชี้ไปที่บรรทัดในรหัสที่จัดสรรหน่วยความจำรั่ว

2) ชี้ให้เห็นถึงความพยายามที่ผิดในการเพิ่มหน่วยความจำ (เช่นการโทรที่ไม่เหมาะสมdelete )

คำแนะนำสำหรับการใช้ "Valgrind"

1) รับ valgrind ที่นี่ที่นี่

2) คอมไพล์โค้ดของคุณด้วย -gธง

3) ในการรันเชลล์:

valgrind --leak-check=yes myprog arg1 arg2

โดยที่ "myprog" เป็นโปรแกรมที่คอมไพล์arg1แล้วarg2ข้อโต้แย้งของโปรแกรม

4) ผลลัพธ์คือรายการของการโทรไปยังmalloc/ newที่ไม่มีการโทรตามมาเพื่อลบฟรี

ตัวอย่างเช่น:

==4230==    at 0x1B977DD0: malloc (vg_replace_malloc.c:136)

==4230==    by 0x804990F: main (example.c:6)

บอกให้คุณทราบว่าบรรทัดใดที่เรียกmallocว่า (ที่ไม่ได้เป็นอิสระ)

ในฐานะที่เป็นออกแหลมโดยคนอื่นตรวจสอบให้แน่ใจว่าทุกnew/ mallocโทรคุณมีตามมาdelete/ freeโทร


6

หากคุณใช้ gcc จะมี gprof ให้ใช้งาน

ฉันอยากรู้ว่าโปรแกรมเมอร์ค้นหาหน่วยความจำรั่วได้อย่างไร

บางคนใช้เครื่องมือบางอย่างก็ทำในสิ่งที่คุณทำได้เช่นกันผ่านการตรวจสอบโค้ดเพียร์

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

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


บางครั้งเราไม่สามารถลบการจัดสรรในตัวสร้างสิ่งที่ต้องทำในโอกาสนั้น
Chris_vr

5
  1. ใน visual studio มีตัวตรวจจับการรั่วไหลของหน่วยความจำในตัวที่เรียกว่า C Runtime Library เมื่อโปรแกรมของคุณออกหลังจากฟังก์ชั่นหลักกลับมา CRT จะตรวจสอบฮีปการดีบักของแอ็พพลิเคชันของคุณ หากคุณมีบล็อกใด ๆ ที่ยังคงจัดสรรในฮีบรูตรวจแก้จุดบกพร่องแล้วคุณมีหน่วยความจำรั่ว ..

  2. ฟอรัมนี้กล่าวถึงวิธีการบางอย่างเพื่อหลีกเลี่ยงการรั่วไหลของหน่วยความจำใน C / C ++ ..


5

ค้นหารหัสของคุณเพื่อหาสิ่งที่เกิดขึ้นnewและตรวจสอบให้แน่ใจว่าพวกเขาทั้งหมดเกิดขึ้นภายในตัวสร้างที่มีการลบการจับคู่ใน destructor ตรวจสอบให้แน่ใจว่านี่เป็นการดำเนินการโยนที่เป็นไปได้เพียงอย่างเดียวใน Constructor นั้น วิธีง่ายๆในการทำเช่นนี้คือห่อตัวชี้ทั้งหมดstd::auto_ptrหรือboost::scoped_ptr(ขึ้นอยู่กับว่าคุณต้องการซีแมนทิกส์ย้ายหรือไม่) สำหรับรหัสในอนาคตทั้งหมดเพียงตรวจสอบให้แน่ใจว่าทุกทรัพยากรเป็นเจ้าของโดยวัตถุที่ล้างข้อมูลทรัพยากรใน destructor หากคุณต้องการความหมายของการย้ายคุณสามารถอัปเกรดเป็นคอมไพเลอร์ที่รองรับการอ้างอิง r-value (ฉันเชื่อว่า VS2010) และสร้างตัวสร้างการย้าย หากคุณไม่ต้องการทำเช่นนั้นคุณสามารถใช้เทคนิคที่ซับซ้อนมากมายที่เกี่ยวข้องกับการใช้งาน swap อย่างมีสติหรือลองใช้ห้องสมุด Boost.Move


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

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

@Chris_vr: หากคุณมีตัวอย่างที่เฉพาะเจาะจงโพสต์เป็นคำถามใหม่เพื่อให้เราสามารถพูดคุยได้ที่นั่น
Doc Brown

5

คุณสามารถใช้เครื่องมือ Valgrind เพื่อตรวจหารอยรั่วของหน่วยความจำ

นอกจากนี้ในการค้นหาการรั่วไหลในฟังก์ชั่นเฉพาะให้ใช้ exit (0) ที่ท้ายฟังก์ชั่นแล้วเรียกใช้ด้วย Valgrind

`$` valgrind ./your_CPP_program 

5

การสำรวจตัวตรวจสอบการรั่วของหน่วยความจำอัตโนมัติ

ในคำตอบนี้ฉันเปรียบเทียบตัวตรวจสอบการรั่วไหลของหน่วยความจำที่แตกต่างกันหลายอย่างในตัวอย่างง่าย ๆ ที่จะเข้าใจหน่วยความจำรั่ว

ก่อนอื่นให้ดูตารางขนาดใหญ่นี้ในวิกิพีเดีย ASan ซึ่งเปรียบเทียบเครื่องมือทั้งหมดที่มนุษย์รู้จัก https://github.com/google/sanitizers/wiki/AddressSanitizerComparisonOfMemoryTools/d06210f759fec97066888e5f27c7e722832b0924

ตัวอย่างการวิเคราะห์จะเป็น:

main.c

#include <stdlib.h>

void * my_malloc(size_t n) {
    return malloc(n);
}

void leaky(size_t n, int do_leak) {
    void *p = my_malloc(n);
    if (!do_leak) {
        free(p);
    }
}

int main(void) {
    leaky(0x10, 0);
    leaky(0x10, 1);
    leaky(0x100, 0);
    leaky(0x100, 1);
    leaky(0x1000, 0);
    leaky(0x1000, 1);
}

GitHub ต้นน้ำต้นน้ำ

เราจะพยายามดูว่าเครื่องมือที่แตกต่างกันทำอะไรอย่างชัดเจนชี้ให้เราไปที่การเรียกรั่ว

tcmalloc จาก gperftools โดย Google

https://github.com/gperftools/gperftools

การใช้งานบน Ubuntu 19.04:

sudo apt-get install google-perftools
gcc -ggdb3 -o main.out main.c -ltcmalloc
PPROF_PATH=/usr/bin/google-pprof \
  HEAPCHECK=normal \
  HEAPPROFILE=ble \
  ./main.out \
;
google-pprof main.out ble.0001.heap --text

เอาต์พุตของการรันโปรแกรมมีการวิเคราะห์การรั่วไหลของหน่วยความจำ:

WARNING: Perftools heap leak checker is active -- Performance may suffer
Starting tracking the heap
Dumping heap profile to ble.0001.heap (Exiting, 4 kB in use)
Have memory regions w/o callers: might report false leaks
Leak check _main_ detected leaks of 272 bytes in 2 objects
The 2 largest leaks:
Using local file ./main.out.
Leak of 256 bytes in 1 objects allocated from:
        @ 555bf6e5815d my_malloc
        @ 555bf6e5817a leaky
        @ 555bf6e581d3 main
        @ 7f71e88c9b6b __libc_start_main
        @ 555bf6e5808a _start
Leak of 16 bytes in 1 objects allocated from:
        @ 555bf6e5815d my_malloc
        @ 555bf6e5817a leaky
        @ 555bf6e581b5 main
        @ 7f71e88c9b6b __libc_start_main
        @ 555bf6e5808a _start


If the preceding stack traces are not enough to find the leaks, try running THIS shell command:

pprof ./main.out "/tmp/main.out.24744._main_-end.heap" --inuse_objects --lines --heapcheck  --edgefraction=1e-10 --nodefraction=1e-10 --gv

If you are still puzzled about why the leaks are there, try rerunning this program with HEAP_CHECK_TEST_POINTER_ALIGNMENT=1 and/or with HEAP_CHECK_MAX_POINTER_OFFSET=-1
If the leak report occurs in a small fraction of runs, try running with TCMALLOC_MAX_FREE_QUEUE_SIZE of few hundred MB or with TCMALLOC_RECLAIM_MEMORY=false, it might help find leaks more re
Exiting with error code (instead of crashing) because of whole-program memory leaks

และผลลัพธ์ของgoogle-pprofการวิเคราะห์การใช้งานฮีปประกอบด้วย:

Using local file main.out.
Using local file ble.0001.heap.
Total: 0.0 MB
     0.0 100.0% 100.0%      0.0 100.0% my_malloc
     0.0   0.0% 100.0%      0.0 100.0% __libc_start_main
     0.0   0.0% 100.0%      0.0 100.0% _start
     0.0   0.0% 100.0%      0.0 100.0% leaky
     0.0   0.0% 100.0%      0.0 100.0% main

ผลลัพธ์ชี้ไปที่สองในสามของการรั่วไหล:

Leak of 256 bytes in 1 objects allocated from:
        @ 555bf6e5815d my_malloc
        @ 555bf6e5817a leaky
        @ 555bf6e581d3 main
        @ 7f71e88c9b6b __libc_start_main
        @ 555bf6e5808a _start
Leak of 16 bytes in 1 objects allocated from:
        @ 555bf6e5815d my_malloc
        @ 555bf6e5817a leaky
        @ 555bf6e581b5 main
        @ 7f71e88c9b6b __libc_start_main
        @ 555bf6e5808a _start

ฉันไม่แน่ใจว่าทำไมคนที่สามไม่ปรากฏ

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

ดังที่กล่าวไว้ในเอาต์พุตตัวเองสิ่งนี้ทำให้เกิดการดำเนินการช้าลงอย่างมาก

เอกสารเพิ่มเติมที่:

ดูเพิ่มเติมที่: วิธีใช้ TCMalloc

ทดสอบใน Ubuntu 19.04, google-perftools 2.5-2

ที่อยู่ Sanitizer (ASan) โดย Google

https://github.com/google/sanitizers

ก่อนหน้านี้กล่าวถึงที่: วิธีการค้นหาหน่วยความจำรั่วในรหัส C ++ / โครงการ? สิ่งที่ต้องทำเทียบกับ tcmalloc

สิ่งนี้ได้รวมเข้ากับ GCC แล้วดังนั้นคุณสามารถทำได้:

gcc -fsanitize=address -ggdb3 -o main.out main.c
./main.out 

และเอาต์พุตการประมวลผล:

=================================================================
==27223==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 4096 byte(s) in 1 object(s) allocated from:
    #0 0x7fabbefc5448 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.5+0x10c448)
    #1 0x55bf86c5f17c in my_malloc /home/ciro/test/main.c:4
    #2 0x55bf86c5f199 in leaky /home/ciro/test/main.c:8
    #3 0x55bf86c5f210 in main /home/ciro/test/main.c:20
    #4 0x7fabbecf4b6a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x26b6a)

Direct leak of 256 byte(s) in 1 object(s) allocated from:
    #0 0x7fabbefc5448 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.5+0x10c448)
    #1 0x55bf86c5f17c in my_malloc /home/ciro/test/main.c:4
    #2 0x55bf86c5f199 in leaky /home/ciro/test/main.c:8
    #3 0x55bf86c5f1f2 in main /home/ciro/test/main.c:18
    #4 0x7fabbecf4b6a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x26b6a)

Direct leak of 16 byte(s) in 1 object(s) allocated from:
    #0 0x7fabbefc5448 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.5+0x10c448)
    #1 0x55bf86c5f17c in my_malloc /home/ciro/test/main.c:4
    #2 0x55bf86c5f199 in leaky /home/ciro/test/main.c:8
    #3 0x55bf86c5f1d4 in main /home/ciro/test/main.c:16
    #4 0x7fabbecf4b6a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x26b6a)

SUMMARY: AddressSanitizer: 4368 byte(s) leaked in 3 allocation(s).

ซึ่งระบุการรั่วไหลทั้งหมดอย่างชัดเจน ดี!

ASan ยังสามารถทำการตรวจสอบเด็ด ๆ อื่น ๆ เช่นการเขียนนอกขอบเขต: ตรวจพบการซ้อน

ทดสอบใน Ubuntu 19.04, GCC 8.3.0

valgrind

http://www.valgrind.org/

พูดถึงก่อนหน้านี้ที่: https://stackoverflow.com/a/37661630/895245

การใช้งาน:

sudo apt-get install valgrind
gcc -ggdb3 -o main.out main.c
valgrind --leak-check=yes ./main.out

เอาท์พุท:

==32178== Memcheck, a memory error detector
==32178== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==32178== Using Valgrind-3.14.0 and LibVEX; rerun with -h for copyright info
==32178== Command: ./main.out
==32178== 
==32178== 
==32178== HEAP SUMMARY:
==32178==     in use at exit: 4,368 bytes in 3 blocks
==32178==   total heap usage: 6 allocs, 3 frees, 8,736 bytes allocated
==32178== 
==32178== 16 bytes in 1 blocks are definitely lost in loss record 1 of 3
==32178==    at 0x483874F: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==32178==    by 0x10915C: my_malloc (main.c:4)
==32178==    by 0x109179: leaky (main.c:8)
==32178==    by 0x1091B4: main (main.c:16)
==32178== 
==32178== 256 bytes in 1 blocks are definitely lost in loss record 2 of 3
==32178==    at 0x483874F: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==32178==    by 0x10915C: my_malloc (main.c:4)
==32178==    by 0x109179: leaky (main.c:8)
==32178==    by 0x1091D2: main (main.c:18)
==32178== 
==32178== 4,096 bytes in 1 blocks are definitely lost in loss record 3 of 3
==32178==    at 0x483874F: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==32178==    by 0x10915C: my_malloc (main.c:4)
==32178==    by 0x109179: leaky (main.c:8)
==32178==    by 0x1091F0: main (main.c:20)
==32178== 
==32178== LEAK SUMMARY:
==32178==    definitely lost: 4,368 bytes in 3 blocks
==32178==    indirectly lost: 0 bytes in 0 blocks
==32178==      possibly lost: 0 bytes in 0 blocks
==32178==    still reachable: 0 bytes in 0 blocks
==32178==         suppressed: 0 bytes in 0 blocks
==32178== 
==32178== For counts of detected and suppressed errors, rerun with: -v
==32178== ERROR SUMMARY: 3 errors from 3 contexts (suppressed: 0 from 0)

ดังนั้นจึงตรวจพบรอยรั่วทั้งหมดอีกครั้ง

ดูเพิ่มเติม: ฉันจะใช้ valgrind เพื่อค้นหาหน่วยความจำรั่วได้อย่างไร

ทดสอบใน Ubuntu 19.04, valgrind 3.14.0


4

Visual Leak Detector (VLD)เป็นระบบตรวจจับการรั่วไหลของหน่วยความจำโอเพ่นซอร์สฟรีที่แข็งแกร่งสำหรับ Visual C ++

เมื่อคุณเรียกใช้โปรแกรมของคุณภายใต้ดีบักเกอร์ Visual Studio Visual Leak Detector จะแสดงผลรายงานการรั่วไหลของหน่วยความจำเมื่อสิ้นสุดเซสชันการดีบัก รายงานการรั่วไหลรวมถึงการโทรสแต็คเต็มแสดงให้เห็นว่าการจัดสรรบล็อกหน่วยความจำรั่วใด ๆ ดับเบิลคลิกที่บรรทัดใน call stack เพื่อข้ามไปยังไฟล์และบรรทัดในหน้าต่างตัวแก้ไข

หากคุณมีปัญหาการถ่ายโอนข้อมูลคุณสามารถใช้!heap -lคำสั่งWindbg ซึ่งจะตรวจพบบล็อกฮีปที่รั่วไหลออกมา เปิดตัวเลือก gflags ดีกว่า:“ สร้างฐานข้อมูลการติดตามสแต็กของโหมดผู้ใช้” จากนั้นคุณจะเห็นสแตกการเรียกการจัดสรรหน่วยความจำ


4

MTunerเป็นเครื่องมือสร้างโปรไฟล์หน่วยความจำแบบหลายแพลตฟอร์มการตรวจหารอยรั่วและการวิเคราะห์ที่รองรับคอมไพเลอร์ MSVC, GCC และ Clang คุณสมบัติรวมถึง:

  • ประวัติการใช้งานไทม์ไลน์ของการใช้หน่วยความจำและบล็อกหน่วยความจำแบบสด
  • การกรองการดำเนินงานหน่วยความจำที่มีประสิทธิภาพขึ้นอยู่กับกอง, แท็กหน่วยความจำ, ช่วงเวลา, ฯลฯ
  • SDK สำหรับการใช้เครื่องมือด้วยตนเองพร้อมรหัสที่มาแบบเต็ม
  • สนับสนุนการรวมอย่างต่อเนื่องผ่านการใช้บรรทัดคำสั่ง
  • เรียกการนำสแต็คทรีและแผนผังแผนผังต้นไม้
  • ล้นหลาม.

ผู้ใช้สามารถสร้างโปรไฟล์แพลตฟอร์มซอฟต์แวร์ที่มี GCC หรือ Clang cross compilers MTuner มาพร้อมกับระบบรองรับ Windows, PlayStation 4 และ PlayStation 3


นี่ควรเป็นคำตอบที่ยอมรับได้ เป็นเครื่องมือที่ยอดเยี่ยมและสามารถจัดการกับปริมาณการจัดสรร / การจัดสรรคืนที่ผู้อื่นไม่สามารถทำได้
Serge Rogatch

3

ใน Windows คุณสามารถใช้CRT แก้ปัญหากอง

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

ใช่ไม่ใช้การจัดการหน่วยความจำด้วยตนเอง(ถ้าคุณเคยโทรdeleteหรือdelete[]ด้วยตนเองแล้วคุณทำผิด) ใช้ RAII และสมาร์ทพอยน์เตอร์ จำกัด การจัดสรรฮีปให้น้อยที่สุด (ส่วนใหญ่ตัวแปรอัตโนมัติจะเพียงพอ)


3

ตอบคำถามส่วนที่สองของคุณ

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

ใช่แล้ว และนี่คือหนึ่งในความแตกต่างที่สำคัญระหว่าง C และ C ++

ใน C ++ คุณไม่ควรโทรnewหรือdeleteรหัสผู้ใช้ RAIIเป็นเทคนิคที่ใช้กันอย่างแพร่หลายซึ่งค่อนข้างแก้ปัญหาการจัดการทรัพยากร ทรัพยากรทุกอย่างในโปรแกรมของคุณ (ทรัพยากรคือสิ่งใด ๆ ที่จะต้องได้รับและหลังจากนั้นปล่อยออกมา: จัดการไฟล์ซ็อกเก็ตเครือข่ายการเชื่อมต่อฐานข้อมูล แต่ยังจัดสรรหน่วยความจำธรรมดาและในบางกรณีคู่ของการเรียก API (BeginX) ) / EndX (), LockY (), UnlockY ()), ควรห่อในชั้นเรียนโดยที่:

  • ตัวสร้างได้รับทรัพยากร (โดยการโทรnewถ้าทรัพยากรเป็นการจัดสรร memroy)
  • destructor ที่ปล่อยทรัพยากร
  • การคัดลอกและการมอบหมายอาจป้องกันได้ (โดยการสร้างตัวคัดลอกและตัวดำเนินการกำหนดค่าเป็นส่วนตัว) หรือนำมาใช้เพื่อทำงานอย่างถูกต้อง (ตัวอย่างเช่นการโคลนทรัพยากรพื้นฐาน)

คลาสนี้ถูกสร้างอินสแตนซ์แบบโลคัลบนสแต็กหรือเป็นสมาชิกคลาสและไม่ใช่โดยการเรียกnewและเก็บตัวชี้

คุณไม่จำเป็นต้องกำหนดคลาสเหล่านี้ด้วยตนเอง คอนเทนเนอร์ไลบรารีมาตรฐานทำงานในลักษณะนี้เช่นกันเพื่อให้วัตถุใด ๆ ที่เก็บไว้ในstd::vectorได้รับอิสระเมื่อเวกเตอร์ถูกทำลาย ดังนั้นอีกครั้งอย่าเก็บตัวชี้ไว้ในคอนเทนเนอร์ (ซึ่งจะทำให้คุณต้องโทรnewและdelete) แต่ควรเป็นวัตถุเอง (ซึ่งจะช่วยให้คุณจัดการหน่วยความจำได้ฟรี ) ในทำนองเดียวกันคลาสสมาร์ทพอยน์เตอร์สามารถใช้เพื่อห่อวัตถุที่เพิ่งได้รับการจัดสรรnewและควบคุมอายุการใช้งาน

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

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


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

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

3

AddressSanitizer (ASan) เป็นเครื่องตรวจจับข้อผิดพลาดของหน่วยความจำที่รวดเร็ว พบข้อบกพร่องใช้เกินและ {heap, stack, global} -buffer ในโปรแกรม C / C ++ พบว่า:

  • ใช้หลังจากฟรี (dereling ตัวชี้ห้อย)
  • บัฟเฟอร์ล้นมากเกินไป
  • กองบัฟเฟอร์ล้น
  • บัฟเฟอร์ล้นทั่วโลก
  • ใช้หลังจากกลับมา
  • ข้อบกพร่องในการเริ่มต้น

เครื่องมือนี้เร็วมาก การชะลอตัวโดยเฉลี่ยของโปรแกรมที่เป็นเครื่องมือคือ ~ 2x



0

นอกจากเครื่องมือและวิธีการที่ให้ไว้ในคำตอบอื่น ๆ แล้วเครื่องมือการวิเคราะห์รหัสแบบคงที่ยังสามารถใช้ในการตรวจจับการรั่วไหลของหน่วยความจำ (และปัญหาอื่น ๆ เช่นกัน) เครื่องมือที่แข็งแกร่งฟรีคือ Cppcheck แต่มีเครื่องมืออื่น ๆ มากมาย Wikipediaมีรายการเครื่องมือวิเคราะห์รหัสคงที่


-1

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


-3

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

น่าเสียดายที่นี่ทำงานได้มากกว่าที่ควรจะเป็นเพราะ C ++ ไม่มีการใช้งาน "โอเปอเรเตอร์" เกินพิกัด มันยิ่งทำงานมากขึ้นในที่ที่มีความหลากหลาย

แต่นี่เป็นความพยายามที่คุ้มค่าเพราะคุณไม่ต้องกังวลเกี่ยวกับการรั่วไหลของหน่วยความจำซึ่งหมายความว่าคุณไม่จำเป็นต้องมองหาพวกเขา

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