8. pointer, stack and heap, memory leaks, dangling pointers
30 May 2020 | Modern C++
Using pointers for classes
- Pointers can point to objects of custom classes:
std :: vector <int> vector_int ;
std :: vector <int>* vec_ptr = & vector_int ;
MyClass obj;
MyClass* obj_ptr = &obj;
- Call object functions from pointer with ->
MyClass obj;
obj.MyFunc();
MyClass* obj_ptr = &obj;
obj_ptr ->MyFunc ();
- obj->Func() ↔ (* obj).Func()
Pointers are polymorphic
- Pointers are just like references, but have additional useful properties:
- Can be reassigned
- Can point to ‘‘nothing’’ (nullptr)
- Can be stored in a vector or an array
- Use pointers for polymorphism
Derived derived;
Base* ptr = &derived;
- Example: for implementing strategy store a pointer to the strategy interface and initialize it with nullptr and check if it is set before calling its methods
#include <iostream>
#include <vector>
using std::cout;
struct AbstractShape {
virtual void Print () const = 0;
};
struct square : public AbstractShape{
void Print () const override {cout << "Square";}
};
struct triangle : public AbstractShape{
void Print () const override {cout << "triangle";}
};
int main () {
std::vector<AbstractShape * > shapes;
Square square;
Triangle triangle;
shapes. push_back (& square);
shapes. push_back (& triangle);
for (const auto* shape : shapes) { shape ->Print (); }
return 0;
}
this pointer
- Every object of a class or a struct holds a pointer to itself
- This pointer is called this
- Allows the objects to:
- Return a reference to themselves: return * this;
- Create copies of themselves within a function
- Explicitly show that a member belongs to the current object: this->x();
Using const with pointers
- Pointers can point to a const variable:
// Cannot change value , can reassign pointer.
const MyType* const_var_ptr = &var;
const_var_ptr = & var_other ;
- Pointers can be const:
// Cannot reassign ponter , can change value.
MyType* const var_const_ptr = &var;
var_const_ptr ->a = 10;
- Pointers can do both at the same time:
// Cannot change in any way, read -only
const MyType* const const_var_const_ptr = &var;
- Read from right to left to see which const refers to what
Memory management structures
- Working memory is divided into two parts:
Stack memory
- Static memory
- Available for short term storage (scope)
- Small / limited (8 MB Linux typisch)
- Memory allocation is fast
- LIFO (Last in First out) structure
- Items added to top of the stack with push
- Items removed from the top with pop
-
result:
42
4952101
-
only pointing first array in here until it give a certain array index to pointer.
Heap Memory
- Dynamic memory
- Available for long time (program runtime)
- Raw modifications possible with new and delete (usually encapsulated within a class)
- Allocation is slower than stack allocations
Operators new and new[]
- User controls memory allocation (unsafe)
- Use new to allocate data:
// pointer variable stored on stack
int* int_ptr = nullptr;
// 'new' returns a pointer to memory in heap
int_ptr = new int;
// also works for arrays
float* float_ptr = nullptr;
// 'new' returns a pointer to an array on heap
float_ptr = new float[number ];
- new returns an address of the variable on the heap
- Prefer using smart pointers!
- Memory is not freed automatically!
- User must remember to free the memory
- Use delete or delete[] to free memory:
```c++
int* int_ptr = nullptr;
int_ptr = new int;
// delete frees memory to which the pointer points
delete int_ptr;
// also works for arrays
float* float_ptr = nullptr;
float_ptr = new float[number ];
// make sure to use ‘delete[]’ for arrays
delete[] float_ptr ;
- Prefer using smart pointers!
### Example: heap memory
```c++
#include <iostream>
using std::cout; using std::endl;
int main () {
int size = 2; int* ptr = nullptr;
{
ptr = new int[size ];
ptr [0] = 42; ptr [1] = 13;
}
// End of scope does not free heap memory!
// Correct access , variables still in memory.
for (int i = 0; i < size; ++i) {
cout << ptr[i] << endl;
}
delete[] ptr; // Free memory.
for (int i = 0; i < size; ++i) {
// Accessing freed memory. UNDEFINED!
cout << ptr[i] << endl;
}
return 0;
}
Memory Leak
- Can happen when working with Heap memory if we are not careful
- Memory leak: memory allocated on Heap access to which has been lost
#include <iostream>
using std::cout; using std::endl;
int main () {
double * ptr_1 = NULL;
double * ptr_2 = NULL;
int size = 10;
// Allocate memory for two arrays on the heap
ptr_1 = new double[size];
ptr_2 = new double[size];
cout << "1: " << ptr_1 << " 2: " << ptr_2 << endl;
ptr_2 = ptr_1;
// ptr_2 overwritten , no chance to access the memory.
cout << "1: " << ptr_1 << " 2: " << ptr_2 << endl;
delete[] ptr_1;
delete[] ptr_2;
return 0;
}
- The memory under address 0x10a3070 is never freed
- Instead we try to free memory under 0x10a3010 twice
- Freeing memory twice is an error
Memory leak example
#include <iostream>
#include <cmath>
#include <algorithm>
using std::cout; using std::endl;
int main() {
double * data = nullptr;
size_t size = pow (1024 , 3) / 8; // Produce 1GB
for (int i = 0; i < 5; ++i) {
// Allocate memory for the data.
data = new double[size ];
std :: fill(data , data + size , 1.23);
// Do some important work with the data here
cout << "Iteration: " << i << " done!" << endl;
}
// This will only free the last allocation!
delete[] data;
int unused; std :: cin >> unused; // Wait for user.
return 0;
}
- result
1 Iteration : 0 done!
2 Iteration : 1 done!
3 Iteration : 2 done!
4 Iteration : 3 done!
5 terminate called after throwing an instance of 'std ::
bad_alloc '
6 what (): std :: bad_alloc
- If we run out of memory an std::bad_alloc error is thrown
- Be careful running this example, everything might become slow
Dangling pointer(Memory Leak 반대개념)
int* ptr_1 = some_heap_address ;
int* ptr_2 = some_heap_address ;
delete ptr_1;
ptr_1 = nullptr;
// Cannot use ptr_2 anymore! Behavior undefined!
- Dangling Pointer: pointer to a freed memory
- Think of it as the opposite of a memory leak
- Dereferencing a dangling pointer causes undefined behavior
Dangling pointer example
#include <iostream>
using std::cout; using std::endl;
int main () {
int size = 5;
int* ptr_1 = new int[size ];
int* ptr_2 = ptr_1; // Point to same data!
ptr_1 [0] = 100; // Set some data.
cout << "1: " << ptr_1 << " 2: " << ptr_2 << endl;
cout << "ptr_2[0]: " << ptr_2 [0] << endl;
delete[] ptr_1; // Free memory.
ptr_1 = nullptr;
cout << "1: " << ptr_1 << " 2: " << ptr_2 << endl;
// Data under ptr_2 does not exist anymore!
cout << "ptr_2[0]: " << ptr_2 [0] << endl;
return 0;
}
Even worse when used in functions
#include <stdio.h>
// data processing
int* GenerateData(int size);
void UseDataForGood (const int* const data , int size);
void UseDataForBad (const int* const data , int size);
int main () {
int size = 10;
int* data = GenerateData (size);
UseDataForGood (data , size);
UseDataForBad (data , size);
// Is data pointer valid here? Should we free it?
// Should we use 'delete[]' or 'delete '?
delete[] data; // ?????????????
return 0;
}
Memory leak or dangling pointer
void UseDataForGood (const int* const data , int size) {
// Process data , do not free. Leave it to caller.
}
void UseDataForBad (const int* const data , int size) {
delete[] data; // Free memory!
data = nullptr; // Another problem - this does nothing!
}
- Memory leak if nobody has freed the memory
- Dangling Pointer if somebody has freed the memory in a function
RAII
- Resource Allocation Is Initialization.
- New object → allocate memory
- Remove object → free memory
- Objects own their data!
struct SomeOtherClass{};
class MyClass {
public:
MyClass () { data_ = new SomeOtherClass ; }
~MyClass () {
delete data_;
data_ = nullptr;
}
private:
SomeOtherClass * data_;
};
int main()
{
MyClass a; //copy constructure
MyClass b(a); // copy object
return 0;
}
- result
- samd data allocated
1 *** Error in `raii_example ':
2 double free or corruption : 0 x0000000000877c20 ***
Shallow vs deep copy
- Shallow copy: just copy pointers, not data
- Deep copy: copy data, create new pointers
- Default copy constructor and assignment operator implement shallow copying
- RAII + shallow copy → dangling pointer
- RAII + Rule of All Or Nothing → correct
- Use smart pointers instead!
Reference
https://www.ipb.uni-bonn.de/teaching/modern-cpp/
Cpp Core Guidelines:
https://github.com/isocpp/CppCoreGuidelines
Git guide:
http://rogerdudler.github.io/git-guide/
C++ Tutorial:
http://www.cplusplus.com/doc/tutorial/
Book: Code Complete 2 by Steve McConnell
Modern CMake Tutorial
https://www.youtube.com/watch?v=eC9-iRN2b04
Compiler Explorer:
https://godbolt.org/
Gdbgui:
https://www.gdbgui.com/
CMake website:
https://cmake.org/
Gdbgui tutorial:
https://www.youtube.com/watch?v=em842geJhfk
Fluent C++: structs vs classes:
https://google.github.io/styleguide/cppguide.html#Structs_vs._Classes
Using pointers for classes
- Pointers can point to objects of custom classes:
std :: vector <int> vector_int ;
std :: vector <int>* vec_ptr = & vector_int ;
MyClass obj;
MyClass* obj_ptr = &obj;
- Call object functions from pointer with ->
MyClass obj; obj.MyFunc(); MyClass* obj_ptr = &obj; obj_ptr ->MyFunc ();
- obj->Func() ↔ (* obj).Func()
Pointers are polymorphic
- Pointers are just like references, but have additional useful properties:
- Can be reassigned
- Can point to ‘‘nothing’’ (nullptr)
- Can be stored in a vector or an array
- Use pointers for polymorphism
Derived derived;
Base* ptr = &derived;
- Example: for implementing strategy store a pointer to the strategy interface and initialize it with nullptr and check if it is set before calling its methods
#include <iostream>
#include <vector>
using std::cout;
struct AbstractShape {
virtual void Print () const = 0;
};
struct square : public AbstractShape{
void Print () const override {cout << "Square";}
};
struct triangle : public AbstractShape{
void Print () const override {cout << "triangle";}
};
int main () {
std::vector<AbstractShape * > shapes;
Square square;
Triangle triangle;
shapes. push_back (& square);
shapes. push_back (& triangle);
for (const auto* shape : shapes) { shape ->Print (); }
return 0;
}
this pointer
- Every object of a class or a struct holds a pointer to itself
- This pointer is called this
- Allows the objects to:
- Return a reference to themselves: return * this;
- Create copies of themselves within a function
- Explicitly show that a member belongs to the current object: this->x();
Using const with pointers
- Pointers can point to a const variable:
// Cannot change value , can reassign pointer. const MyType* const_var_ptr = &var; const_var_ptr = & var_other ;
- Pointers can be const:
// Cannot reassign ponter , can change value. MyType* const var_const_ptr = &var; var_const_ptr ->a = 10;
- Pointers can do both at the same time:
// Cannot change in any way, read -only const MyType* const const_var_const_ptr = &var;
- Read from right to left to see which const refers to what
Memory management structures
- Working memory is divided into two parts:
Stack memory
- Static memory
- Available for short term storage (scope)
- Small / limited (8 MB Linux typisch)
- Memory allocation is fast
- LIFO (Last in First out) structure
- Items added to top of the stack with push
- Items removed from the top with pop
-
result: 42 4952101
-
only pointing first array in here until it give a certain array index to pointer.
Heap Memory
- Dynamic memory
- Available for long time (program runtime)
- Raw modifications possible with new and delete (usually encapsulated within a class)
- Allocation is slower than stack allocations
Operators new and new[]
- User controls memory allocation (unsafe)
- Use new to allocate data:
// pointer variable stored on stack int* int_ptr = nullptr; // 'new' returns a pointer to memory in heap int_ptr = new int; // also works for arrays float* float_ptr = nullptr; // 'new' returns a pointer to an array on heap float_ptr = new float[number ];
- new returns an address of the variable on the heap
- Prefer using smart pointers!
- Memory is not freed automatically!
- User must remember to free the memory
- Use delete or delete[] to free memory: ```c++ int* int_ptr = nullptr; int_ptr = new int; // delete frees memory to which the pointer points delete int_ptr;
// also works for arrays float* float_ptr = nullptr; float_ptr = new float[number ]; // make sure to use ‘delete[]’ for arrays delete[] float_ptr ;
- Prefer using smart pointers!
### Example: heap memory
```c++
#include <iostream>
using std::cout; using std::endl;
int main () {
int size = 2; int* ptr = nullptr;
{
ptr = new int[size ];
ptr [0] = 42; ptr [1] = 13;
}
// End of scope does not free heap memory!
// Correct access , variables still in memory.
for (int i = 0; i < size; ++i) {
cout << ptr[i] << endl;
}
delete[] ptr; // Free memory.
for (int i = 0; i < size; ++i) {
// Accessing freed memory. UNDEFINED!
cout << ptr[i] << endl;
}
return 0;
}
Memory Leak
- Can happen when working with Heap memory if we are not careful
- Memory leak: memory allocated on Heap access to which has been lost
#include <iostream>
using std::cout; using std::endl;
int main () {
double * ptr_1 = NULL;
double * ptr_2 = NULL;
int size = 10;
// Allocate memory for two arrays on the heap
ptr_1 = new double[size];
ptr_2 = new double[size];
cout << "1: " << ptr_1 << " 2: " << ptr_2 << endl;
ptr_2 = ptr_1;
// ptr_2 overwritten , no chance to access the memory.
cout << "1: " << ptr_1 << " 2: " << ptr_2 << endl;
delete[] ptr_1;
delete[] ptr_2;
return 0;
}
- The memory under address 0x10a3070 is never freed
- Instead we try to free memory under 0x10a3010 twice
- Freeing memory twice is an error
Memory leak example
#include <iostream>
#include <cmath>
#include <algorithm>
using std::cout; using std::endl;
int main() {
double * data = nullptr;
size_t size = pow (1024 , 3) / 8; // Produce 1GB
for (int i = 0; i < 5; ++i) {
// Allocate memory for the data.
data = new double[size ];
std :: fill(data , data + size , 1.23);
// Do some important work with the data here
cout << "Iteration: " << i << " done!" << endl;
}
// This will only free the last allocation!
delete[] data;
int unused; std :: cin >> unused; // Wait for user.
return 0;
}
- result
1 Iteration : 0 done! 2 Iteration : 1 done! 3 Iteration : 2 done! 4 Iteration : 3 done! 5 terminate called after throwing an instance of 'std :: bad_alloc ' 6 what (): std :: bad_alloc
- If we run out of memory an std::bad_alloc error is thrown
- Be careful running this example, everything might become slow
Dangling pointer(Memory Leak 반대개념)
int* ptr_1 = some_heap_address ;
int* ptr_2 = some_heap_address ;
delete ptr_1;
ptr_1 = nullptr;
// Cannot use ptr_2 anymore! Behavior undefined!
- Dangling Pointer: pointer to a freed memory
- Think of it as the opposite of a memory leak
- Dereferencing a dangling pointer causes undefined behavior
Dangling pointer example
#include <iostream> using std::cout; using std::endl; int main () { int size = 5; int* ptr_1 = new int[size ]; int* ptr_2 = ptr_1; // Point to same data! ptr_1 [0] = 100; // Set some data. cout << "1: " << ptr_1 << " 2: " << ptr_2 << endl; cout << "ptr_2[0]: " << ptr_2 [0] << endl; delete[] ptr_1; // Free memory. ptr_1 = nullptr; cout << "1: " << ptr_1 << " 2: " << ptr_2 << endl; // Data under ptr_2 does not exist anymore! cout << "ptr_2[0]: " << ptr_2 [0] << endl; return 0; }
Even worse when used in functions
#include <stdio.h> // data processing int* GenerateData(int size); void UseDataForGood (const int* const data , int size); void UseDataForBad (const int* const data , int size); int main () { int size = 10; int* data = GenerateData (size); UseDataForGood (data , size); UseDataForBad (data , size); // Is data pointer valid here? Should we free it? // Should we use 'delete[]' or 'delete '? delete[] data; // ????????????? return 0; }
Memory leak or dangling pointer
void UseDataForGood (const int* const data , int size) {
// Process data , do not free. Leave it to caller.
}
void UseDataForBad (const int* const data , int size) {
delete[] data; // Free memory!
data = nullptr; // Another problem - this does nothing!
}
- Memory leak if nobody has freed the memory
- Dangling Pointer if somebody has freed the memory in a function
RAII
- Resource Allocation Is Initialization.
- New object → allocate memory
- Remove object → free memory
- Objects own their data!
struct SomeOtherClass{}; class MyClass { public: MyClass () { data_ = new SomeOtherClass ; } ~MyClass () { delete data_; data_ = nullptr; } private: SomeOtherClass * data_; }; int main() { MyClass a; //copy constructure MyClass b(a); // copy object return 0; }
- result
- samd data allocated
1 *** Error in `raii_example ': 2 double free or corruption : 0 x0000000000877c20 ***
- samd data allocated
Shallow vs deep copy
- Shallow copy: just copy pointers, not data
- Deep copy: copy data, create new pointers
- Default copy constructor and assignment operator implement shallow copying
- RAII + shallow copy → dangling pointer
- RAII + Rule of All Or Nothing → correct
- Use smart pointers instead!
Reference
https://www.ipb.uni-bonn.de/teaching/modern-cpp/
Cpp Core Guidelines: https://github.com/isocpp/CppCoreGuidelines
Git guide: http://rogerdudler.github.io/git-guide/
C++ Tutorial: http://www.cplusplus.com/doc/tutorial/
Book: Code Complete 2 by Steve McConnell
Modern CMake Tutorial https://www.youtube.com/watch?v=eC9-iRN2b04
Compiler Explorer: https://godbolt.org/ Gdbgui: https://www.gdbgui.com/ CMake website: https://cmake.org/ Gdbgui tutorial: https://www.youtube.com/watch?v=em842geJhfk
Fluent C++: structs vs classes: https://google.github.io/styleguide/cppguide.html#Structs_vs._Classes
Comments