Home > Back-end >  C - Possible memory leak from a handled exception?
C - Possible memory leak from a handled exception?

Time:04-26

I'm working on a C application (an OpenSSL assignment for university), and I'm running it through valgrind, as one does. I've noticed some rather strange output when the program fails due to invalid input:

==1739== HEAP SUMMARY:
==1739==     in use at exit: 588 bytes in 6 blocks
==1739==   total heap usage: 52 allocs, 46 frees, 99,206 bytes allocated
==1739== 
==1739== 44 bytes in 1 blocks are possibly lost in loss record 3 of 6
==1739==    at 0x483BE63: operator new(unsigned long) (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==1739==    by 0x4C20378: std::string::_Rep::_S_create(unsigned long, unsigned long, std::allocator<char> const&) (in /usr/lib/x86_64-linux-gnu/libstdc  .so.6.0.28)
==1739==    by 0x4C03187: std::logic_error::logic_error(char const*) (in /usr/lib/x86_64-linux-gnu/libstdc  .so.6.0.28)
==1739==    by 0x4C0325C: std::invalid_argument::invalid_argument(char const*) (in /usr/lib/x86_64-linux-gnu/libstdc  .so.6.0.28)
==1739==    by 0x10FB6D: lab2::cryptoEngine::CBCCryptoEngine::encrypt() (CBCCryptoEngine.cpp:39)
==1739==    by 0x10E355: main (main.cpp:135)
==1739== 
==1739== 144 bytes in 1 blocks are possibly lost in loss record 4 of 6
==1739==    at 0x483B7F3: malloc (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so)
==1739==    by 0x4BDB1F3: __cxa_allocate_exception (in /usr/lib/x86_64-linux-gnu/libstdc  .so.6.0.28)
==1739==    by 0x10FB5B: lab2::cryptoEngine::CBCCryptoEngine::encrypt() (CBCCryptoEngine.cpp:39)
==1739==    by 0x10E355: main (main.cpp:135)
==1739== 
==1739== LEAK SUMMARY:
==1739==    definitely lost: 0 bytes in 0 blocks
==1739==    indirectly lost: 0 bytes in 0 blocks
==1739==      possibly lost: 188 bytes in 2 blocks
==1739==    still reachable: 400 bytes in 4 blocks
==1739==                       of which reachable via heuristic:
==1739==                         stdstring          : 44 bytes in 1 blocks
==1739==         suppressed: 0 bytes in 0 blocks
==1739== Reachable blocks (those to which a pointer was found) are not shown.
==1739== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==1739== 
==1739== For lists of detected and suppressed errors, rerun with: -s

The code causing it is just a regular exception that is thrown when an input file is invalid. The exception is caught properly like this:

try {
    engine.encrypt(bad_argument); // Placeholder. The exception type stands, though...
}
catch (const std::invalid_argument &e) {
    std::cerr << "Exception while encrypting file: " << e.what() << std::endl;
    delete (engine);
    exit(EXIT_FAILURE);
}

I'm not 100% sure what it means, and if it is even a problem, since the memory will be reclaimed by the OS anyway. But I've never seen this kind of thing before, and wanted to check.

So, my question is, what is it caused by? Should I fix it? If so, how?

CodePudding user response:

std::logic_error's constructor allocated memory for the "explanatory string". This is what() returns to you, in the exception handler (std::invalid_argument inherits from std::logic_error).

Observe that the backtrace shows the constructor overload that takes a const char * for a parameter. It would've been acceptable if that literal const char * got stashed away, and handed back to you from what(). However there are various immaterial reasons why the constructor is coded to make a copy of the "explanatory string" so that its contents are wholly owned by the constructed std::logic_error.

Your exception handler quietly called exit() and the process terminated. At which point valgrind notified you that the aforementioned allocated memory wasn't destroyed. This is true, that memory wasn't deallocated.

Had your exception handler's scope ended naturally (without calling exit()), std::logic_error's destructor would've deleted the allocated memory, and everyone would've lived happily ever after.

TLDR: this is only a technical memory leak. It is technically true only because the rug was pulled from under the process, by calling exit.

Note that valgrind said "possibly" instead of "definitely". There's no doubt that you have a memory leak when valgrind "definitely" claims one. If it's just "possibly", then there may or may not be a real memory leak.

CodePudding user response:

exit loses more or less everything not recursively owned by objects of static storage duration. Of course this includes everything recursively owned by things on the stack, but also the active exception object (its memory is allocated in an unspecified way). In this particular case, you lose a buffer owned by a std::string owned by the active exception object.

If you care about such things, don't use exit in the middle of things. Rethrow the exception, catch it in main, let the handler finish, and then call exit (or just fall out of main naturally). You probably shouldn't, as this is not a real leak (i.e. there is no memory allocated and lost by the program while it is still running).

  • Related