C++ Memory Management

25,983 views
25,768 views

Published on

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

Published in: Technology, Education
2 Comments
15 Likes
Statistics
Notes
  • was a Good article !! Thanx !!
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • IT IS GOOD GOR ME
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
No Downloads
Views
Total views
25,983
On SlideShare
0
From Embeds
0
Number of Embeds
221
Actions
Shares
0
Downloads
833
Comments
2
Likes
15
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>

×