7

When running the following code, I see the memory consumption of the process grow. Is there a memory leak in my code, is there a memory leak in the std implementation, or is it intended behaviour? It's running on a Windows 10 machine; both Visual Studio and the task manager show approximately 1MB memory growth per minute.

for (int i = 0; i < 10000; i++) {

    std::exception_ptr exptr = nullptr;
    std::string errmsg = "some error";
    try {
        throw std::runtime_error(errmsg.c_str());
    }
    catch (...) {
        exptr = std::current_exception();
    }

    if (exptr) {
        try {
            rethrow_exception(exptr);
        }
        catch (std::exception const& ex) {
            exptr = nullptr;
            std::cout << ex.what() << std::endl;
        }
    }
    std::this_thread::sleep_for(std::chrono::milliseconds(10ms));
}

When directly throwing and logging without delay (without using std::exception_ptr), the memory consumption doesn't grow. std::exception_ptr is advertised as behaving like a smart pointer, so when resetting it (setting it to nullptr), the underlying resource should be destroyed. Therefore, I expect the underlying exception to be freed appropriately.

Jeff Schaller
  • 2,352
  • 5
  • 23
  • 38
Philipp B.
  • 81
  • 4
  • 1
    "the std implementation" - there are multiple std implementations. It's possible, though unlikely, that one of them has a bug. Which one are you using? Microsoft's "STL"? – Nelfeal Nov 17 '22 at 12:44
  • your for loop running for 10,000 times. each time that smart pointer will be constructed and destructed. I will recommend put `std::exception_ptr exptr = nullptr;` before for loop start and check once. – Vishal Nov 17 '22 at 12:50
  • You should be more specific at least what compiler version and what build configuration you use (debug/release, win32/x64, default project parameters or not). Do you run application with debugger from VS or manually? – dewaffled Nov 17 '22 at 12:51
  • It is curious, because your snippet is close to 1:1 the example given in cpp-ref: https://en.cppreference.com/w/cpp/error/exception_ptr – nick Nov 17 '22 at 12:53
  • then there's this (C++11): https://stackoverflow.com/a/16916241/20213170 – nick Nov 17 '22 at 12:54
  • Is the destructor only called when going out of scope ? is that the problem with ````exptr = nullptr;```` ? Sorry for comment flooding... – nick Nov 17 '22 at 12:58
  • It looks like [the MSVC implementation does things differently](https://godbolt.org/z/hdb8696ea), but I don't see why this would cause any memory leak. – Nelfeal Nov 17 '22 at 13:05
  • General question to all who are in discussion. he is assigning `exptr = nullptr;` inside catch. Smart pointer will now point to NULL. But my question, will it delete the exception object which it was pointing to ? Or it will just point to null and that older objects memory will be leaked ? – Vishal Nov 17 '22 at 13:12
  • You could always attack the problem directly: Look at the leaked memory, try to figure out what it is. You can use Time Travel Tracing to work backward to see where it came from. – Raymond Chen Nov 17 '22 at 13:12
  • @Vishal It will not leak the old exception. `std::exception_ptr` acts like a shared_ptr. – Raymond Chen Nov 17 '22 at 13:13
  • @Nelfeal yes i am using Microsoft STL – Philipp B. Nov 17 '22 at 13:45
  • @dewaffled sorry, here is the information: i am on x64, it occurs both in Debug and Release build started from Visual Studio, as well as starting the .exe manually and watching Task Manager. compiler version: 19.29.30146, toolset:Visual Studio 2019 (v142) – Philipp B. Nov 17 '22 at 13:53
  • 1
    @nick i can only assure you that i wrote it down by hand, but this is indeed interesting. regarding your SO link, i saw this but there the situation is different (objects holding circular references to exception_ptrs). – Philipp B. Nov 17 '22 at 13:53
  • 2
    @RaymondChen thanks for the hint with Time Travel Tracing. And yes, acting like a shared_ptr assures that if one does ptr = nullptr; the underlying object's destructor will be called. – Philipp B. Nov 17 '22 at 13:54
  • 1
    @Nelfeal thanks for the link to compiler explorer. My current guess is that the OS might handle the freed virtual memory "lazily", i.e. not immediately consider it as being ready to be allocated again. This leads to growing process memory, however this is not a memory leak, as the OS "promises" to be able to 'manage' the situation... some day? – Philipp B. Nov 17 '22 at 14:03
  • @PhilippB.: Maybe have a go with tracking memory usage with perfmon.exe ? It should be able to differentiate between "in use" and "might free later" memory (i knew the technical names once, but they elude me). About the example: just goes to show that you did a really good job in reducing your problem to the minimal code snippet :) But it gives me some confidence that this not a bug in the VS standard lib. – nick Nov 17 '22 at 14:25
  • 2
    I have found out the following: when i copy the code into a fresh project, the memory consumption doesn't grow. I compared the compiler options, and the following compiler option needs to be activated to avoid growing memory consumption: /EHa – Philipp B. Nov 18 '22 at 08:44
  • 2
    From the msvc compiler documentation i read: [...]The default exception unwinding code doesn't destroy automatic C++ objects outside of try blocks that go out of scope because of an exception. Resource leaks and undefined behavior may result when a C++ exception is thrown.[...] So the growing memory consumption probably was a memory leak. – Philipp B. Nov 18 '22 at 08:51
  • If i _don't_ use rethrow_exception(exptr) there happens to be no mem growth, no matter if the compiler option /EHa is given or not. – Philipp B. Nov 18 '22 at 08:58
  • @PhilippB.: Wow, quite a ride ! Good on you for solving this :) – nick Nov 18 '22 at 09:31
  • And i stand corrected about my assumptions about "not a bug in the VS standard lib". At least they admit it, if you know were to look... ;D – nick Nov 18 '22 at 09:45

1 Answers1

0

I have found out the following: when i copy the code into a fresh project, the memory consumption doesn't grow. I compared the compiler options, and the following compiler option needs to be activated to avoid growing memory consumption: /EHsc

From the msvc compiler documentation i read: [...]The default exception unwinding code doesn't destroy automatic C++ objects outside of try blocks that go out of scope because of an exception. Resource leaks and undefined behavior may result when a C++ exception is thrown.[...] So the growing memory consumption probably was a memory leak.

Philipp B.
  • 81
  • 4