3

My team is working on an application, where we need to track memory usage, and provide statistics on how much memory areas of the program utilize (e.g. N bytes used by uncontrolled STL containers). I need to find a way to identify memory allocated in 3rd party libs from STL containers.

The application makes use of 3rd party libraries that either we don't have access to the source code, or have been directed not to make changes to the source. Some of these libraries use standard STL containers, like std::vector<int>, but they have used (or appear to use, in the case of the closed libs) the default std::allocator. We are targeting Windows, with future work planned for Mac and Linux platforms, using C++17 as much as possible.

I've overridden the malloc and free functions; overridden new, new[], delete and delete[] operators; and created an STLAllocator class derived from std::allocator that is used as the _Alloc template parameter for our use of STL containers. For the libraries that provide hooks to replace the memory allocators, I have done so. When the STL containers in the remaining 3rd partly libs use the default std::allocator, I can see their new and delete calls come through the new and delete overrides, but these appear no different to tracking than a call to new or delete made from main.

I've read many great descriptions of how to declare and use your own std::allocator class, been reminded of the template parameter equality issue when providing different allocators, and made aware of an upcoming solution using std::experimental::pmr::polymorphic_allocator, but I haven't found a definitive answer to my question. Is there a way to supplant the default std::allocator for 3rd party libs that don't provide a hook to override the default std::allocator used by STL containers?

For anyone interested, here is the link that describes the template parameter equality issue; it's also a good overview of std::allocator in general: https://blog.feabhas.com/2019/03/thanks-for-the-memory-allocator/

Paul
  • 387
  • 1
  • 6
  • 11
  • 1
    You can modify all `new` expressions in the main application to use a custom overload of `operator new` and distinguish them from library code that way if that is an acceptable solution. – walnut Apr 10 '20 at 21:54
  • Unfortunately, this is more of a general-purpose tracking, and is not supposed to impose overloading new and delete for each class. – Paul Apr 10 '20 at 22:01
  • @walnut, Ah, sorry, I misunderstood what you meant. Yeah, as mentioned above, I've already overloaded `new`, `new[]`, `delete` and `delete[]`. The issue with this solution is that `std::alllocator` calls `new`, which comes through the overloaded `new`, and there's no way to distinguish them for tagging. – Paul Apr 10 '20 at 22:05
  • @walnut Yes, I've already done both. e.g. `operator new(std::size_t)` and `operator new(std::size_t, const char* libID, const char* file, uint32_t line)`. Yes, I could extend that to include another tag. My initial problem though is that I need to distinguish the STL container allocations coming from the libraries. So, this could separate the main application from the other libraries, but not separate out what libraries are making the allocations. – Paul Apr 10 '20 at 22:23
  • 1
    ok, the last part answers my original question. In that case I don't think there is any reliable solution maybe aside from inspecting the call stack in `operator new` to decide the origin of the call. – walnut Apr 10 '20 at 22:25
  • No worries. I appreciate your determination in making sure we weren't miscommunicating. I have considered looking through something like Google's Breakpad library to figure out a way to get the stackframes, hash them into a value, and use that. But, that's probably overkill for what we're doing. – Paul Apr 10 '20 at 22:28

1 Answers1

3

Is there a way to supplant the default std::allocator for 3rd party libs that don't provide a hook to override the default std::allocator used by STL containers?

Not in general; especially for things that you don't have source code for.

Consider (for example) a call to std::allocator<int>::allocate. Chances are, it's marked as inline, which means that the body of the function has been embedded in the object code that you're linking. Providing your own copy of that function at link time (or in a separate dylib) will have no effect.

Providing your own global operator new is probably the best you can do.

Marshall Clow
  • 15,972
  • 2
  • 29
  • 45
  • Yeah, based on what I've found so far, I was afraid that was the answer; but I figured I would ask in case there was something I was missing. – Paul Apr 10 '20 at 21:56
  • At least in the case of VS17, they don't appear to mark it as inline: ``` _NODISCARD _DECLSPEC_ALLOCATOR _Ty * allocate(_CRT_GUARDOVERFLOW const size_t _Count) { // allocate array of _Count elements return (static_cast<_Ty *>(_Allocate<_New_alignof<_Ty>>(_Get_size_of_n(_Count)))); } ``` – Paul Apr 10 '20 at 21:57
  • 2
    @Paul Functions defined in a class body are `inline` by default and it also doesn't really matter, because `std::allocator` is a class template which results in essentially the same issue as with `inline` if the `std::allocator` specialization is not explicitly specialized. – walnut Apr 10 '20 at 22:00
  • @walnut As I understand it, it is up to the compiler whether or not to make the optimization to inline it. So while yes, it's a possibility, it's not a given. – Paul Apr 10 '20 at 22:03
  • 1
    @Paul I am talking about the `inline` specifier. Whether or not the function is inlined is always up to the compiler and not related to `inline`, but if a function is `inline` or a template specialization, then the object code for the function is guaranteed to be present in the library. In any case, the issue pointed out by @MarshallCrow is that the relevant functions *may* be inlined and so there is nothing you can do to change their behavior. `operator new` and `malloc` are special, because they are specifically allowed to be replaced by the C++ and POSIX(?) standards respectively. – walnut Apr 10 '20 at 22:07
  • @walnut So you're saying the `inline` specifier is always implicitly declared for methods where the function is defined inside the class body? – Paul Apr 10 '20 at 22:13
  • 1
    @Paul Yes, see second sentence on https://en.cppreference.com/w/cpp/language/inline. – walnut Apr 10 '20 at 22:14
  • @walnut Thank you for the pointer. I also just found the question asked and answered here for anyone else following: https://stackoverflow.com/a/11527505/524597 – Paul Apr 10 '20 at 22:16