This is the mail archive of the
libstdc++@gcc.gnu.org
mailing list for the libstdc++ project.
Memory leaks in standard containers
- From: Stephen M. Webb <stephen dot webb at bregmasoft dot com>
- To: libstdc++ at gcc dot gnu dot org
- Date: Mon, 18 Nov 2002 12:11:18 -0500
- Subject: Memory leaks in standard containers
- Organization: Bregmasoft
- Reply-to: stephen dot webb at bregmasoft dot com
I discovered through inspection that all of the standard containers
leak memory under certain circumstances. I don't know if this is
considered a QoI issue, but I would consider it a serious bug.
The problem is that memory allocated through the container's allocator
is never deallocated in the event that a container object's copy
constructor throws an exception. The leak might occur in copy
constructors and assignment operators.
As far as I can see, the correct solution is to wrap pointers to
allocated memory in standard containers with some sort of auto_ptr-like
smart pointer. I will work up a treatment, but in the mean time I've
got a test case to illustrate the problem. Note that this is a
quick-and-dirty testcase implementation, I can get something better in
a few days.
Diffs follow and two source files should be attached.
Index: Makefile.am
===================================================================
RCS file: /cvs/gcc/gcc/libstdc++-v3/testsuite/Makefile.am,v
retrieving revision 1.11
diff -c -3 -p -r1.11 Makefile.am
*** Makefile.am 1 Sep 2002 18:09:18 -0000 1.11
--- Makefile.am 18 Nov 2002 15:59:50 -0000
*************** INCLUDES = \
*** 51,57 ****
## Build support library.
noinst_LIBRARIES = libv3test.a
! libv3test_a_SOURCES = testsuite_hooks.cc
## Build support utilities.
## Only build this as native, as need to find startup files and libc
to link.
--- 51,57 ----
## Build support library.
noinst_LIBRARIES = libv3test.a
! libv3test_a_SOURCES = testsuite_hooks.cc testsuite_allocator.cc
## Build support utilities.
## Only build this as native, as need to find startup files and libc
to link.
Index: testsuite_hooks.cc
===================================================================
RCS file: /cvs/gcc/gcc/libstdc++-v3/testsuite/testsuite_hooks.cc,v
retrieving revision 1.4
diff -c -3 -p -r1.4 testsuite_hooks.cc
*** testsuite_hooks.cc 24 Oct 2002 23:27:27 -0000 1.4
--- testsuite_hooks.cc 18 Nov 2002 15:59:51 -0000
*************** gnu_counting_struct::size_type gnu_coun
*** 80,83 ****
int gnu_copy_tracker::itsCopyCount = 0;
int gnu_copy_tracker::itsDtorCount = 0;
!
--- 80,84 ----
int gnu_copy_tracker::itsCopyCount = 0;
int gnu_copy_tracker::itsDtorCount = 0;
! unsigned int gnu_copy_constructor::count_ = 0;
! unsigned int gnu_copy_constructor::throw_on_ = 0;
Index: testsuite_hooks.h
===================================================================
RCS file: /cvs/gcc/gcc/libstdc++-v3/testsuite/testsuite_hooks.h,v
retrieving revision 1.10
diff -c -3 -p -r1.10 testsuite_hooks.h
*** testsuite_hooks.h 2 Aug 2002 16:04:16 -0000 1.10
--- testsuite_hooks.h 18 Nov 2002 15:59:52 -0000
*************** class gnu_copy_tracker
*** 152,157 ****
--- 152,192 ----
static int itsDtorCount;
};
+ // A class for counting copy constructors and possible throwing an
+ // exception on a desired count.
+ class gnu_copy_constructor
+ {
+ public:
+ static unsigned int
+ count()
+ { return count_; }
+
+ static void
+ mark_call()
+ {
+ count_++;
+ if (count_ == throw_on_)
+ {
+ __throw_exception_again "copy constructor exception";
+ }
+ }
+
+ static void
+ reset()
+ {
+ count_ = 0;
+ throw_on_ = 0;
+ }
+
+ static void
+ throw_on(unsigned int count)
+ { throw_on_ = count; }
+
+ private:
+ static unsigned int count_;
+ static unsigned int throw_on_;
+ };
+
struct gnu_char
{
unsigned long c;
Index: 23_containers/vector_ctor.cc
===================================================================
RCS file:
/cvs/gcc/gcc/libstdc++-v3/testsuite/23_containers/vector_ctor.cc,v
retrieving revision 1.8
diff -c -3 -p -r1.8 vector_ctor.cc
*** 23_containers/vector_ctor.cc 1 May 2002 02:17:35 -0000 1.8
--- 23_containers/vector_ctor.cc 18 Nov 2002 15:59:53 -0000
***************
*** 22,27 ****
--- 22,28 ----
#include <vector>
#include <string>
+ #include <testsuite_allocator.h>
#include <testsuite_hooks.h>
template<typename T>
*************** void test04()
*** 94,105 ****
--- 95,159 ----
#endif
}
+ class SmwTest
+ {
+ public:
+ SmwTest() { }
+ SmwTest(const SmwTest& rhs)
+ { gnu_copy_constructor::mark_call(); }
+ ~SmwTest() { }
+ };
+
+ void
+ test_default_ctor_exception_safety()
+ {
+ typedef std::vector<SmwTest, gnu_new_allocator<SmwTest> > SmwVector;
+ gnu_copy_constructor::reset();
+ gnu_copy_constructor::throw_on(3);
+
+ try
+ {
+ SmwVector v01(7);
+ VERIFY(("no exception thrown", false));
+ }
+ catch (...)
+ {
+ }
+
+ VERIFY(("memory leak detected:",
+ gnu_allocator_tracker::allocationTotal() ==
gnu_allocator_tracker::deallocationTotal()));
+ }
+
+ void
+ test_copy_ctor_exception_safety()
+ {
+ typedef std::vector<SmwTest, gnu_new_allocator<SmwTest> > SmwVector;
+ SmwVector v01(7);
+ gnu_copy_constructor::reset();
+ gnu_copy_constructor::throw_on(3);
+
+ try
+ {
+ SmwVector v02(v01);
+ VERIFY(("no exception thrown", false));
+ }
+ catch (...)
+ {
+ }
+
+ VERIFY(("memory leak detected:",
+ gnu_allocator_tracker::allocationTotal() ==
gnu_allocator_tracker::deallocationTotal()));
+ }
+
int main()
{
test01();
test02();
test03();
test04();
+ test_default_ctor_exception_safety();
+ test_copy_ctor_exception_safety();
return 0;
}
--
Stephen M. Webb
#ifndef SMW_ALLOCATOR_H_
#define SMW_ALLOCATOR_H_
#include <cstddef>
#include <limits>
class gnu_allocator_tracker {
public:
typedef std::size_t size_type;
static void*
allocate(size_type blocksize)
{
allocationTotal_ += blocksize;
return ::operator new(blocksize);
}
static void
construct()
{ constructCount_++; }
static void
destroy()
{ destructCount_++; }
static void
deallocate(void* p, size_type blocksize)
{
::operator delete(p);
deallocationTotal_ += blocksize;
}
static size_type
allocationTotal()
{ return allocationTotal_; }
static size_type
deallocationTotal()
{ return deallocationTotal_; }
static int
constructCount()
{ return constructCount_; }
static int
destructCount()
{ return destructCount_; }
static void
resetCounts()
{
allocationTotal_ = 0;
deallocationTotal_ = 0;
constructCount_ = 0;
destructCount_ = 0;
}
private:
static size_type allocationTotal_;
static size_type deallocationTotal_;
static int constructCount_;
static int destructCount_;
};
// A simple basic allocator that just forwards to the gnu_allocator_tracker
// to fulfill memory requests. This class is templated on the target object
// type, but gnu_allocator_tracker isn't.
template <class T>
class gnu_new_allocator
{
public:
typedef T value_type;
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
template <class U> struct rebind { typedef gnu_new_allocator<U> other; };
pointer
address(reference value) const
{ return &value; }
const_pointer
address(const_reference value) const
{ return &value; }
gnu_new_allocator() throw()
{ }
gnu_new_allocator(const gnu_new_allocator&) throw()
{ }
template <class U>
gnu_new_allocator(const gnu_new_allocator<U>&) throw()
{ }
~gnu_new_allocator() throw()
{ }
size_type
max_size() const throw()
{ return std::numeric_limits<std::size_t>::max() / sizeof(T); }
pointer
allocate(size_type num, const void* = 0)
{ return static_cast<pointer>(gnu_allocator_tracker::allocate(num * sizeof(T))); }
void
construct(pointer p, const T& value)
{
new (p) T(value);
gnu_allocator_tracker::construct();
}
void
destroy(pointer p)
{
p->~T();
gnu_allocator_tracker::destroy();
}
void
deallocate(pointer p, size_type num)
{ gnu_allocator_tracker::deallocate(p, num * sizeof(T)); }
};
template <class T1, class T2>
bool
operator==(const gnu_new_allocator<T1>&, const gnu_new_allocator<T2>&) throw()
{ return true; }
template <class T1, class T2>
bool
operator!=(const gnu_new_allocator<T1>&, const gnu_new_allocator<T2>&) throw()
{ return false; }
#endif // SMW_ALLOCATOR_H_
// vi:set ts=2 sw=2 ai et:
// Utility subroutines for the C++ library testsuite.
//
// Copyright (C) 2002 Free Software Foundation, Inc.
//
// This file is part of the GNU ISO C++ Library. This library is free
// software; you can redistribute it and/or modify it under the
// terms of the GNU General Public License as published by the
// Free Software Foundation; either version 2, or (at your option)
// any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this library; see the file COPYING. If not, write to the Free
// Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307,
// USA.
//
// As a special exception, you may use this file as part of a free software
// library without restriction. Specifically, if other files instantiate
// templates or use macros or inline functions from this file, or you compile
// this file and link it with other files to produce an executable, this
// file does not by itself cause the resulting executable to be covered by
// the GNU General Public License. This exception does not however
// invalidate any other reasons why the executable file might be covered by
// the GNU General Public License.
#include <testsuite_allocator.h>
gnu_allocator_tracker::size_type gnu_allocator_tracker::allocationTotal_ = 0;
gnu_allocator_tracker::size_type gnu_allocator_tracker::deallocationTotal_ = 0;
int gnu_allocator_tracker::constructCount_ = 0;
int gnu_allocator_tracker::destructCount_ = 0;