在C / C ++中捕获内存泄漏


我问你,哈布罗夫奇人!


今天,我想谈谈如何处理C或C ++中的内存泄漏。


Habré上已有两篇文章,分别是: 我们处理内存泄漏(C ++ CRT)C ++中的内存泄漏:Visual Leak Detector 。 但是,我相信它们没有被充分公开,否则这些方法可能无法提供您所需的结果,因此,我想尽可能地找出所有可用的方法,以使您的生活更轻松。


Windows-开发
让我们从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.hStudent.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();


结果,在Debug模式下,工作室将输出以下内容:


 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中找到了一个选项:内存快照。 这很简单:在调试模式下,当您返回0时,需要转到诊断工具中的“内存使用情况”选项卡,然后单击“获取快照”。 如第一个屏幕截图所示,也许您将禁用此功能。 然后,您需要打开并重新启动调试。


第一张截图


第二张截图


拍照后,您会在堆下看到一个大小。 我认为这是程序期间分配的内存量。 点击这个尺寸。 我们将有一个窗口,其中包含存储在此堆中的对象。 要查看详细信息,需要选择一个对象,然后单击“ Foo对象表示实例”按钮。


第三张截图


第四张截图


是的 这是胜利! 完整的跟踪与呼叫位置! 这是最初需要的。


Linux-开发
现在,让我们看看在Linux上会发生什么。

在Linux上,存在valgrind实用程序。 要安装valgrind,您需要在控制台中注册sudo apt install valgrind (对于Debian系列)。


我编写了一个填充动态数组的小程序,但同时未清除内存:


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 ,然后valgrind除了上述内容外,还将输出以下内容:


 ==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/zh-CN480368/


All Articles