28

Is it possible to create macros to replace all forms of operator new with overloads that include additional args...say __FILE__ and __LINE__?

The trouble appears to be that operator new can either be coded with or without parentheses, therefore:

  • object-like macros:

    #define new new(__FILE__, __LINE__)
    

    will replace declarations like:

    A* a = new A();
    
  • and function-like macros:

    #define new(A) new (A, __FILE__, __LINE__)
    

    will replace declarations like:

    A* a = new(std::nothrow) A();
    

Unfortunately it's an error to attempt to declare two macros with the same identifier, even if they are of different types, so the following fails:

#define new new(__FILE__, __LINE__)
#define new(A) new (A, __FILE__, __LINE__) // Error: "new" already defined

Since I'm using g++ I was hopeful that employing their syntax of variadic macros would yield success, but unfortunately not. The following:

#define new(...) new(__FILE__, __LINE__, ## __VA_ARGS__)

only matches new(xyx) A(), not new A().

I know that essays have been written about why it is impossible, but I feel like I'm so close that there must be a way. Is there anything obvious that I'm missing?

a3f
  • 8,517
  • 1
  • 41
  • 46
David Citron
  • 43,219
  • 21
  • 62
  • 72
  • Note that it's not clear whether `#define new` is guaranteed to work: http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#369 – Johannes Schaub - litb Sep 02 '09 at 01:07
  • 1
    @JohannesSchaub-litb But what it is clear is that you invoke UB if you also include any standard header after that. – curiousguy Aug 21 '12 at 03:11
  • Do _not_ ever try to `#define` any keyword, or name of standard function, type, object, template, etc., in any header: including a standard header file, after you have done such a `#define`, has undefined behavior. – curiousguy Aug 21 '12 at 03:13

7 Answers7

27

Here is what I use:

In new.cpp

const char* __file__ = "unknown";
size_t __line__ = 0;

void* operator new(size_t size) {
    void *ptr = malloc(size);
    record_alloc(ptr,__file__,__line__);
    __file__ = "unknown";
    __line__ = 0;
    return ptr;
}

void delete(void *ptr)
{
   unrecord_alloc(ptr);
   free(ptr);
}

For compactness, I'm leaving out the other definitions of new and delete. "record_alloc" and "unrecord_alloc" are functions that maintain a linked list of structure containing ptr, line, and file).

in new.hpp

extern const char* __file__;
extern size_t __line__;
#define new (__file__=__FILE__,__line__=__LINE__) && 0 ? NULL : new

For g++, "new" is expanded only once. The key is the "&& 0" which makes it false and causes the real new to be used. For example,

char *str = new char[100];

is expanded by the preprocessor to

char *str = (__file__="somefile.c",__line__=some_number) && 0 ? NULL : new char [100];

Thus file and line number are recorded and your custom new function is called.

This works for any form of new -- as long as there is a corresponding form in new.cpp

thegreendroid
  • 3,239
  • 6
  • 31
  • 40
  • 3
    Couldn't you use the comma operator instead of &&0?NULL: ? Like, `#define new (file=..,line=..),new`, like @dirkgently suggests with his answer? – strager Sep 02 '09 at 01:08
  • 6
    I think using the comma operator will cause problems in code like: `f(new char[100]);` The comma in it would make them two arguments. However, doing `(__file__=__FILE__,__line__=__LINE__,0) ? 0 : new ` would probably be better. avoids the needless `&&` operation, and avoids including `` – Johannes Schaub - litb Sep 02 '09 at 01:31
  • 3
    I like this solution for debug builds. It isn't thread safe, but a bit of thread local storage fixes that. I think that would make it a bit costly for a release build ... but there are never any leaks in release builds anyway – Mark Wilkins Oct 22 '09 at 14:55
  • very very smart If I think again you could do something like: #define new PrepareNew(__FILE__,__LINE__, other ... parameters); new #define delete PrepareDelete( .... ); delete You could implement this to be thread safe without any hassle – INS Nov 06 '09 at 08:46
  • Great solution, this should suffice for most cases. However this macro trick will expand the placement new operator too which is undesirable. Also, the macro will need to be undefined (using #undef) before #include'ing any STL container header like , etc. – thegreendroid Sep 21 '14 at 08:56
  • I'm using this idea but some libraries like boost use the global namespace resolution form `::new` which causes error `'(': illegal token on right side of '::'`. Assuming you actually want to parse such calls (instead of putting `#undef new` before the `#include "lib.h"`) you can use the following trick. Put `extern bool dummy_;` in `new.hpp` and `bool dummy_ = false;` in `new.cpp`, then use `#define new dummy_ ? 0 : (__file__=__FILE__,__line__=__LINE__,0) ? 0 : new`. It leads to the same end result but the compiler finds an acceptable token first. – George Skelton Jun 29 '16 at 13:33
8

You should check out this excellent blog entry by my coworker Calvin. We had a situation recently where we wanted to enable this type of fix in order to associate memory leaks with the line that allocated them in diagnostic/debug builds. It's an interesting trick

https://learn.microsoft.com/en-us/archive/blogs/calvin_hsia/overload-operator-new-to-detect-memory-leaks

lornova
  • 6,667
  • 9
  • 47
  • 74
JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
  • 2
    That technique doesn't actually show the file/line whence the new was called. When __FILE__ and __LINE__ are passed as default function parameters, you always get the file/line at which the function is declared. Even his sample output shows this. – David Citron Mar 06 '09 at 21:43
4

3.7.4 Dynamic storage duration

2 The library provides default definitions for the global allocation and deallocation functions. Some global allocation and deallocation functions are replaceable (18.5.1). A C++ program shall provide at most one definition of a replaceable allocation or deallocation function. Any such function definition replaces the default version provided in the library (17.6.4.6) [...]

17.6.4.6 Replacement functions

  1. A C++ program may provide the definition for any of eight dynamic memory allocation function signatures declared in header (3.7.4, Clause 18):

    • operator new(std::size_t)
    • operator new(std::size_t, const std::nothrow_t&)
    • operator new[](std::size_t)
    • operator new[](std::size_t, const std::nothrow_t&)
    • operator delete(void*)
    • operator delete(void*, const std::nothrow_t&)
    • operator delete[](void*)
    • operator delete[](void*, const std::nothrow_t&)

Hope this clarifies what is a legal overload and what isn't.

This may be of interest to a few here:

#define delete cout <<  "delete called at: " << __LINE__ << " of " << __FILE__  << endl, delete 

using namespace std;

void *operator new(size_t size, ostream& o, char *f, unsigned l) {
    o << "new called at: " << l << " of " << f << endl;
    return ::new char[size];
}

int main() {
    int *a = new(cout, __FILE__, __LINE__) int;
    delete a;
}

Caveat Lector: What I do here is a Bad Thing (TM) to do -- overloading new/delete globally.

CB Bailey
  • 755,051
  • 104
  • 632
  • 656
dirkgently
  • 108,024
  • 16
  • 131
  • 187
  • 2
    Why is overloading new globally a bad thing? Our entire architecture depends on it; it's the only way to force everything, including crappy third party code, to go through our pooled allocators. – Crashworks Sep 02 '09 at 01:01
  • I like your idea about using the comma operator with the #define. It's not exactly what the OP wanted, but it could be a workaround. – strager Sep 02 '09 at 01:06
  • Wouldn't your marco expand recursively, or am I missing something important about the comma? – Chris Lutz Sep 02 '09 at 01:19
  • 3
    @Chris Lutz - Macros never expand recursively, ever. – Mankarse May 20 '11 at 10:09
2

I found the following library "nvwa" very useful for tracking down new/delete memory leaks - have a look at the file "debug_new" for examples, or just use it 'as is'.

James Fisher
  • 292
  • 1
  • 6
  • Wow--that's a great find. I'm not sure if that's using operator overloading for good or for evil, but it definitely appears to work! – David Citron Mar 10 '09 at 22:10
  • Hmmm...the macro replacement is generating bogus output on some 3rd-party code that I can't change that explicitly invokes "::operator new" (the "::operator" is remaining, of course, and is making it bogus). Any ideas? – David Citron Mar 10 '09 at 23:58
1

You don't say what compiler you are using, but at least with GCC, you can override new and log the caller address, then later translate that to file/line information with addr2line (or use the BFD library to do that immediately).

TrayMan
  • 7,180
  • 3
  • 24
  • 33
  • Oops, sorry. You did say that you are using g++. So there you go. – TrayMan Mar 06 '09 at 16:41
  • Unfortunately addr2line requires debugging symbols to work. Our executables are typically compiled with -O2 and without -g, so I don't think this would work. Please correct me if I'm wrong. – David Citron Mar 06 '09 at 19:07
  • You can have -g flag with -O2 flag. Also, unless you specify the -s flag, there's going to be some information, like which function and which source file an address belongs to (if I recall right). – TrayMan Mar 06 '09 at 21:51
  • Unfortunately -O2 collapses (optimizes away) some function calls so the call stack does not match the source. – David Citron Mar 06 '09 at 23:11
  • (been a while, but just in case...) You shouldn't have to have -g on your deployed system. Compile with -g, then 'strip' if you need to for your deployment; run addr2line on your unstripped "offline" version; the addresses will be the same. Works for gdb'ing core files from non -g apps, too. – Scott Jan 23 '17 at 23:33
0

What you could do is to overload the operator new and get the stack trace there (platform specific) and use the stack information to deduce from where new was called.

lothar
  • 19,853
  • 5
  • 45
  • 59
  • This answer would be better if it included a link to http://www.gnu.org/software/hello/manual/libc/Backtraces.html -- however, run-time performance impact is an issue and compile-time optimizations collapse function calls making the backtrace inaccurate. – David Citron Apr 07 '09 at 14:08
-2

No, there's no way.

You could do this in the bad old days of malloc()/free() but not for new.

You can replace the memory allocator by globally overriding the new operator, but you cannot inject the special variables you're talking about.

Jason Cohen
  • 81,399
  • 26
  • 107
  • 114