C++ Memory Management


Published on

This talk is about the most error prone part of C++ - The memory management part.

Published in: Technology, Education
  • was a Good article !! Thanx !!
    Are you sure you want to  Yes  No
    Your message goes here
    Are you sure you want to  Yes  No
    Your message goes here
No Downloads
Total views
On SlideShare
From Embeds
Number of Embeds
Embeds 0
No embeds

No notes for slide

C++ Memory Management

  1. 1. C++ Memory Management Anil Bapat
  2. 2. Agenda <ul><li>Why pointers are evil </li></ul><ul><li>new/delete operators </li></ul><ul><li>Overloading new/delete </li></ul><ul><li>Memory pools </li></ul><ul><li>Smart Pointers </li></ul><ul><ul><li>Scoped pointers </li></ul></ul><ul><ul><li>Reference counting </li></ul></ul><ul><ul><li>Copy on write </li></ul></ul>
  3. 3. Common Pointer related pitfalls <ul><li>Bitwise copying of objects could lead to dangling pointers + memory leak </li></ul><ul><li>Treating operator= the same as a copy constructor could lead to memory leaks </li></ul><ul><li>Treating arrays polymorphically leads to a crash </li></ul><ul><li>Non virtual destructors in base class could lead to memory leaks </li></ul>
  4. 4. Common Pointer related pitfalls (Contd) <ul><li>Pairing up new with delete [] or new [] with delete leads to undefined behavior (a.k.a late night debugging) </li></ul><ul><li>Pairing up new with free or malloc with delete also leads to undefined behavior </li></ul><ul><li>Incorrect casts (Pick base class pointer from container and cast it to the wrong derived class) leads to crashes </li></ul>
  5. 5. Common Pointer related pitfalls (Contd) <ul><li>Returning a pointer from within a function </li></ul><ul><ul><li>Returning pointer to local data (Crash) </li></ul></ul><ul><ul><li>Returning pointer to a static buffer (crash) </li></ul></ul><ul><li>Un-allocated pointers being used in programs </li></ul><ul><li>Too many exit paths (Including exceptions) from functions, memory allocated within the function needs to be freed in all these paths explicitly </li></ul><ul><li>Overlapping src and dst pointers in memcpy </li></ul>
  6. 6. new/delete operators <ul><li>The memory related operators are – </li></ul><ul><ul><li>new </li></ul></ul><ul><ul><li>delete </li></ul></ul><ul><ul><li>new [] </li></ul></ul><ul><ul><li>delete [] </li></ul></ul><ul><ul><li>placement new </li></ul></ul><ul><ul><li>placement delete </li></ul></ul>
  7. 7. new/delete operators (Contd) <ul><li>operator new allocates memory for the specified type, if memory is unavailable it invokes the installed new_handler function if it is available, else throws a bad_alloc exception </li></ul><ul><li>operator new (std::nothrow) does all of the above, but doesn’t throw an exception, returns NULL instead </li></ul>
  8. 8. new/delete operators (Contd) <ul><li>One could use the set_new_handler function to install a custom new_handler which will be called when new cannot allocate any more memory </li></ul><ul><li>The custom new_handler could free up some excess capacity in the memory pools and make more memory available, or simply abort/throw an exception, or even install ANOTHER new_handler </li></ul>
  9. 9. new/delete operators (Contd) <ul><li>The operator delete frees up the memory allocated by operator new </li></ul><ul><li>The size of the object to be deleted is passed to the operator delete along with the raw pointer </li></ul><ul><li>OK to call delete NULL/0 </li></ul><ul><li>new[]/delete[] do alloc/de-alloc for arrays </li></ul><ul><li>Doing delete [] pBase with pBase pointing to a derived class array leads to a crash </li></ul>
  10. 10. placement new/delete <ul><li>With placement new, no memory allocation done, but the object is constructed and ‘Placed’ at the memory location provided via the void* passed to new </li></ul><ul><li>With placement delete, no memory de-allocation is done </li></ul><ul><li>Used for ‘Memory mapping (mmap)’, ‘Cache Friendly Code’ and such advanced applications </li></ul>
  11. 11. the new_handler <ul><li>When new/new[] is not able to allocate the required memory, it invokes the new_handler </li></ul><ul><li>Users install new_handlers that could somehow ‘Create more memory’ (Usually by reducing the capacity of certain pools) </li></ul><ul><li>Users typically have 2 new_handlers defined – CreateMoreMemoryHdlr() and GiveUpHdlr(), the first time the former is invoked and the former installs the latter handler after it has created more memory. The latter handler simply aborts the program </li></ul>
  12. 12. Overloading new/delete operators <ul><li>new/delete/new[]/delete[] operators can be replaced ‘Globally’, not the placement new/delete </li></ul><ul><li>All the 6 operators could be replaced for a specific class </li></ul><ul><li>If a class replaces new/delete, it can still use the global new[] and delete[], but not the global placement new/delete, or for that matter any other overloaded new/delete form, it will have to define own placement/overloaded new/delete as well in the class itself </li></ul>
  13. 13. Overloading new/delete operators (Contd) <ul><li>To be safe, when you decide to go for class specific replacement, replace all of new/delete/new[] and delete[] together in the class. If you need them, replace the placement new/delete as well </li></ul><ul><li>In the base class methods, compare the size of the object with the base class type, if unequal, direct the call to the global ::operator new/delete method (To account for derived classes missing new/delete operators) </li></ul>
  14. 14. Overloading new/delete operators (Contd) <ul><li>If base class dtor is virtual, then operator delete in the derived class will get the correct size_t value, regardless of which type of pointer is used for the delete (Surprise!) </li></ul><ul><li>However, the behavior is undefined if we do this for deleting a derived class array using base pointer </li></ul>
  15. 15. Why replace new/delete? <ul><li>Better performance </li></ul><ul><li>Debugging </li></ul><ul><li>Collecting statistics </li></ul>
  16. 16. Performance <ul><li>Pre-allocating memory at init time, instead of getting it at runtime from the system, saves us time in the critical path and also reduces ‘Heap fragmentation’ </li></ul><ul><li>Custom new/deletes could do some simplifying assumptions to increase speed </li></ul><ul><ul><li>All objects of same size [simplified search algorithm – no best fit/worst fit fundas required] </li></ul></ul><ul><ul><li>Same thread creating and freeing memory for a specific type (No locks required) </li></ul></ul>
  17. 17. Debugging <ul><li>Custom new/delete methods could be made to insert a ‘Magic number’ few bytes before and after the actual memory allocated and then detect when this gets overwritten </li></ul><ul><li>Custom new/deletes could build a list of allocated memory elements and later check for leaks </li></ul>
  18. 18. Collecting statistics <ul><li>While doing system engineering of a product, one could use custom new/deletes to collect the statistics of the peak/average memory usage on a global or a class wise basis. </li></ul><ul><li>Tests run on the system can help size our memory pools appropriately and to accurately determine our memory requirements and the spread </li></ul>
  19. 19. Memory pools <ul><li>Maximum benefit by defining ‘Class specific memory pools’ as we can make most simplifying assumptions on a class basis (Fixed size and threading) </li></ul><ul><li>Not much benefit in defining global memory pools </li></ul><ul><ul><li>No simplifying assumptions can be made </li></ul></ul><ul><ul><li>The default new/delete are very efficient general purpose memory allocators, and it is very tough to outdo them in speed </li></ul></ul><ul><li>Profile and compare the default new/delete with your own new/delete version before you replace the global new/delete operators. </li></ul>
  20. 20. Class Specific Pools <ul><li>Have a static member of the class as your own memory pool instance (Memory pool ctor takes NumElements and FixedSizeOfElems as parameters) </li></ul><ul><li>Define new/delete operators in your class to alloc/free elements from your own pool </li></ul><ul><li>Alternative – Hierarchy specific pools (Define a memory pool in the base class with size_of_elems = max(sizes of all possible derived classes) and num_elems = (Total expected number of all objects in hierarchy) – Easier, less code, but be careful, check for size. </li></ul>
  21. 21. Memory debuggers are tricked <ul><li>Memory debuggers like valgrind can’t account for what happens within your pools. </li></ul><ul><li>There should be a DEBUG flag setting which can disable all pool dependency in your code so you could use that flag for valgrind debugging </li></ul><ul><li>Valgrind works on ‘Binary instrumentation’ </li></ul><ul><li>Get familiar with valgrind – We identified several vicious memory leaks and corruptions with this tool in multiple products – valgrind.org </li></ul>
  22. 22. Smart Pointers <ul><li>Looks and feels like a pointer, but is smarter </li></ul><ul><li>operators ->, * and ! overloaded to enable sp->, *sp and if (!sp) work as if sp was actually a pointer type </li></ul><ul><li>Behaves just like pointers w.r.t upcasts and downcasts the inheritance hierarchy. This is done using a template member function in the sp class that does all valid conversions of the internal pointer </li></ul>
  23. 23. The ‘Smart’ part <ul><li>Prevent memory leaks – dtor automatically frees the contained pointer </li></ul><ul><li>Prevent dangling pointers – copy ctor and assignment operators implement specific ‘Transfer of ownership’ policies </li></ul><ul><li>Improve performance – By implementing reference counting and copy on write </li></ul>
  24. 24. Smarter, and invisible too! <ul><li>All operations happen ‘Under the hood’ without the users knowing about it (Fully encapsulated) </li></ul><ul><li>Except the place where the sp is defined, everywhere else it is just like using a pointer </li></ul><ul><li>Existing code that uses pointers, could be made to use smart pointers with minimal code change </li></ul>
  25. 25. Smart pointer class template <class T> class SmartPointer { public: SmartPointer(T* pointee = NULL) : _pointee(pointee); SmartPointer(const SmartPointer& rhs); SmartPointer& operator=(const SmartPointer& rhs); ~SmartPointer(); T* operator->() const; T& operator*() const; bool operator!() const; template <class Destination> operator SmartPointer<Destination>() { return SmartPointer<Destination>(_pointee);}; private: T* _pointee; };
  26. 26. Smart pointer usage Base* pBase = new Base; Base* pDerived = new Derived; extern func(SmartPointer<Base> spBase); // Function that takes // base smart pointer as argument SmartPointer<Derived> sp(pDerived); sp->mData = 0; cout << (*sp).mData; if (!sp) cout << “pClass pointer value is NULL”; func(sp) // Can pass smart pointer of derived class type to a // function that accepts smart pointer of base class type
  27. 27. Types of smart pointers <ul><li>Categorized based on the ‘Ownership policy’ they implement </li></ul><ul><ul><li>Scoped pointers (Eg: auto_ptr) </li></ul></ul><ul><ul><li>Shared pointers (The boost shared_ptr) </li></ul></ul><ul><li>Scoped pointers are light weight and don’t allow multiple pointers to same data </li></ul><ul><li>Shared pointers implement reference counting and copy on write and hence are heavy duty beasts </li></ul>
  28. 28. Scoped Pointers <ul><li>dtor destroys the contained pointer (Preventing memory leaks and providing exception safety) </li></ul><ul><li>Either prevent copying (Like boost::scoped_ptr) or transfer ownership to destination (Like std::auto_ptr) or do a deep copy (Like nonstd::auto_ptr) </li></ul><ul><li>std::auto_ptr is dangerous. Passing it to a function, makes the passed value point to NULL </li></ul>
  29. 29. Reference counting <ul><li>Keep a count of the number of references to a particular piece of data and delete the data when this count goes to 0 </li></ul><ul><li>This is the C++ ‘Garbage collection’ mechanism </li></ul><ul><li>Significant performance gains for frequently referenced huge data structures </li></ul>
  30. 30. Reference counting (Contd) <ul><li>Value - A contained class that contains the contained raw pointer to data along with the reference count value </li></ul><ul><li>SmartPointer ctor sets the pValue->refcount to 1 </li></ul><ul><li>SmartPointer copy ctor increments the pValue->refcount </li></ul><ul><li>SmartPointer dtor decrements the pValue->refcount and if it is 0, deletes the contained raw pointer </li></ul><ul><li>SmartPointer = operator decrements the pValue->refcount of LHS object and deletes if 0, and then increments the pValue->refcount of RHS object, also sets the pValue to point to the RHS pValue </li></ul>
  31. 31. Copy on Write <ul><li>Copy on Write technique adds further smarts to reference counting </li></ul><ul><li>When some data is changed by some reference, don’t change the existing data, instead make a copy of the data, and have the modifying reference point to the new copy, breaking away from the earlier shared group </li></ul><ul><li>Works like ref-counting until there’s only read-only access, but deviates when the data is changed </li></ul><ul><li>non const member functions to implement the COW technique </li></ul>
  32. 32. Smart pointer techniques beyond pointers <ul><li>Scoped locks automate locking and unlocking of mutexes </li></ul><ul><li>Extend to any resource (Eg: File handles) </li></ul><ul><li>Used for implementing ‘Proxy’ objects -> and * could get some data across the network and make it available locally (‘Under the hood’), allowing us to access remote objects using pointers </li></ul>