dexterous
dexterous

Reputation: 6526

How to print the caller function name in the overloaded new operator?

I have a very big project. I am trying to monitor the memory allocated and deallocated. Here is the sample program I tried. However, I see that it just prints the function name of new, which I understand. The question is how can I print the function name, line number of the caller.

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;
    }

};

Here was the erroneous result -

FUNC = operator newLINE = 25

Upvotes: 1

Views: 2762

Answers (3)

Prikso NAI
Prikso NAI

Reputation: 2652

If your only concern is tracing new/delete operations, overloading the global new operation is not only not necessary and overkill, but introduces so many extra trouble, one cannot even begin to understand.

For proper overloading of (global or not) new/delete, here are some resources:

Also take note of valid reasons for overloading the new/delete operators:

The cleanest solution would be for you to write your own new/delete-wrapper macros/functions, and replace all occurences of new/delete in the source code. For instance:

#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)... };
}

If you want to avoid having to replace new/deletes in the sources, you could still inject tracing code with a macro for 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;
}

Prints:

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

This already introduces extra hussle, specifically with what happens if operator new throws. How would you handle that? Also, this example in not complete, as there is no overloading/macro for non-throwing uses of new (new(std::nothrow)).

(Thanks to Mike Seymour for pointing this out) there is also the obvious extra trouble that #define new has to be extremely carefully scoped to affect only your source code, well after any declarations definitions. See his comment for extra horror.

Even with this approach, you'd still need to wrap deallocations/deletions, as the delete operator cannot receive extra arguments in expression syntax.

All in all, this is a very dirty hack and I would not recommend it.

Finally, if you decide on actually overloading global new/delete, make sure you read well about it. You can then trace the caller function in there by following advice about "caller info"/"caller name", such as this:

Upvotes: 1

John_West
John_West

Reputation: 2399

On my machine one run of the revised code resulted in 26503 records (new calls) with QtAplication, 2042 records - without using Qt.

Visual inspection of these operations is useless. Using valgrind you could determine how much memory is leaked, what caused the leak, what caused memory corruption, where reading garbage from unallocated memory ocuured, etc.

Also, you can overload operator new/delete of your several chosen class(es) to know, when the objects are created/removed. This information can be stored in the file and then examined.

Revised code:

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

Upvotes: 0

bcmpinc
bcmpinc

Reputation: 3380

For Windows with the Microsoft c++ compiler, you can get a backtrace with function name, file name, line number and address of the calling functions using CaptureStackBackTrace and Microsoft's 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;
}

This sample code is based on the question How can you use CaptureStackBackTrace to capture the exception stack, not the calling stack?.

Upvotes: 0

Related Questions