2015-02-24 59 views
1

我有一个非常大的项目。我试图监视分配和释放的内存。这是我试过的示例程序。但是,我看到它只是打印新的函数名称,我理解。问题是如何打印函数名称,调用者的行号。如何在重载的新操作符中打印调用者函数名称?

main.cpp

#include <QtCore/QCoreApplication> 
#include <cstdlib> 
#include <stdio.h> 
#include <fstream> 

#include <memOperation.h> 
#include <DumpMemory.h> 

#define BUFFER (4) 

class MemPlay; 

#define LOG_STRING()\ 
{\ 
    std::ofstream dumpfile; \ 
    dumpfile.open("/export/home/joshis1/DBG_REC.log"); \ 
    dumpfile<<"FUNC = "<<__FUNCTION__<<"LINE = "<<__LINE__<<std::endl; \ 
    dumpfile.close(); \ 
} 

void* operator new(std::size_t sz) 
{ 
    void *mem = std::malloc(sz + BUFFER); 
    memset(mem+sz,'PACE',4); 
    LOG_STRING(); 
    return mem; 
} 

void operator delete(void* ptr) 
{ 
    std::free(ptr); 
} 


int main(int argc, char *argv[]) 
{ 
    QCoreApplication a(argc, argv); 

    MemPlay *pMemPlay1 = new MemPlay(); 
    pMemPlay1->MyMemPlay(); 


    return a.exec(); 
} 

memOperation.h

#include "QDebug" 

class MemPlay 
{ 
public: 

    void MyMemPlay() 
    { 
     qDebug()<<"My Mem Play"; 

     char *t = new char[10] ; 

     strcpy(t,"SHREYASJOSHI_SAYS_HELLO_WORLD_AND_CORRUPTS_MEMORY"); 

    } 

    void FreeMemPlay(void *t) 
    { 
     delete t; 
    } 

}; 

这是错误的结果 -

FUNC = operator newLINE = 25 
+0

可移植的,你不能。如果您使用的是GNU,请使用[backtrace](http://linux.die.net/man/3/backtrace_symbols)。 – 2015-02-24 11:55:07

+2

您可以使用内存分析器(例如[valgrind](http://valgrind.org/docs/manual/manual.html))来查看内存分配/重新分配 – 2015-02-24 11:58:18

+0

重载类特定的'operator new',而不是全局的: https://www.relisoft.com/book/tech/9new.html – 2015-02-24 12:05:04

回答

0

对于Windows的Microsoft C++编译器,你可以用函数名,文件名,行号和使用CaptureStackBackTrace呼叫功能的地址和回溯微软debug help library

#include <cstdio> 
#include <Windows.h> 
#include "dbghelp.h" 

using namespace std; 

#define TRACE_MAX_STACK_FRAMES 1024 
#define TRACE_MAX_FUNCTION_NAME_LENGTH 1024 

int printStackTrace() { 
    void *stack[TRACE_MAX_STACK_FRAMES]; 
    WORD numberOfFrames = CaptureStackBackTrace(0, TRACE_MAX_STACK_FRAMES, stack, NULL); 
    SYMBOL_INFO *symbol = (SYMBOL_INFO *)malloc(sizeof(SYMBOL_INFO)+(TRACE_MAX_FUNCTION_NAME_LENGTH - 1) * sizeof(TCHAR)); 
    symbol->MaxNameLen = TRACE_MAX_FUNCTION_NAME_LENGTH; 
    symbol->SizeOfStruct = sizeof(SYMBOL_INFO); 
    DWORD displacement; 
    IMAGEHLP_LINE64 *line = (IMAGEHLP_LINE64 *)malloc(sizeof(IMAGEHLP_LINE64)); 
    line->SizeOfStruct = sizeof(IMAGEHLP_LINE64); 
    for (int i = 0; i < numberOfFrames; i++) { 
    DWORD64 address = (DWORD64)(stack[i]); 
    SymFromAddr(process, address, NULL, symbol); 
    if (SymGetLineFromAddr64(process, address, &displacement, line)) { 
     printf("\tat %s in %s: line: %lu: address: 0x%0X\n", symbol->Name, line->FileName, line->LineNumber, symbol->Address); 
    } else { 
     printf("\tat %s, address 0x%0X.\n", symbol->Name, symbol->Address); 
    } 
    } 
    free(symbol); 
    return 0; 
} 

void function2() { 
    int a = 0; 
    int b = 0; 
    throw new exception; 
} 

void function1() { 
    int a = 0; 
    function2(); 
} 

void function0() { 
    function1(); 
} 

int main(int argc, char* argv[]) { 
    HANDLE process = GetCurrentProcess(); 
    SymInitialize(process, NULL, TRUE); 
    function0(); 
    return 0; 
} 

此示例代码基于问题How can you use CaptureStackBackTrace to capture the exception stack, not the calling stack?

0

在我的机器上,一次修改后的代码导致26503条记录(new调用)与QtAplication,2042条记录 - 不使用Qt

这些操作的目视检查是无用的。使用valgrind你可以确定有多少内存泄漏,什么导致了泄漏,什么导致了内存损坏,从未分配的内存中读取垃圾等。

另外,你可以重载操作符new/delete你选择的几个类es)知道什么时候创建/移除了这些对象。这些信息可以存储在文件中,然后进行检查。

修改后的代码:

main.cpp

//#include <QtCore/QCoreApplication> 
#include <cstdlib> 
#include <stdio.h> 
#include <fstream> 
#include <iostream> 

#include <memOperation.h> 

void* operator new(std::size_t sz) 
{ 
    void *mem = std::malloc(sz + BUFFER); 
    memset(mem+sz,'0',BUFFER); 
    LOG_STRING(); 
    return mem; 
} 

void operator delete(void* ptr) 
{ 
    std::free(ptr); 
} 

int main(int argc, char *argv[]) 
{ 
    //QCoreApplication a(argc, argv); 

    MemPlay *pMemPlay1 = new MemPlay(); 
    pMemPlay1->MyMemPlay(); 
    std::cout<<"Memplay at "<<pMemPlay1<<" size "<<sizeof(*pMemPlay1)<<" trailing "; 
    puts((char*)(pMemPlay1)+sizeof(*pMemPlay1)); 

    return 0; 
    //return a.exec(); 
} 

memOperation.h

#ifndef MEMOPERATION_H 
#define MEMOPERATION_H 

#include "QDebug" 

#define BUFFER (4) 

#define LOG_STRING()\ 
{\ 
    std::ofstream dumpfile; \ 
    dumpfile.open("DBG_REC.log", std::ofstream::out | std::ofstream::app); \ 
    dumpfile<<"FUNC = "<<__FUNCTION__<<"LINE = "<<__LINE__<<std::endl; \ 
    dumpfile.close(); \ 
} 

class MemPlay 
{ 
public: 

    void MyMemPlay() 
    { 
     std::cout<<"My Mem Play char * "; 

     char *t = new char[10]; 

     //strcpy(t,"SHREYASJOSHI_SAYS_HELLO_WORLD_AND_CORRUPTS_MEMORY"); 
     std::cout<<" trailing "; 
     //puts(t); 
     puts(t+10); 

     FreeMemPlay(t); 
    } 

    void FreeMemPlay(char *t) 
    { 
     delete t; 
    } 

    void * operator new (std::size_t sz) 
    { 
     void *mem = std::malloc(sz + BUFFER); 
     memset(mem+sz,'1',BUFFER); 
     LOG_STRING(); 
     return mem; 
    } 
    void operator delete (void * mem) 
    { 
     if (mem) 
      std::free(mem); 
    } 

}; 

#endif // MEMOPERATION_H 
1

如果你只关注跟踪new/delete操作,重载全局new操作不仅是不是必要和矫枉过正,但是引入了这么多额外的麻烦,一个甚至不能开始理解。

对于(全球与否)新/删除的恰当超载,这里有一些资源:

还注意到正当理由超载新的/删除运营商:

最干净的解决办法是为你写你自己的new/delete -wrapper宏/功能,并替换的new/delete所有出现在源代码中。例如:

#define NEW(T, ...) traced_new<T>(__FILE__, __LINE__, __FUNCTION__, __VA_ARGS__) 

template <typename T, typename... Args> 
T* traced_new<T>(std::string file, unsigned long line, std::string func, Args&&... args) 
{ 
    // log ... 
    return new T { std::forward<Args>(args)... }; 
} 

如果你想避免更换新/删除的来源,你仍然可以注入了宏跟踪代码new

#include <iostream> 
#include <string> 

struct traced_new_tag_t {}; 
constexpr traced_new_tag_t traced_new_tag; 

void* operator new (std::size_t n, traced_new_tag_t, std::string file, unsigned long line, std::string func) 
{ 
    void* const p = operator new(n); 
    std::cerr << file << ':' << line << ": " << func << " allocates " << n << " bytes at " << p << "\n"; 
    return p; 
} 

void* operator new[] (std::size_t n, traced_new_tag_t, std::string file, unsigned long line, std::string func) 
{ 
    void* const p = operator new[](n); 
    std::cerr << file << ':' << line << ": " << func << " allocates [] " << n << " bytes at " << p << "\n"; 
    return p; 
} 

#define new new(traced_new_tag, __FILE__, __LINE__, __FUNCTION__) 

int main (int, char**) 
{ 
    long long *p0, *p1; 
    std::cout << (p0 = new long long) << '\n'; 
    std::cout << (p1 = new long long [3]) << '\n'; 
    return 0; 
} 

打印:

t.cpp:26: main allocates 8 bytes at 0xbf9070 
0xbf9070 
t.cpp:27: main allocates [] 24 bytes at 0xbf9090 
0xbf9090 

这已经引入了额外的麻烦,特别是如果operator new抛出会发生什么情况。你将如何处理?另外,这个例子还没有完成,因为没有对于非抛出使用的超载/宏使用newnew(std::nothrow))。

感谢Mike西摩指出这一点)也有明显的额外麻烦#define new必须非常仔细作用域只影响你的源代码,以及之后的任何声明的定义。看到他对额外恐怖的评论。

即使采用这种方法,仍然需要包装释放/删除操作,因为delete操作符在表达式语法中无法接收额外的参数。

总而言之,这是一个非常肮脏的黑客,我不会推荐它。

最后,如果您决定实际上重载全局新建/删除,请确保您阅读完好。然后,您可以通过对“主叫信息” /“呼叫者姓名”,像这样的以下建议跟踪调用函数中有:

+0

'#define new'会导致未定义的行为;具体来说,它将破坏任何使用或声明operator new的代码,或者使用placement-new表达式,包括多个标准库头。请不要这样做。 – 2015-02-24 18:02:09