3

I am trying to understand the use of smart pointers in modern C++, and I have written a small, simple program to test in valgrind. The problem is that the example below:

#include <iostream>
#include <memory>

class Base {
    private:
        virtual double meth_1( double x ) const = 0;
        virtual void meth_2( int y ) const = 0;
    protected:
        Base()
        {
            std::cout << "ctor of base for: " << this << std::endl;
        }
    public:
        virtual ~Base()
        {
            std::cout << "dtor of base for: " << this << std::endl;
        }
        double IMeth_1( double x ) const
        {
            return meth_1(x);
        }
        void IMeth_2( int y ) const
        {
            meth_2(y);
        }
};

class Derived_1 : public Base {
    private:
        double meth_1( double x ) const final
        {
            return x + 5.0;
        }
        void meth_2( int y ) const final
        {
            std::cout << (y + 5) << std::endl;
        }
    public:
        Derived_1() : Base()
        {
            std::cout << "ctor of Derived_1: " << this << std::endl;
        }
        ~Derived_1()
        {
            std::cout << "dtor of Derived_1: " << this << std::endl;
        }
};

class Derived_2 : public Base {
    private:
        double meth_1( double x ) const final
        {
            return x + 10.0;
        }
        void meth_2( int y ) const final
        {
            std::cout << (y + 10) << std::endl;
        }
    public:
        Derived_2() : Base()
        {
            std::cout << "ctor of Derived_2: " << this << std::endl;
        }
        ~Derived_2()
        {
            std::cout << "dtor of Derived_2: " << this << std::endl;
        }
};

void Fun( const Base& crBase )
{
    crBase.IMeth_2( 5 );
}

int main( int argc, char* argv[] ) {
    std::unique_ptr< Base > upBase;

    for ( std::size_t idx = 0ul; idx < 2ul; idx++ ) {
        upBase      = std::make_unique< Derived_1 >();
        std::cout   << upBase->IMeth_1( idx )   << std::endl;
        upBase->IMeth_2( idx );
        std::cout   << "----------"             << std::endl;
    }

    for ( std::size_t idx = 0ul; idx < 2ul; idx++ ) {
        upBase      = std::make_unique< Derived_2 >();
        std::cout   << upBase->IMeth_1( idx )   << std::endl;
        upBase->IMeth_2( idx );
        std::cout   << "----------"             << std::endl;
    }

    upBase = std::make_unique< Derived_1 >();
    Fun( *upBase );

    return 0;
}

gives a memory leak when run with valgrind --leak-check=full --show-leak-kinds=all <prog_name>:

==32350== HEAP SUMMARY:
==32350==     in use at exit: 72,704 bytes in 1 blocks
==32350==   total heap usage: 6 allocs, 5 frees, 72,744 bytes allocated
==32350== 
==32350== 72,704 bytes in 1 blocks are still reachable in loss record 1 of 1
==32350==    at 0x4C28C10: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==32350==    by 0x4EBE1EF: pool (eh_alloc.cc:117)
==32350==    by 0x4EBE1EF: __static_initialization_and_destruction_0 (eh_alloc.cc:244)
==32350==    by 0x4EBE1EF: _GLOBAL__sub_I_eh_alloc.cc (eh_alloc.cc:307)
==32350==    by 0x400F279: call_init.part.0 (in /usr/lib/ld-2.22.so)
==32350==    by 0x400F38A: _dl_init (in /usr/lib/ld-2.22.so)
==32350==    by 0x4000DB9: ??? (in /usr/lib/ld-2.22.so)
==32350== 
==32350== LEAK SUMMARY:
==32350==    definitely lost: 0 bytes in 0 blocks
==32350==    indirectly lost: 0 bytes in 0 blocks
==32350==      possibly lost: 0 bytes in 0 blocks
==32350==    still reachable: 72,704 bytes in 1 blocks
==32350==         suppressed: 0 bytes in 0 blocks
==32350== 
==32350== For counts of detected and suppressed errors, rerun with: -v
==32350== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Is that block with 72,704 bytes still in use at exit a false positive, or am I misusing the smart pointer? I assume I am not doing any kind of slicing as the base dtor is called each time the object is deleted.

Sorry if this has been a dumb question, but I could not find any valgrind/false positive/unique_ptr-related topic in SO. Moreover, I am not aware of any additional block that might have been created in unique_ptr similar to that in shared_ptr to keep track of the object.

Edit: Not a duplicate of Still Reachable Leak detected by Valgrind, as in my case I am not using threads (and valgrind is known to give false positives in, especially, OpenMPI environments). Moreover, in the other question, the problem is solved by a proper modification to the code provided. Still, there is not any consensus on what to call a true memory leak --- that is, should the reachable blocks that are still in use at exit be considered as memory leaks, or not.

Community
  • 1
  • 1
Arda Aytekin
  • 1,231
  • 14
  • 24
  • Looks like a valgrind bug, it's detecting its own allocation... Your usage of `std::unique_ptr` looks fine. – Quentin Oct 04 '15 at 12:14
  • 1
    Glancing at the code it looks fine, but you should try to reduce it since there still seems to be quite a bit of unnecessary code here for reproducing the issue. As a general rule when testing small things in valgrind, put your entire test into a function and call it many times. Then if the number of lost blocks increases you know you have a real memory leak. – Dave Oct 04 '15 at 12:14
  • Thank you, both, for your comments. I will try this function approach to see if the blocks in use at exit increase or stay constant, @Dave . – Arda Aytekin Oct 04 '15 at 13:04
  • Are you sure this is a memory leak? Valgrind seem to complain about referencable (reachable, non-leaked) allocated data, which may be allocated by system libraries once and is intended to stay for the whole application lifecycle. – Basilevs Oct 04 '15 at 14:57
  • Possible duplicate of [Still Reachable Leak detected by Valgrind](http://stackoverflow.com/questions/3840582/still-reachable-leak-detected-by-valgrind) – Basilevs Oct 04 '15 at 14:59
  • @Basilevs, I have seen that post. But there is not any consensus in that topic, either, on what should be defined as a memory leak. My situation was different in two aspects: (1) the reachable blocks in my code for this simple example were occupying way too much space compared to the example discussed in that topic, (2) you can get 0 blocks at exit for many small examples in valgrind. That's why I thought there might have been something wrong with my coding style. – Arda Aytekin Oct 05 '15 at 11:07

1 Answers1

6

This is not a valgrind bug. It is a libstdc++ specific feature, that was introduced in http://gcc.gnu.org/viewcvs/gcc?view=revision&revision=219988.

If you look at the code you'll see that class pool from libstdc++-v3/libsupc++/eh_alloc.cc doesn't have a destructor, because it an emergency memory pool that is intended to stay during the whole runtime of an application.

Even a minimal program shows the problem:

 ~ % echo "int main () {}" | g++ -x c++ -
 ~ % valgrind --leak-check=full --show-leak-kinds=all ./a.out
==502== Memcheck, a memory error detector
==502== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==502== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==502== Command: ./a.out
==502== 
==502== 
==502== HEAP SUMMARY:
==502==     in use at exit: 72,704 bytes in 1 blocks
==502==   total heap usage: 1 allocs, 0 frees, 72,704 bytes allocated
==502== 
==502== 72,704 bytes in 1 blocks are still reachable in loss record 1 of 1
==502==    at 0x402CC6F: malloc (vg_replace_malloc.c:299)
==502==    by 0x40F420F: _GLOBAL__sub_I_eh_alloc.cc (in /usr/lib64/gcc/x86_64-pc-linux-gnu/5.2.1/libstdc++.so.6.0.21)
==502==    by 0x4010AA4: call_init.part.0 (dl-init.c:72)
==502==    by 0x4010D44: call_init (dl-init.c:30)
==502==    by 0x4010D44: _dl_init (dl-init.c:120)
==502==    by 0x4000C79: ??? (in /lib64/ld-2.22.90.so)
octoploid
  • 625
  • 3
  • 7
  • 5
    If this is true, that would still make it a valgrind bug, I think. valgrind is supposed to have a suppression list so that well-known false positives -- allocations from the standard libraries that do not really indicate any memory leak in the application being tested -- aren't warned about. Based on your answer it seems like the suppression list is missing an entry. –  Oct 04 '15 at 15:15
  • I will ask the author of that code tomorrow if the missing destructor is really intended. – octoploid Oct 04 '15 at 15:37
  • [It's already been pointed out in the PR leading to the change you linked to.](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64535) And the short answer: it's tricky, there are corner cases to consider. –  Oct 04 '15 at 15:49
  • There is already an open valgrind bug for this problem: https://bugs.kde.org/show_bug.cgi?id=345307 – octoploid Oct 05 '15 at 08:06