التقاط تسرب الذاكرة في C / C ++


أحييك يا خابروفيانس!


اليوم أريد أن أفتح القليل من الضوء على كيفية التعامل مع تسرب الذاكرة في C أو C ++.


يوجد بالفعل مقالان عن Habré ، وهما: نحن نتعامل مع تسرب الذاكرة (C ++ CRT) و تسرب الذاكرة في C ++: مرئي للكشف عن التسرب . ومع ذلك ، أعتقد أنه لم يتم الكشف عنها بشكل كافٍ ، أو أن هذه الطرق قد لا تعطي النتيجة التي تحتاجها ، لذلك أود أن أبذل قصارى جهدنا لإبراز جميع الطرق المتاحة من أجل تسهيل حياتك.


ويندوز - التنمية
لنبدأ باستخدام Windows ، أي تطوير Visual Studio ، لأن معظم المبرمجين المبتدئين يكتبون خصيصًا لهذا IDE.



لفهم ما يحدث ، أقدم مثالًا حقيقيًا:


Main.c
struct Student create_student(); void ControlMenu(); int main() { ControlMenu(); return 0; } void ShowListMenu(int kX) { char listMenu[COUNT_LIST_MENU][55] = { {"Read students from file"}, {"Input student and push"}, {"Input student and push it back"}, {"Input student and push it after student"}, {"Delete last student"}, {"Write students to file"}, {"Find student"}, {"Sort students"}, {"Show list of students"}, {"Exit"} }; for (int i = 0; i < COUNT_LIST_MENU; i++) { if (i == kX) { printf("%s", listMenu[i]); printf(" <=\n"); } else printf("%s\n", listMenu[i]); } } void ControlMenu() { struct ListOfStudents* list = NULL; int kX = 0, key; int exit = FALSE; ShowListMenu(kX); do { key = _getch(); switch (key) { case 72: //up { if (kX == 0) kX = COUNT_LIST_MENU-1; else kX--; }break; case 80: //down { if (kX == COUNT_LIST_MENU-1) kX = 0; else kX++; }break; case 13: { if (kX == 0) { int sizeStudents = 0; struct Student* students = (struct Student*)malloc(1 * sizeof(struct Student)); char* path = (char*)malloc(255 * sizeof(char)); printf("Put the path to file with students: "); scanf("%s", path); int size = 0; students = read_students(path, &size); if (students == NULL) { printf("Can't open this file.\n"); } else { for (int i = 0; i < size; i++) { if (i == 0) { list = init(students[i]); } else { list = add_new_elem_to_start(list, students[i]); } } } free(students); printf("\nPress any key to continue..."); getchar(); getchar(); free(path); } else if (kX == 1 || kX == 2 || kX == 3 || kX == 6) { struct Student student = create_student(); if (kX == 1) { if (list == NULL) { list = init(student); } else { list = add_new_elem_to_start(list, student); } printf("\nPress any key to continue..."); getchar(); getchar(); } else if (kX == 2) { if (list == NULL) { list = init(student); } else { list = add_new_elem_to_end(list, student); } printf("\nPress any key to continue..."); getchar(); getchar(); } else if (kX == 3) { if (list == NULL) { list = init(student); printf("The list was empty, so, list have been created.\n"); } else { int position; printf("Put the position: "); scanf("%d", &position); list = add_new_elem_after_pos(list, student, position); } printf("\nPress any key to continue..."); getchar(); getchar(); } else { if (find_elem(list, student)) printf("Student exist"); else printf("Student doesn't exist"); printf("\nPress any key to continue..."); getchar(); getchar(); } } else if (kX == 4) { if (list == NULL) { printf("List is empty.\n"); } else { list = delete_elem(list); } printf("\nPress any key to continue..."); getchar(); getchar(); } else if (kX == 5) { char* path = (char*)malloc(255 * sizeof(char)); printf("Put the path to file with students: "); scanf("%s", path); if (write_students(list, path) == 0) { printf("Can't write"); printf("\nPress any key to continue..."); getchar(); getchar(); } free(path); } else if (kX == 7) { if (list == NULL) { printf("List is empty.\n"); } else { list = sort_list(list); } printf("\nThe list was successfully sorted"); printf("\nPress any key to continue..."); getchar(); getchar(); } else if (kX == 8) { system("cls"); show_list(list); printf("\nPress any key to continue..."); getchar(); getchar(); } else exit = TRUE; }break; case 27: { exit = TRUE; }break; } system("cls"); ShowListMenu(kX); } while (exit == FALSE); while (list != NULL) { list = delete_elem(list); } } struct Student create_student() { struct Student new_student; do { printf("Write the name of student\n"); scanf("%s", new_student.first_name); } while (strlen(new_student.first_name) == 0); do { printf("Write the last name of student\n"); scanf("%s", new_student.last_name); } while (strlen(new_student.last_name) == 0); do { printf("Write the patronyminc of student\n"); scanf("%s", new_student.patronyminc); } while (strlen(new_student.patronyminc) == 0); do { printf("Write the city of student\n"); scanf("%s", new_student.city); } while (strlen(new_student.city) == 0); do { printf("Write the district of student\n"); scanf("%s", new_student.disctrict); } while (strlen(new_student.disctrict) == 0); do { printf("Write the country of student\n"); scanf("%s", new_student.country); } while (strlen(new_student.country) == 0); do { printf("Write the phone number of student\n"); scanf("%s", new_student.phoneNumber); } while (strlen(new_student.phoneNumber) != 13); char* choose = (char*)malloc(255 * sizeof(char)); while (TRUE) { printf("Does student live in hostel? Y - yes, N - no\n"); scanf("%s", choose); if (strcmp(choose, "y") == 0 || strcmp(choose, "Y") == 0) { new_student.is_live_in_hostel = TRUE; break; } if (strcmp(choose, "n") == 0 || strcmp(choose, "n") == 0) { new_student.is_live_in_hostel = FALSE; break; } } while (TRUE) { printf("Does student get scholarship? Y - yes, N - no\n"); scanf("%s", choose); if (strcmp(choose, "y") == 0 || strcmp(choose, "Y") == 0) { new_student.is_live_in_hostel = TRUE; break; } if (strcmp(choose, "n") == 0 || strcmp(choose, "n") == 0) { new_student.is_live_in_hostel = FALSE; break; } } free(choose); for (int i = 0; i < 3; i++) { char temp[10]; printf("Write the %d mark of ZNO\n", i + 1); scanf("%s", temp); new_student.mark_zno[i] = atof(temp); if (new_student.mark_zno[i] == 0) { i--; } } return new_student; } 

وأيضًا يوجد Student.h و Student.c فيهما الإعلان عن الهياكل والوظائف.


هناك مهمة: لإظهار عدم وجود تسرب الذاكرة. أول ما يتبادر إلى الذهن هو CRT. كل شيء بسيط جدا هنا.


في الجزء العلوي من الملف حيث يوجد main ، أضف قطعة الكود هذه:


 #define __CRTDBG_MAP_ALLOC #include <crtdbg.h> #define DEBUG_NEW new(_NORMAL_BLOCK, __FILE__, __LINE__) #define new DEBUG_NEW 

وقبل return 0 تحتاج إلى تسجيل هذا: _CrtDumpMemoryLeaks(); .


نتيجة لذلك ، في وضع التصحيح ، سيقوم الاستوديو بإخراج هذا:


 Detected memory leaks! Dumping objects -> {79} normal block at 0x00A04410, 376 bytes long. Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD Object dump complete. 

سوبر! الآن أنت تعرف أن لديك تسرب للذاكرة. أنت الآن بحاجة إلى القضاء على هذا ، لذلك تحتاج فقط إلى معرفة أين ننسى مسح الذاكرة. وهنا تنشأ مشكلة: أين ، في الواقع ، تم تخصيص هذه الذاكرة؟



بعد تكرار كل الخطوات ، اكتشفت أن الذاكرة مفقودة في مكان ما هنا:


 if (kX == 0) { int sizeStudents = 0; struct Student* students = (struct Student*)malloc(1 * sizeof(struct Student)); char* path = (char*)malloc(255 * sizeof(char)); printf("Put the path to file with students: "); scanf("%s", path); int size = 0; students = read_students(path, &size); if (students == NULL) { printf("Can't open this file.\n"); } else { for (int i = 0; i < size; i++) { if (i == 0) { list = init(students[i]); } else { list = add_new_elem_to_start(list, students[i]); } } } free(students); printf("\nPress any key to continue..."); getchar(); getchar(); free(path); } 

لكن كيف هذا؟ هل أنا تحرير كل شيء؟ أم لا؟


وهنا فاتني حقًا Valgrind ، بتتبع مكالماته ...


نتيجة لذلك ، بعد 15 دقيقة من المشي ، وجدت نظيرًا لـ Valgrind - Visual Leak Detector . هذه مكتبة تابعة لجهة خارجية ، وهي عبارة عن غلاف يحتوي على CRT وعد بإظهار التتبع! هذا ما احتاجه


لتثبيته ، تحتاج إلى الانتقال إلى المستودع والعثور على vld-2.5.1-setup.exe في الأصول


صحيح ، كان التحديث الأخير من وقت Visual Studio 2015 ، لكنه يعمل مع Visual Studio 2019. التثبيت قياسي ، ما عليك سوى اتباع التعليمات.


لتمكين VLD ، يجب عليك التسجيل #include <vld.h> .


ميزة هذه الأداة المساعدة أنه لا يمكنك تشغيل وضع التصحيح (F5) ، لأنه يتم عرض كل شيء في وحدة التحكم. في البداية ، سيتم عرض هذا:


 Visual Leak Detector read settings from: C:\Program Files (x86)\Visual Leak Detector\vld.ini Visual Leak Detector Version 2.5.1 installed. 

وهنا ما سوف ينتج عندما تسرب الذاكرة:


 WARNING: Visual Leak Detector detected memory leaks! ---------- Block 1 at 0x01405FD0: 376 bytes ---------- Leak Hash: 0x555D2B67, Count: 1, Total 376 bytes Call Stack (TID 8908): ucrtbased.dll!malloc() test.exe!0x00F41946() test.exe!0x00F42E1D() test.exe!0x00F44723() test.exe!0x00F44577() test.exe!0x00F4440D() test.exe!0x00F447A8() KERNEL32.DLL!BaseThreadInitThunk() + 0x19 bytes ntdll.dll!RtlGetAppContainerNamedObjectPath() + 0xED bytes ntdll.dll!RtlGetAppContainerNamedObjectPath() + 0xBD bytes Data: CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD ........ ........ CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD ........ ........ CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD ........ ........ CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD ........ ........ CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD ........ ........ CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD ........ ........ CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD ........ ........ CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD ........ ........ CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD ........ ........ CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD ........ ........ CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD ........ ........ CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD ........ ........ CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD ........ ........ CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD ........ ........ CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD ........ ........ CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD ........ ........ Visual Leak Detector detected 1 memory leak (412 bytes). Largest number used: 3115 bytes. Total allocations: 3563 bytes. Visual Leak Detector is now exiting. 

هنا ، أرى الأثر! لذا ، أين هي سطور الكود؟ أين هي أسماء الوظائف؟



حسنًا ، تم الوفاء بالوعد ، لكن هذه ليست النتيجة التي أردت.


لا يزال هناك خيار واحد وجدته في Google: لقطة من الذاكرة. يتم ذلك ببساطة: في وضع التصحيح ، عندما تعود إلى الصفر ، تحتاج إلى الانتقال إلى علامة التبويب "استخدام الذاكرة" في أداة التشخيص والنقر فوق "التقاط لقطة". ربما سيتم تعطيل هذه الميزة لك ، كما في لقطة الشاشة الأولى. ثم تحتاج إلى تشغيل وإعادة تشغيل التصحيح.


أول لقطة للشاشة


لقطة الثانية


بعد التقاط الصورة ، سترى حجمًا أسفل الكومة. أعتقد أن هذا هو مقدار الذاكرة التي تم تخصيصها أثناء البرنامج. انقر على هذا الحجم. سيكون لدينا نافذة تحتوي على كائنات مخزنة في هذه الكومة. للاطلاع على معلومات مفصلة ، تحتاج إلى تحديد كائن والنقر فوق الزر "مثيلات تمثيل كائن فو".


لقطة للشاشة الثالثة


قطة الرابعة


نعم! هذا نصر! تتبع كامل مع موقع الدعوة! هذا هو ما كان مطلوبا في الأصل.


لينكس - التنمية
الآن ، دعونا نرى ما يحدث على لينكس.

على نظام Linux ، توجد أداة مساعدة valgrind. لتثبيت valgrind ، تحتاج إلى تسجيل sudo apt install valgrind في وحدة التحكم (لعائلة دبيان).


كتبت برنامجًا صغيرًا يملأ صفيفًا ديناميكيًا ، ولكن في نفس الوقت ، لا يتم مسح الذاكرة:


main.c
 #include <stdlib.h> #include <stdio.h> #define N 10 int main() { int * mas = (int *)malloc(N * sizeof(int)); for(int i = 0; i < N; i++) { *(mas+i) = i; printf("%d\t", *(mas+i)); } printf("\n"); return 0; } 

بعد تجميع البرنامج باستخدام CLang ، نحصل على ملف .out ، والذي نرميه إلى valgrind.


باستخدام valgrind ./a.out . كيف يعمل valgrind ، أعتقد أنه من المنطقي وصفه في مقالة منفصلة ، والآن ، كيف يتم تشغيل البرنامج ، سوف يقوم valgrind بإخراج هذا:


 ==2342== HEAP SUMMARY: ==2342== in use at exit: 40 bytes in 1 blocks ==2342== total heap usage: 2 allocs, 1 frees, 1,064 bytes allocated ==2342== ==2342== Searching for pointers to 1 not-freed blocks ==2342== Checked 68,984 bytes ==2342== ==2342== LEAK SUMMARY: ==2342== definitely lost: 40 bytes in 1 blocks ==2342== indirectly lost: 0 bytes in 0 blocks ==2342== possibly lost: 0 bytes in 0 blocks ==2342== still reachable: 0 bytes in 0 blocks ==2342== suppressed: 0 bytes in 0 blocks ==2342== Rerun with --leak-check=full to see details of leaked memory ==2342== ==2342== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) ==2342== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) 

وبالتالي ، يظهر valgrind حتى الآن مقدار الذاكرة المفقودة. لمعرفة أين تم تخصيص الذاكرة ، عليك أن تكتب --leak-check=full ، ثم ، سوف --leak-check=full ، بالإضافة إلى ما سبق ، هذا:


 ==2348== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1 ==2348== at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so) ==2348== by 0x40053A: main (in /home/hunterlan/Habr/a.out) 

بالطبع ، لا توجد سلسلة محددة هنا ، ولكن تم بالفعل تحديد وظيفة ، وهي أخبار جيدة.


هناك بدائل لل valgrind ، مثل strace أو Dr.Memory ، لكنني لم استخدمها ، وهي تستخدم أساسا حيث valgrind عاجزة.


النتائج


أنا سعيد لأنني واجهت مشكلة العثور على تسرب للذاكرة في Visual Studio ، حيث تعلمت الكثير من الأدوات الجديدة ، ومتى وكيفية استخدامها ، وبدأت في فهم كيفية عمل هذه الأدوات.


شكرا لاهتمامكم ، رمز الكتابة الجيدة لك!

Source: https://habr.com/ru/post/ar480368/


All Articles