Outline
Purpose
Provide a test allocator that reports the call stack for leaks.
Classes
Description
This component provides an instrumented allocator, balst::StackTraceTestAllocator
, that implements the bdlma::ManagedAllocator
protocol. An object of this type records the call stack for each allocation performed, and can report, either using the reportBlocksInUse
method or implicitly at destruction, the call stack associated with every allocated block that has not (yet) been freed. It is optionally supplied a bslma::Allocator
at construction that it uses to allocate memory.
,------------------------------.
`------------------------------'
| ctor/dtor
| numAllocations
| allocationLimit
| numBlocksInUse
| reportBlocksInUse
| setFailureHandler
V
,----------------------.
( bdlma::ManagedAllocator)
`----------------------'
| release
V
,----------------.
`----------------'
allocate
deallocate
Definition balst_stacktracetestallocator.h:550
Definition bslma_allocator.h:457
Note that allocation using a balst::StackTraceTestAllocator
is deliberately incompatible with the default global new
, malloc
, delete
, and free
. Using delete
or free
to free memory supplied by this allocator will corrupt the dynamic memory manager and also cause a memory leak (and will be reported by purify as freeing mismatched memory, freeing unallocated memory, or as a memory leak). Using deallocate
to free memory supplied by global new
or malloc
will immediately cause an error to be reported to the associated ostream
and the abort handler (which can be configured to be a no-op) called.
Overhead Efficiency
There is some overhead to using this allocator. It's is slower than bslma::NewDeleteAllocator
and uses more memory. It is, however, comparable in speed and memory usage to bslma::TestAllocator
. The stack trace stored for each allocation is stack pointers only, which are compact and quick to obtain. Actual resolving of the stack pointer to subroutine names and, on some platforms, source file names and line numbers, is expensive but doesn't happen during allocation or deallocation and is put off until a memory leak report is being generated.
Note that the overhead increases and efficiency decreases as the numRecordedFrames
argument to the constructor is increased.
Failure Handler
An object of type balst::StackTraceTestAllocator
always has a failure handler associated with it. This is a configurable bdef::Functton
object that will be called if any error condition is detected, after the error condition is reported. By default, it is set to balst::StackTraceTestAllocator::failAbort
which calls abort
, but it may be set by setFailureHandler
to another routine. If the client does not want a core dump to occur, it is recommended they do:
stackTraceTestAllocator.setFailureHandler(
The stack trace test allocator is prepared for the failure handler to return, throw (provided the client will catch the exception) or longjmp without undefined behavior.
If a memory is found to be outstanding during destruction, that is considered a memory leak and a report is written. After the report, the failure handler is called, and if the failure handler returns, the leaked memory is then released. This means that if the failure handler throws or longjmps in this case, the leaked memory will not be freed, and there will be no way to release it afterward since the allocator will no longer exist.
Usage
This section illustrates intended use of this component.
Example 1: Basic Usage
In this example, we will define a class ShipsCrew
that does something, but leaks memory, and then we will demonstrate the use of the stack trace test allocator to locate the leak.
First, we define ShipsCrew
, a class that will read the names of a ship's crew from a file at construction, and make the results available through accessors:
struct ShipsCrew {
private:
struct CharStrLess {
bool operator()(const char *a, const char *b) const
{
return bsl::strcmp(a, b) < 0;
}
};
const char *d_captain;
const char *d_firstMate;
const char *d_cook;
NameSet d_sailors;
private:
void free(const char **str);
public:
explicit
ShipsCrew(const char *crewFileName,
~ShipsCrew();
const char *captain();
const char *cook();
const char *firstMate();
const char *firstSailor();
const char *nextSailor(const char *currentSailor);
};
Definition bslstl_string.h:1281
Definition bslstl_set.h:657
Then, we implement the private manipulators of the class:
PRIVATE MANIPULATORS
{
if (!d_sailors.count(name.
c_str())) {
d_sailors.insert(copy(name));
}
}
{
char *result = (
char *) d_allocator_p->allocate(str.
length() + 1);
bsl::strcpy(result, str.
c_str());
return result;
}
void ShipsCrew::free(const char **str)
{
assert(str);
if (*str) {
d_allocator_p->deallocate(const_cast<char *>(*str));
*str = 0;
}
}
{
free(&d_captain);
d_captain = copy(name);
}
{
free(&d_cook);
d_cook = copy(name);
}
{
free(&d_firstMate);
d_firstMate = copy(name);
}
size_type length() const BSLS_KEYWORD_NOEXCEPT
Definition bslstl_string.h:6601
const CHAR_TYPE * c_str() const BSLS_KEYWORD_NOEXCEPT
Definition bslstl_string.h:6705
Next, we implement the creators:
ShipsCrew::ShipsCrew(const char *crewFileName,
: d_captain(0)
, d_firstMate(0)
, d_cook(0)
, d_sailors(
bslma::Default::allocator(basicAllocator))
, d_allocator_p(
bslma::Default::allocator(basicAllocator))
{
bsl::ifstream input(crewFileName);
bsl::size_t colon = line.find(':');
setCaptain(name);
}
setFirstMate(name);
}
setCook(name);
}
addSailor(name);
}
else {
cerr << "Unrecognized field '" << field << "' in line '" <<
line << "'\n";
}
}
else {
cerr << "Garbled line '" << line << "'\n";
}
}
}
ShipsCrew::~ShipsCrew()
{
free(&d_captain);
free(&d_firstMate);
const NameSet::iterator
end = d_sailors.end();
for (NameSet::iterator it = d_sailors.begin();
end != it; ++it) {
d_allocator_p->deallocate(const_cast<char *>(*it));
}
}
basic_string substr(size_type position=0, size_type numChars=npos) const
Definition bslstl_string.h:7263
static const size_type npos
Definition bslstl_string.h:1676
#define BSLS_ASSERT(X)
Definition bsls_assert.h:1804
std::basic_istream< CHAR_TYPE, CHAR_TRAITS > & getline(std::basic_istream< CHAR_TYPE, CHAR_TRAITS > &is, basic_string< CHAR_TYPE, CHAR_TRAITS, ALLOCATOR > &str, CHAR_TYPE delim)
T::iterator end(T &container)
Definition bslstl_iterator.h:1523
Definition balxml_encoderoptions.h:68
static int lowerCaseCmp(const char *lhsString, const char *rhsString)
Then, we implement the public accessors:
ACCESSORS
const char *ShipsCrew::captain()
{
return d_captain ? d_captain : "";
}
const char *ShipsCrew::cook()
{
return d_cook ? d_cook : "";
}
const char *ShipsCrew::firstMate()
{
return d_firstMate ? d_firstMate : "";
}
const char *ShipsCrew::firstSailor()
{
NameSet::iterator it = d_sailors.begin();
return d_sailors.end() == it ? 0 : *it;
}
const char *ShipsCrew::nextSailor(const char *currentSailor)
{
assert(currentSailor);
NameSet::iterator it = d_sailors.find(currentSailor);
if (d_sailors.end() != it) {
++it;
}
return d_sailors.end() == it ? 0 : *it;
}
Next, in main
, we create our file ./shipscrew.txt
describing the crew of the ship. Note that the order of crew members is not important:
{
bsl::ofstream outFile("shipscrew.txt");
outFile << "sailor:Mitch Sandler\n"
<< "sailor:Ben Lampert\n"
<< "cook:Bob Jones\n"
<< "captain:Steve Miller\n"
<< "sailor:Daniel Smith\n"
<< "first mate:Sally Chandler\n"
<< "sailor:Joe Owens\n";
}
Then, we set up a test case to test our ShipsCrew
class. We do not use the stack trace test allocator yet, we just use a bslma::TestAllocator
to get memory usage statistics and determine whether any leakage occurred.
{
{
ShipsCrew crew("shipscrew.txt", &ta);
assert(tam.isInUseUp());
assert(tam.isTotalUp());
if (verbose) {
cout << "Captain: " << crew.captain() <<
"\nFirst Mate: " << crew.firstMate() <<
"\nCook: " << crew.cook() << endl;
for (const char *sailor = crew.firstSailor(); sailor;
sailor = crew.nextSailor(sailor)) {
cout << "Sailor: " << sailor << endl;
}
}
}
int bytesLeaked = ta.numBytesInUse();
if (bytesLeaked > 0) {
cout << bytesLeaked << " bytes of memory were leaked!\n";
}
}
Definition bslma_testallocatormonitor.h:471
Definition bslma_testallocator.h:384
The program generates the following output in non-verbose mode, telling us that one segment of 10 bytes was leaked:
10 bytes of memory were leaked!
MEMORY_LEAK from Bslma Test Allocator:
Number of blocks in use = 1
Number of bytes in use = 10
Next, we would like to use stack trace test allocator to tell us WHERE the memory leak is, but we have a problem: our test case not only uses bslma::TestAllocator
, but it calls the numBytesInUse
accessor, which is not available from stack trace test allocator. We are also using bslma::TestAllocatorMonitor
, which will only work with bslma::TestAllocator
. So if we were to just substitute the stack trace test allocator for the bslma test allocator, it would break our test case in several ways. To instrument our test with a minimal change to the code, we create a stack test test allocator and feed that allocator to the constructor to bslma test allocator. The rest of our example will now work without modification. (Note that it is important to call ta.setNoAbort(true)
when we use this method, otherwise the bslma test allocator will bomb out before the destructor for stta
is able to generate its report).
{
ta.setNoAbort(true);
{
ShipsCrew crew("shipscrew.txt", &ta);
assert(tam.isInUseUp());
assert(tam.isTotalUp());
if (verbose) {
cout << "Captain: " << crew.captain() <<
"\nFirst Mate: " << crew.firstMate() <<
"\nCook: " << crew.cook() << endl;
for (const char *sailor = crew.firstSailor(); sailor;
sailor = crew.nextSailor(sailor)) {
cout << "Sailor: " << sailor << endl;
}
}
}
int bytesLeaked = ta.numBytesInUse();
if (bytesLeaked > 0) {
cout << bytesLeaked << " bytes of memory were leaked!\n";
}
}
void setFailureHandler(const FailureHandler &func)
void setName(const char *name)
Now, this generates the following report:
10 bytes of memory were leaked!
MEMORY_LEAK from Bslma Test Allocator:
Number of blocks in use = 1
Number of bytes in use = 10
===========================================================================
Error: memory leaked:
1 block(s) in allocator 'stta' in use.
Block(s) allocated from 1 trace(s).
---------------------------------------------------------------------------
Allocation trace 1, 1 block(s) in use.
Stack trace at allocation time:
(0): BloombergLP::
balst::StackTraceTestAllocator::allocate(int)+0x17d at 0x
805e741 in balst_stacktracetestallocator.t.dbg_exc_mt
(1): BloombergLP::
bslma::TestAllocator::allocate(int)+0x12c at 0x8077398 in
balst_stacktracetestallocator.t.dbg_exc_mt
(2): ShipsCrew::copy(
bsl::basic_string<char,
std::char_traits<char>,
bsl::a
llocator<char> > const&)+0x31 at 0x804c3db in balst_stacktracetestallocator
.t.dbg_exc_mt
(3): ShipsCrew::setCook(
bsl::basic_string<char,
std::char_traits<char>,
bsl
::allocator<char> > const&)+0x2d at 0x804c4c1 in balst_stacktracetestalloca
tor.t.dbg_exc_mt
(4): ShipsCrew::ShipsCrew(char const*, BloombergLP::
bslma::Allocator*)+0x23
4 at 0x804c738 in balst_stacktracetestallocator.t.dbg_exc_mt
(5): main+0x53c at 0x804d55e in balst_stacktracetestallocator.t.dbg_exc_mt
(6): __libc_start_main+0xdc at 0x182e9c in /lib/libc.so.6
(7): --unknown-- at 0x804c1d1 in balst_stacktracetestallocator.t.dbg_exc_mt
Definition balst_objectfileformat.h:161
Definition bdlb_printmethods.h:283
Definition bdldfp_decimal.h:5188
Finally, Inspection shows that frame (3) of the stack trace from the allocation of the leaked segment was from ShipsCrew::setCook
. Inspection of the code shows that we neglected to free d_cook
in the destructor and we can now easily fix our leak.