the Compartmented Robust Posix C++ Unit Test system

Chapter 9. Heap Management

Finding memory leaks can be tricky, and testing behaviour under out of memory situations even trickier still. With crpcut both are easy, since crpcut provides simple access to query and limit the heap.

[Note]Note
For these to work, you must link your test program with -lcrpcut instead of -lcrpcut_basic.

The simplest way to assert that your code does not leak heap objects is to use ASSERT_SCOPE_HEAP_LEAK_FREE or VERIFY_SCOPE_HEAP_LEAK_FREE. With it, you check that any heap object allocated inside the code block is also deallocated when code block ends.

[Tip]Tip
Use the command line flag -b / --backtrace-heap to display the full stack backtrace for allocation of each object.

A simple example. The test program:

     
     #include <crpcut.hpp>
     #include <string>
     
     TEST(heap_in_balance)
     {
       std::string before("a");
       ASSERT_SCOPE_HEAP_LEAK_FREE
       {
         std::string after("b");
       }
     }
     
     TEST(heap_imbalance)
     {
       std::string before("a");
       ASSERT_SCOPE_HEAP_LEAK_FREE
       {                           // This is not a memory leak, but it is reported
         std::string after("b");   // as such because the object allocated here is
         std::swap(before, after); // not released at the end of the block
       }
     }
     
     TEST(heap_leak)
     {
       char *p[5];
       ASSERT_SCOPE_HEAP_LEAK_FREE
       {
         int i;
         for (i = 0; i < 5; ++i)
           {
             p[i] = new char[i+1];
           }
         while (--i > 0)
           {
             delete[] p[i];
           }
       }
     }
     int main(int argc, char *argv[])
     {
       return crpcut::run(argc, argv);
     }

    

reports two failed test:


     FAILED!: heap_imbalance
     phase="running"  --------------------------------------------------------------
     samples/assert_scope_leak_free.cpp:43
     ASSERT_SCOPE_LEAK_FREE
     1 object
     
     26 bytes at 0x62ba20 allocated with new
     Allocated at:
     /var/tmp/build/lib64/libcrpcut.so.1(operator new(unsigned long)+0x1d) [0x7fca98f49984]
     /usr/lib/gcc/x86_64-pc-linux-gnu/5.3.0/libstdc++.so.6(std::basic_string<char, std::char_traits<char>, std::allocator<char> >::_Rep::_S_create(unsigned long, unsigned long, std::allocator<char> const&)+0x59) [0x384b4d8f29]
     -------------------------------------------------------------------------------
     ===============================================================================
     FAILED!: heap_leak
     phase="running"  --------------------------------------------------------------
     samples/assert_scope_leak_free.cpp:53
     ASSERT_SCOPE_LEAK_FREE
     1 object
     
     1 byte at 0x62b860 allocated with new[]
     Allocated at:
     /var/tmp/build/lib64/libcrpcut.so.1(operator new[](unsigned long)+0x1d) [0x7fca98f499e9]
     ../test/assert_scope_leak_free(heap_leak::test()+0x8f) [0x4088cf]
     -------------------------------------------------------------------------------
     ===============================================================================
     3 test cases selected
     
                    Sum   Critical   Non-critical
     PASSED   :       1          1              0
     FAILED   :       2          2              0

    

When more detailed control of the heap is needed, the function size_t crpcut::heap::allocated_bytes() reports how much memory is allocated on the heap. The number returned is the sum of bytes allocated, sans any overhead, i.e. it is the sum of the size arguments to all calls to malloc(), calloc(), realloc(), operator new and operator new[].

[Tip]Tip
Use a fixture that compares allocated bytes at destruction time with allocated bytes at construction time. If they aren't equal, the test has leaked, even if the exact objects at beginning and end aren't identical.

The heap can also be limited with the function size_t crpcut::heap::set_limit(size_t bytes).

The parameter bytes is the maximum number of bytes that is allowed to allocate from the heap. The return value is the previous limit. There is a special value crpcut::heap::system, which removes any artificial limitations.

Here's a function and helper class that casts between types by means of interpreting the stream representation.


     #include <sstream>
     #include <ostream>
     #include <istream>
     #include <string>
     
     class cast_type
     {
     private:
       cast_type(const std::string str) : s(str) {}
       cast_type& operator=(const cast_type&);
     public:
       cast_type(const cast_type &other) : s(other.s) { }
       ~cast_type() {  }
       template <typename T>
       operator T() const
       {
         std::istringstream is(s);
         T rv = T();
         is >> rv;
         return rv;
       }
     private:
       std::string s;
     
       template <typename T>
       friend cast_type stream_cast(const T&);
     };
     
     template <typename T>
     inline cast_type stream_cast(const T& t)
     {
       std::ostringstream os;
       os << t;
       return cast_type(os.str());
     }

Below is a small test program to verify its correctness. It includes a memory leak check and a low memory situation:


     #include "stream-cast.hpp"
     #include <crpcut.hpp>
     
     class leak_detect
     {
     protected:
       leak_detect() : pre(crpcut::heap::allocated_bytes()) {}
       ~leak_detect()
       {
         size_t post = crpcut::heap::allocated_bytes();
         if (post != pre)
           {
             FAIL << "Memory leak detected\n"
                  << pre << " Bytes allocated at construction time\n"
                  << post << " Bytes allocated at destruction time";
           }
       }
     private:
       size_t pre;
     };
     
     TEST(simple_string_to_int, leak_detect)
     {
       const char s[] = "123";
       int n = stream_cast(s);
       ASSERT_EQ(n, 123);
     }
     
     TEST(constrained_string_to_int, leak_detect)
     {
       crpcut::heap::set_limit(crpcut::heap::allocated_bytes() + 10);
       const char s[] = "1234567";
       int n = stream_cast(s);
       ASSERT_EQ(1234567, n);
     }
     
     int main(int argc, char *argv[])
     {
       return crpcut::run(argc, argv);
     }

Running the test yields:


     FAILED!: constrained_string_to_int
     phase="running"  --------------------------------------------------------------
     samples/heap-check.cpp:60
     ASSERT_EQ(1234567, n)
       where n = 0
     -------------------------------------------------------------------------------
     ===============================================================================
     2 test cases selected
     
                    Sum   Critical   Non-critical
     PASSED   :       1          1              0
     FAILED   :       1          1              0

No memory leaks, that's the good news, but that it silently provides the wrong answer under low-memory situations, that is very bad. Can you see why?