如何在linux下检测内存泄漏

如何在linux下检测内存泄漏,第1张

要想检测内存泄漏,就必须对程序中的内存分配和释放情况进行记录,所能够采取的办法就是重载所有形式的operator new 和 operator delete,截获 new operator 和 delete operator 执行过程中的内存 *** 作信息。下面列出的就是重载形式

void* operator new( size_t nSize, char* pszFileName, int nLineNum )

void* operator new[]( size_t nSize, char* pszFileName, int nLineNum )

void operator delete( void *ptr )

void operator delete[]( void *ptr )

我们为 operator new 定义了一个新的版本,除了必须的 size_t nSize 参数外,还增加了文件名和行号,这里的文件名和行号就是这次 new operator *** 作符被调用时所在的文件名和行号,这个信息将在发现内存泄漏时输出,以帮助用户定位泄漏具体位置。对于 operator delete,因为无法为之定义新的版本,我们直接覆盖了全局的 operator delete 的两个版本。

在重载的 operator new 函数版本中,我们将调用全局的 operator new 的相应的版本并将相应的 size_t 参数传入,而后,我们将全局 operator new 返回的指针值以及该次分配所在的文件名和行号信息记录下来,这里所采用的数据结构是一个 STL 的 map,以指针值为 key 值。当 operator delete 被调用时,如果调用方式正确的话(调用方式不正确的情况将在后面详细描述),我们就能以传入的指针值在 map 中找到相应的数据项并将之删除,而后调用 free 将指针所指向的内存块释放。当程序退出的时候,map 中的剩余的数据项就是我们企图检测的内存泄漏信息--已经在堆上分配但是尚未释放的分配信息。

以上就是内存检测实现的基本原理,现在还有两个基本问题没有解决:

1) 如何取得内存分配代码所在的文件名和行号,并让 new operator 将之传递给我们重载的 operator new。

2) 我们何时创建用于存储内存数据的 map 数据结构,如何管理,何时打印内存泄漏信息。

先解决问题1。首先我们可以利用 C 的预编译宏 __FILE__ 和 __LINE__,这两个宏将在编译时在指定位置展开为该文件的文件名和该行的行号。而后我们需要将缺省的全局 new operator 替换为我们自定义的能够传入文件名和行号的版本,我们在子系统头文件 MemRecord.h 中定义:

#define DEBUG_NEW new(__FILE__, __LINE__ )

而后在所有需要使用内存检测的客户程序的所有的 cpp 文件的开头加入

#include "MemRecord.h"

#define new DEBUG_NEW

就可以将客户源文件中的对于全局缺省的 new operator 的调用替换为 new (__FILE__,__LINE__) 调用,而该形式的new operator将调用我们的operator new (size_t nSize, char* pszFileName, int nLineNum),其中 nSize 是由 new operator 计算并传入的,而 new 调用点的文件名和行号是由我们自定义版本的 new operator 传入的。我们建议在所有用户自己的源代码文件中都加入上述宏,如果有的文件中使用内存检测子系统而有的没有,则子系统将可能因无法监控整个系统而输出一些泄漏警告。

再说第二个问题。我们用于管理客户信息的这个 map 必须在客户程序第一次调用 new operator 或者 delete operator 之前被创建,而且在最后一个 new operator 和 delete operator 调用之后进行泄漏信息的打印,也就是说它需要先于客户程序而出生,而在客户程序退出之后进行分析。能够包容客户程序生命周期的确有一人--全局对象(appMemory)。我们可以设计一个类来封装这个 map 以及这对它的插入删除 *** 作,然后构造这个类的一个全局对象(appMemory),在全局对象(appMemory)的构造函数中创建并初始化这个数据结构,而在其析构函数中对数据结构中剩余数据进行分析和输出。Operator new 中将调用这个全局对象(appMemory)的 insert 接口将指针、文件名、行号、内存块大小等信息以指针值为 key 记录到 map 中,在 operator delete 中调用 erase 接口将对应指针值的 map 中的数据项删除,注意不要忘了对 map 的访问需要进行互斥同步,因为同一时间可能会有多个线程进行堆上的内存 *** 作。

好啦,内存检测的基本功能已经具备了。但是不要忘了,我们为了检测内存泄漏,在全局的 operator new 增加了一层间接性,同时为了保证对数据结构的安全访问增加了互斥,这些都会降低程序运行的效率。因此我们需要让用户能够方便的 enable 和 disable 这个内存检测功能,毕竟内存泄漏的检测应该在程序的调试和测试阶段完成。我们可以使用条件编译的特性,在用户被检测文件中使用如下宏定义:

#include "MemRecord.h"

#if defined( MEM_DEBUG )

#define new DEBUG_NEW

#endif

当用户需要使用内存检测时,可以使用如下命令对被检测文件进行编译

g++ -c -DMEM_DEBUG xxxxxx.cpp

就可以 enable 内存检测功能,而用户程序正式发布时,可以去掉 -DMEM_DEBUG 编译开关来 disable 内存检测功能,消除内存检测带来的效率影响。

测试过程:insert采用malloc分配内存,释放时采用free释放内存,并erase *** 作子。使用top窗口查看程序使用的内存。

测试结果:free后, *** 作系统为程序分配的内存并不会free掉,而被Hold了。当再次调用insert入map,会使用未free掉的内存。

如果后面insert同样采用malloc分配内存, *** 作系统会优先使用先去malloc且free掉的内存,若不够再malloc新内存。

这个我们期末考试考过。

inotify只能监控单层目录变化,不能监控子目录中的变化情况。

如果需要监控子目录,需要在调用inotify_add_watch(int fd, char *dir, int mask):int建立监控时,递归建立子目录的监控,伪代码如下

void addwatch(int fd, char *dir, int mask)

{

wd = inotify_add_watch(fd, dir, mask)

向目录集合加入(wd, dir)

for (dir下所有的子目录subdir)

addwatch(fd, subdir, mask)

}

这样就可以得到一个目录集合,其中每一个wd对应一个子目录。

当你调用read获取信息时,可以得到一个下面的结构体

struct inotify_event

{

int wd /* Watch descriptor. */

uint32_t mask /* Watch mask. */

uint32_t cookie /* Cookie to synchronize two events. */

uint32_t len/* Length (including NULs) of name. */

char name __flexarr /* Name. */

}

其中,通过event->wd和刚才记录的目录集合可以知道变动的具体子目录。

event->name为具体的文件名称。

event->name是一个char name[0]形式的桩指针,具体的name占据的长度可以由event->len得出

我的监控部分代码如下:

enum {EVENT_SIZE = sizeof(struct inotify_event)}

enum {BUF_SIZE = (EVENT_SIZE + 16) <<10}

void watch_mon(int fd)

{

int i, length

void *buf

struct inotify_event *event

buf = malloc(BUF_SIZE)

while ((length = read(fd, buf, BUF_SIZE)) >= 0)

{

i = 0

while (i <length)

{

event = buf + i

if (event->len)

具体处理函数(event)

i += EVENT_SIZE + event->len

}

}

close(fd)

exit(1)

}

在你的具体处理函数中,通过wd辨识子目录,通过name辨识文件

这是利用C++STLmap写的一个范例,可以监视当前目录下(含子目录)的变化,创建,删除过程(新建立的目录不能监视,只能通过监视到创建新目录的事件后重新初始化监视表)

新版1.1.0,可以监视创建的子目录,方法是,当do_action探测到新目录创建的动作时,调用inotify_add_watch追加新的监视

/*

Copyright (C) 2010-2011 LIU An (SuperHacker@china.com.cn)

This program is free software: you can redistribute it and/or modify

it under the terms of the GNU General Public License as published by

the Free Software Foundation, either version 3 of the License, or

(at your option) any later version.

This program is distributed in the hope that it will be useful,

but WITHOUT ANY WARRANTYwithout even the implied warranty of

MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the

GNU General Public License for more details.

You should have received a copy of the GNU General Public License

along with this program. If not, see <http://www.gnu.org/licenses/>.

*/

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <unistd.h>

#include <sys/types.h>

#include <sys/inotify.h>

#include <errno.h>

#include <dirent.h>

#include <map>

#include <string>

using namespace std

void addwatch(int, char*, int)

static int filter_action(uint32_t mask)

int watch_init(int mask, char *root)

void addwatch(int fd, char *dir, int mask)

static void do_action(int fd, struct inotify_event *event)

void watch_mon(int fd)

static void send_mess(char *name, char *act, int ewd)

void append_dir(int fd, struct inotify_event *event, int mask)

map<int, string>dirset

enum{MASK = IN_MODIFY | IN_CREATE | IN_DELETE}

int main(int argc, char **argv)

{

int fd

if (argc != 2)

{

fprintf(stderr, "Usage: %s dir\n", argv[0])

exit(1)

}

fd = watch_init(MASK, argv[1])

watch_mon(fd)

return 0

}

int watch_init(int mask, char *root)

{

int i, fd

if ((fd = inotify_init()) <0)

perror("inotify_init")

addwatch(fd, root, mask)

return fd

}

void addwatch(int fd, char *dir, int mask)

{

int wd

char subdir[512]

DIR *odir

struct dirent *dent

if ((odir = opendir(dir)) == NULL)

{

perror("fail to open root dir")

exit(1)

}

wd = inotify_add_watch(fd, dir, mask)

dirset.insert(make_pair(wd, string(dir)))

errno = 0

while ((dent = readdir(odir)) != NULL)

{

if (strcmp(dent->d_name, ".") == 0

|| strcmp(dent->d_name, "..") == 0)

continue

if (dent->d_type == DT_DIR)

{

sprintf(subdir, "%s/%s", dir, dent->d_name)

addwatch(fd, subdir, mask)

}

}

if (errno != 0)

{

perror("fail to read dir")

exit(1)

}

closedir (odir)

}

enum {EVENT_SIZE = sizeof(struct inotify_event)}

enum {BUF_SIZE = (EVENT_SIZE + 16) <<10}

void watch_mon(int fd)

{

int i, length

void *buf

struct inotify_event *event

buf = malloc(BUF_SIZE)

while ((length = read(fd, buf, BUF_SIZE)) >= 0)

{

i = 0

while (i <length)

{

event = (struct inotify_event*)(buf + i)

if (event->len)

do_action(fd, event)

i += EVENT_SIZE + event->len

}

}

close(fd)

exit(1)

}

static char action[][10] =

{

"modified",

"accessed",

"created",

"removed"

}

enum{NEWDIR = IN_CREATE | IN_ISDIR}

static void do_action(int fd, struct inotify_event *event)

{

int ia, i

if ((ia = filter_action(event->mask)) <0)

return

if ((event->mask &NEWDIR) == NEWDIR)

append_dir(fd, event, MASK)

send_mess(event->name, action[ia], event->wd)

}

void append_dir(int fd, struct inotify_event *event, int mask)

{

char ndir[512]

int wd

sprintf(ndir, "%s/%s", dirset.find(event->wd)->second.c_str(),

event->name)

wd = inotify_add_watch(fd, ndir, mask)

dirset.insert(make_pair(wd, string(ndir)))

}

static int filter_action(uint32_t mask)

{

if (mask &IN_MODIFY)

return 0

if (mask &IN_ACCESS)

return 1

if (mask &IN_CREATE)

return 2

if (mask &IN_DELETE)

return 3

return -1

}

static void send_mess(char *name, char *act, int ewd)

{

char format[] = "%s was %s.\n"

char file[512]

sprintf(file, "%s/%s", dirset.find(ewd)->second.c_str(), name)

printf(format, file, act)

}

参考资料是我们作业的提交,没有考虑递归创建子目录监控的问题。


欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/yw/7628635.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-04-07
下一篇2023-04-07

发表评论

登录后才能评论

评论列表(0条)

    保存