How to properly replace global new & delete operators
Asked Answered
C

2

69

(There were at least 4-5 topics with a similar topic on SO. I read each of them and I don't feel they really help me with this specific issue).

I'm using Visual Studio .NET 2003 on Windows 7.

I have my own overloads of new/delete that point to my own custom calls to malloc() and free() for diagnostics. My new/delete overloads are in a header file which I've included in a few files.

The problem is, the code base is pretty much spaghetti and there is no easy way to make sure these overloads get used by everything. There are includes to third party libraries that are black-box. We also use STL everywhere.

In my tests I've found that STL is still mixing calls to my own new/delete and the standard MSVC new/delete calls.

It doesn't seem realistic to include my header file in thousands of other files, that would just take far too long. Can anyone offer some tips on how to properly and effectively overload new/delete globally so everything uses my custom memory manager?

Coughlin answered 18/11, 2011 at 16:58 Comment(1)
If you define the operators globally in a pre-compiled header that should cover the most ground. Alternately you can use the CRT heap functions if this is for detecting memory leaks.Penultimate
B
95

That's not how this works. You replace the two operators, and this is done at link time. All you need to do is write a single TU that defines these operators and link it into the mix. Nobody else ever needs to know about this:

// optional_ops.cpp

void * operator new(std::size_t n) throw(std::bad_alloc)
{
  //...
}
void operator delete(void * p) throw()
{
  //...
}

In principle, there's no need for any header files to declare these functions (operator new, operator delete), since the declarations of those two functions are already hardcoded into the language, if you will. However, the names std, std::bad_alloc and std::size_t are not predeclared, so you will probably want to include <new> or some other header to provide those names.

In C++11 and beyond, you can alternatively use decltype(sizeof(0)) to get the size of the first parameter in a way that doesn't require any kind of library. C++11 also has a simpler exception model without dynamic exception specifications (which were finally removed from the language entirely in C++17).

void * operator new(decltype(sizeof(0)) n) noexcept(false)
{
  //...
}
Baggott answered 18/11, 2011 at 17:5 Comment(23)
Won't the linker complain about duplicate definitions? I think the ODR applies here. Not to mention we have 120 DLLs that we build, and I'd have to link it in each one of those DLL projects. I guess this is still better than the alternatives.Coughlin
@RobertDailey: Nope, special case, covered by the standard, weak references, etc etc. I actually reported a bug in GCC concerning this the other day, so with the latest version this should even work with -fwhole-program and -flto and whatnot (see here and here.)`Baggott
Could you explain what you mean by "covered by the standard" and "weak references"? Thanks!!Coughlin
@RobertDailey: The standard covers that user definitions replace the functions, so there's no violation of ODR. The compiler implements this by making the standard functions "weak" references that are overridden by the linker if another symbol of the same name is found.Baggott
Thanks for the information. What section in the standard can this be found? I want to do some more reading on this & related rules. Thanks so much!!Coughlin
@KerrekSB, don't forget the other 3 versions: new[], delete[], nothrowReinhardt
@MSN, more like, power-3: throw/nothrow, scalar/array, new/delete. Yep. Plenty of code :-) But at least you don't break anything if you always keep each new/delete pair together.Baggott
Does this same technique work for replacing malloc() and free() on Windows?Coughlin
@RobertDailey: malloc() lives on different level. Usually malloc() is actually used directly by the C++ allocation functions. Since malloc() is part of your C library, you have to convince the linker to prefer a different implementation. On Linux that's trivial with LD_PRELOAD, and it's a piece of cake there to try out any of the competing allocators (tcmalloc is pretty dramatic).Baggott
Yeah, on Windows you can't offer up replacement new/delete functions for the whole program without linking them into every DLL. Or at least forcing the dlls to import those functions, but I'm not sure if that works. I think it's unfortunate that DLLs work this way. On other platforms when shared libraries are linked at program start-up time they generally obey the same rules as statically linked translation units, so if your program replaces new/delete the shared libraries will get those replacements too.Pazia
Do they need to be defined in a cpp? Can't they be inlined?Apparition
@AbhishekJain: Since they affect the entire program, no, they cannot generally be inlined. Linkers need to provide some special magic ("weak linkage") to allow this replacement mechanism.Baggott
You might also want to override the "no throw" versions of new, which have a second parameter const std::nothrow_t& (part of the C++ standard)Inharmonious
Is there some documentation on all the new/delete's that could/should be implemented? throws, nothrows, arrays etc edit: this perhaps; en.cppreference.com/w/cpp/memory/new/operator_newNita
@SoylentGraham: The most direct way may be to read section [support.dynamic] in the Standard. That gives a comprehensive description.Baggott
When I first tried to replace new/delete, the process crashed before it could break at _start()...or so the debug session said. The process ran without issue after I added #include <new>, and removed //#include <iostream> //using namespace std; Note: this is on an arm v5 target.Trost
@stephen: The names std and std::size_t aren't pre-declared, so it is up to you to make them declared in the TU where you define the replacement functions; typically via #include <new>. But users don't need to include that header.Baggott
@KerrekSB Exactly. What's interesting to me is the program compiled and immediate went off into the weeds prior to my modification. It's as if std or <iostream> allowed for some inappropriate code.Trost
Does this solution work across shared libraries under Linux?Lateen
@AndreasPasternak: Do you mean shared libraries loaded at runtime via dlopen? I'm not sure, but I think so, at least as long as the shared library doesn't itself replace the operators. The built-in definitions typically are implemented with "weak linkage", and I imagine that that continues to work during dynamic loading. But best to try it out yourself!Baggott
@KerrekSB: No, I just link everything together at compile time. Unfortunately, it does not work across shared libraries if I do not use LD_PRELOAD. See my question here: stackoverflow.com/questions/51735795/…Lateen
@AndreasPasternak: So you link everything in one step, and one of your libraries replaces the operators, and the replaced functions end up not getting called? Or do they only get called by code in the library that replaces them? I'm not sure, I'd look for "weak linkage" in your compiler's documentation.Baggott
@KerrekSB: I build one shared library which contains the overloaded new/delete operators. Then I build another simple shared library. I then link everything together to an executable. new/deletes in the executable cpp are properly handled but new/deletes in the shared library are not. If I preload my small shared library which contains new/delete with LD_PRELOAD it works tough and new/delete in the other shared library is properly handled. I will check "weak linkage", thank youLateen
D
45

Also add these lines:

void *operator new[](std::size_t s) throw(std::bad_alloc)
{
    // TODO: implement
    return NULL;
}
void operator delete[](void *p) throw()
{
    // TODO: implement
}
Disturbed answered 18/11, 2011 at 18:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.