如何利用分类信息网站做推广,哪些网站做的好看,网站二级域名查询,网站网页设计屏幕尺寸在日常的开发中#xff0c;内存泄漏是一种比较比较棘手的问题#xff0c;这是由于其具有隐蔽性#xff0c;即使发生了泄漏#xff0c;很难检测到并且不好定位到哪里导致的泄漏。如果程序在运行的过程中不断出现内存泄漏#xff0c;那么越来越多的内存得不到释放#xff0… 在日常的开发中内存泄漏是一种比较比较棘手的问题这是由于其具有隐蔽性即使发生了泄漏很难检测到并且不好定位到哪里导致的泄漏。如果程序在运行的过程中不断出现内存泄漏那么越来越多的内存得不到释放可用的内存越来越小最终导致系统无法正常运行。 本文主要介绍一种能够检测内存的方法方便在日常的开发过程中排除程序是否存在内存泄漏的情况。 内存泄漏主要针对在堆区分配的内存无法得到释放在堆区分配内存的方法有malloc和new对应释放内存为free和delete。new和delete是针对C的本文主要监控通过new分配的内存的情况。 new和delete是C语言提供的运算符在程序可以对这两个运算符进行重载如下所示。 void * operator new(size_t size){void *ptr malloc(size);LOGI(new size %d ptr %p ,size, ptr);return ptr;
}void * operator new[](size_t size){void *ptr malloc(size);LOGI(new array size %d ptr %p ,size, ptr);return ptr;
}void operator delete(void *ptr) {LOGI(delete pointer %p,ptr);if(ptr nullptr) return;free(ptr);
}
void operator delete[](void *ptr) {LOGI(delete array %p,ptr);if(ptr nullptr) return;free(ptr);
} 上面重载了new、new[]delete和delete[]四个运算符为了验证正常使用new和delete操作能够调用以上的运算符下面定义一个简单的类
class MEM{
public:MEM(){LOGI(MEM constructor);}~MEM(){LOGI(MEM destructor);}
private:int a;
}; 这里定义MEM类并在构造函数和析构函数加了打印主要为了验证它们是否会被调用下面开始使用new和delete申请和释放内存如下所示。
LOGI(new int---------);
int *p1 new int(3);
LOGI(new int array---------);
int *p2 new int[5];
LOGI(new MEM object---------);
MEM * p3 new MEM();
LOGI(new MEM object array---------);
MEM * p4 new MEM[5];LOGI(delete p1---------);
delete p1;
LOGI(delete p2---------);
delete []p2;
LOGI(delete p3---------);
delete p3;
LOGI(delete p4---------);
delete []p4;
LOGI(---------);
上面的测试代码流程如下
new int(3) 请求分配一个整数
new int[5] 请求分配一个数组大小为5
new MEM() 请求分配一个MEM类型的对象
new MEM[5] 请求分配一个MEM类型的数组大小为5
最后调用delete分别释放以上分配的内存。运行以上代码打印结果如下。
09:57:35.596 28994-29029 Native I new int---------
09:57:35.596 28994-29029 Native I new size 4 ptr 0xdc5191c0
09:57:35.596 28994-29029 Native I new int array---------
09:57:35.596 28994-29029 Native I new array size 20 ptr 0xdc50ed60
09:57:35.596 28994-29029 Native I new MEM object---------
09:57:35.596 28994-29029 Native I new size 4 ptr 0xdc5191c8
09:57:35.596 28994-29029 Native I MEM constructor
09:57:35.596 28994-29029 Native I new MEM object array---------
09:57:35.596 28994-29029 Native I new array size 24 ptr 0xdc50edc0
09:57:35.596 28994-29029 Native I MEM constructor
09:57:35.596 28994-29029 Native I MEM constructor
09:57:35.596 28994-29029 Native I delete p1---------
09:57:35.597 28994-29029 Native I delete pointer 0xdc5191c0
09:57:35.597 28994-29029 Native I delete p2---------
09:57:35.597 28994-29029 Native I delete array 0xdc50ed60
09:57:35.597 28994-29029 Native I delete p3---------
09:57:35.597 28994-29029 Native I MEM destructor
09:57:35.597 28994-29029 Native I delete pointer 0xdc5191c8
09:57:35.597 28994-29029 Native I delete p4---------
09:57:35.597 28994-29029 Native I MEM destructor
09:57:35.597 28994-29029 Native I MEM destructor
09:57:35.597 28994-29029 Native I delete array 0xdc50edc0
09:57:35.597 28994-29029 Native I --------- 通过以上log可以看到重载的运算符new、delete构造函数和析构函数里都走进去了说明重载运算符是可以接管分配和释放内存的工作的而调用构造函数和析构函数还是由编译器处理了无须担心创建对象和销毁对象时这两个函数没有被调用。 尽管通过重载new和delete运算符可以接管内存的分配和释放工作但是在new操作符函数中还是无法指定是谁申请的内存为了能确定是哪里申请的内存需要对new操作符进行改进如下所示。
void * operator new(size_t size,const char * file, size_t line){LOGI(new size %d file: %s line %d,size, file, line);void *ptr malloc(size);return ptr;
}void * operator new[](size_t size,const char * file, size_t line){LOGI(new array size %d file: %s line %d,size, file, line);void *ptr malloc(size);return ptr;
}#define new new(__FILE__,__LINE__) 上面重新定义了new操作符加入了文件命和行号并且把new定义为一个宏调用new时自动加入文件名宏和行号宏这样在代码中调用new申请内存时自动带上对应的文件名和行号。有了文件名和行号就能知道哪个地方申请的内存。 为了统计当前系统内存的使用请求接下来要把内存申请的记录保存起来这里使用一个单链表对内存的申请信息进行保存链表的元素使用Node表示代码如下。 typedef struct Node{void *ptr;size_t size;char *file;size_t line;struct Node *next;
} Node;Node *head nullptr;
void addRecord(void *ptr, size_t size, const char *file, size_t line){LOGI(addRecord);Node * node (Node *)malloc(sizeof(Node));node-ptr ptr;node-size size;node-file (char *)malloc(strlen(file)1);strcpy(node-file,file);node-line line;node-next nullptr;if(head nullptr){head node;} else{node-next head;head node;}
}
void removeRecord(void *ptr){if(head nullptr) return;Node *p head;if(p-ptr ptr){head head-next;if(p-file ! nullptr){free(p-file);}free(p);return;}Node * q p-next;while (q ! nullptr){if(q-ptr ptr){p-next q-next;if(q-file ! nullptr){free(q-file);}free(q);return;}p q;q q-next;}
}void * operator new(size_t size,const char * file, size_t line){LOGI(new size %d file: %s line %d,size, file, line);void *ptr malloc(size);if(ptr ! nullptr){addRecord(ptr, size, file, line);}return ptr;
}void * operator new[](size_t size,const char * file, size_t line){LOGI(new array size %d file: %s line %d,size, file, line);void *ptr malloc(size);if(ptr ! nullptr){addRecord(ptr, size, file, line);}return ptr;
}void operator delete(void *ptr) {if(ptr nullptr) return;removeRecord(ptr);free(ptr);
}
void operator delete[](void *ptr) {if(ptr nullptr) return;removeRecord(ptr);free(ptr);
}#define new new(__FILE__,__LINE__) 链表元素使用Node表示Node包含了申请内存的地址大小、文件名、行号以及下一个Node的地址。 head表示链表头。 addRecord向链表添加记录 removeRecord根据指针从链表中释放对应的Node。 new运算符申请内存后向链表添加记录delete运算符从链表删除记录后再释放内存。 有了保存内存信息的聊吧可以统计当前内存的使用请求下面实现统计当前内存使用情况的快照。
int showSnapshot(){LOGI(Memory Snapshot Begin);int total 0;Node *p head;while (p ! nullptr){total p-size;LOGI(file %s line %d allocate size %d, p-file,p-line, p-size);p p-next;}LOGI(total memory allocate is %d, total);LOGI(Memory Snapshot End);return total;
} 在showSnapshot中先遍历链表打印当前内存的信息最后打印当前申请的总的内存。下面再来打印上面的测试代码的内存快照代码如下。
int *p1 new int(3);int *p2 new int[5];MEM * p3 new MEM();MEM * p4 new MEM[5];showSnapshot();delete p1;delete []p2;delete p3;delete []p4;在申请完所有的内存后调用showSnapshot打印当前内存的申请情况如下所示。
2024-05-29 10:11:47.489 29299-29334 Native com.example.memory.monitor I Memory Snapshot Begin
2024-05-29 10:11:47.489 29299-29334 Native com.example.memory.monitor I file D:/samples/Demos/AndroidSamples/Memroy/app/src/main/cpp/main.cpp line 213 allocate size 24
2024-05-29 10:11:47.489 29299-29334 Native com.example.memory.monitor I file D:/samples/Demos/AndroidSamples/Memroy/app/src/main/cpp/main.cpp line 212 allocate size 4
2024-05-29 10:11:47.489 29299-29334 Native com.example.memory.monitor I file D:/samples/Demos/AndroidSamples/Memroy/app/src/main/cpp/main.cpp line 211 allocate size 20
2024-05-29 10:11:47.489 29299-29334 Native com.example.memory.monitor I file D:/samples/Demos/AndroidSamples/Memroy/app/src/main/cpp/main.cpp line 210 allocate size 4
2024-05-29 10:11:47.489 29299-29334 Native com.example.memory.monitor I total memory allocate is 52
2024-05-29 10:11:47.489 29299-29334 Native com.example.memory.monitor I Memory Snapshot End 从以上的快照可以看到当前内存的申请情况通过这些信息可以排查某个文件的某一行申请的内存是否应该释放调由此可以判断是否出现内存泄漏的情况。 在平常的开发中尽可能使用智能指针减少显示通过new申请内存的情况这样也可以避免内存泄漏。
本示例的工程已上传到github链接为示例工程地址