> animals{};
```
```c++
// create the right type of animal
if (animal == "dog")
{
Dog dog{}; // create dog object
animals.push_back(dog); // store reference to dog object
} // โผ๏ธdog object ceases to existโผ๏ธ
```
```c++
// walk through the list
for (auto const& animal : animals)
{
animal.get().speak(); // oops, attempt to access object
// that no longer exists ๐ฃ
}
```
---
I want animal objects that don't go out of scope!
---
Where are variables stored anyway? ๐ค
---
## Stack vs. Heap
---
* Where are variables stored?
* Where are function arguments stored?
* How does the program know where to return when a function ends?
---
### The call stack
---
A stack is a LIFO data structure.
Note:
* LIFO = Last In First Out
* New items are added to the top.
* The most recent item is removed first.
---
#### How does the call stack work?
Note:
* Next slide has an example.
* Students try to understand this themselves and explain it to each other.
---
```c++ []
int c(int j, int k)
{
return j + k;
}
int b(int i)
{
return i * 2;
}
int a()
{
int x{5};
return c(b(x), x);
}
int main()
{
return a();
}
```
```mermaid
block-beta
columns 3
addr1["0x1000"]:1
frame1[" "]:2
addr2["0x1040"]:1
frame2[" "]:2
addr3["0x1080"]:1
frame3[" "]:2
addr4["0x10C0"]:1
frame4[" "]:2
addr5["0x1200"]:1
frame5[" "]:2
addr6["0x1240"]:1
frame6[" "]:2
addr7["0x1280"]:1
frame7[" "]:2
addr8["0x12C0"]:1
frame8[" "]:2
addr9["0x1300"]:1
frame9[" "]:2
addr10["0x1340"]:1
frame10[" "]:2
addr11["0x1380"]:1
frame11[" "]:2
addr12["0x13C0"]:1
frame12[" "]:2
space:3
space callstack("Call Stack") space
space:3
space:3
reg1["PC"]:1
val1["address of call to main()"]:2
reg2["SP"]:1
val2["0x13C0"]:2
reg3["RAX"]:1
val3[" "]:2
space:3
space registers("Registers") space
```
Note:
* The main function is called from startup code.
* The stack pointer register (SP) keeps track of where we are. It points to the top of the stack.
* The program counter register (PC) keeps track of which instruction we are executing. It points to the current instruction in
the assembly code.
* The RAX register is used to store function return values. They are not stored on the stack.
* When the end of a function is reached, its stack frame is removed from the stack. The return value (if any) is set in RAX. And
the program counter is updated to the return address that was stored on the stack.
* We use line numbers here for the program counter. In reality it holds the address of the current instruction in the assembly
code.
--
```c++ [17]
int c(int j, int k)
{
return j + k;
}
int b(int i)
{
return i * 2;
}
int a()
{
int x{5};
return c(b(x), x);
}
int main()
{
return a();
}
```
```mermaid
block-beta
columns 3
addr1["0x1000"]:1
frame1[" "]:2
addr2["0x1040"]:1
frame2[" "]:2
addr3["0x1080"]:1
frame3[" "]:2
addr4["0x10C0"]:1
frame4[" "]:2
addr5["0x1200"]:1
frame5[" "]:2
addr6["0x1240"]:1
frame6[" "]:2
addr7["0x1280"]:1
frame7[" "]:2
addr8["0x12C0"]:1
frame8[" "]:2
addr9["0x1300"]:1
frame9[" "]:2
addr10["0x1340"]:1
frame10[" "]:2
addr11["0x1380"]:1
frame11[" "]:2
addr12["0x13C0"]:1
frame12["return address for main()"]:2
space:3
space callstack("Call Stack") space
space:3
space:3
reg1["PC"]:1
val1["address of call to main()"]:2
reg2["SP"]:1
val2["0x1380"]:2
reg3["RAX"]:1
val3[" "]:2
space:3
space registers("Registers") space
```
Note:
* After the main function ends, code in the startup function should resume.
* The address of the next instruction in that startup function is the return address for main() that is stored on the stack.
--
```c++ [19]
int c(int j, int k)
{
return j + k;
}
int b(int i)
{
return i * 2;
}
int a()
{
int x{5};
return c(b(x), x);
}
int main()
{
return a();
}
```
```mermaid
block-beta
columns 3
addr1["0x1000"]:1
frame1[" "]:2
addr2["0x1040"]:1
frame2[" "]:2
addr3["0x1080"]:1
frame3[" "]:2
addr4["0x10C0"]:1
frame4[" "]:2
addr5["0x1200"]:1
frame5[" "]:2
addr6["0x1240"]:1
frame6[" "]:2
addr7["0x1280"]:1
frame7[" "]:2
addr8["0x12C0"]:1
frame8[" "]:2
addr9["0x1300"]:1
frame9[" "]:2
addr10["0x1340"]:1
frame10[" "]:2
addr11["0x1380"]:1
frame11["return address for a()"]:2
addr12["0x13C0"]:1
frame12["return address for main()"]:2
space:3
space callstack("Call Stack") space
space:3
space:3
reg1["PC"]:1
val1["line 19, a()"]:2
reg2["SP"]:1
val2["0x1340"]:2
reg3["RAX"]:1
val3[" "]:2
space:3
space registers("Registers") space
```
Note:
* Call function a().
* Its return address (address of return instruction on line 19) is stored on the stack.
--
```c++ [13]
int c(int j, int k)
{
return j + k;
}
int b(int i)
{
return i * 2;
}
int a()
{
int x{5};
return c(b(x), x);
}
int main()
{
return a();
}
```
```mermaid
block-beta
columns 3
addr1["0x1000"]:1
frame1[" "]:2
addr2["0x1040"]:1
frame2[" "]:2
addr3["0x1080"]:1
frame3[" "]:2
addr4["0x10C0"]:1
frame4[" "]:2
addr5["0x1200"]:1
frame5[" "]:2
addr6["0x1240"]:1
frame6[" "]:2
addr7["0x1280"]:1
frame7[" "]:2
addr8["0x12C0"]:1
frame8[" "]:2
addr9["0x1300"]:1
frame9[" "]:2
addr10["0x1340"]:1
frame10["int x = 5"]:2
addr11["0x1380"]:1
frame11["return address for a()"]:2
addr12["0x13C0"]:1
frame12["return address for main()"]:2
space:3
space callstack("Call Stack") space
space:3
space:3
reg1["PC"]:1
val1["line 13, x = 5"]:2
reg2["SP"]:1
val2["0x1300"]:2
reg3["RAX"]:1
val3[" "]:2
space:3
space registers("Registers") space
```
Note:
* A local variable x is created in function a.
* Local variables are stored on the stack!
--
```c++ [14]
int c(int j, int k)
{
return j + k;
}
int b(int i)
{
return i * 2;
}
int a()
{
int x{5};
return c(b(x), x);
}
int main()
{
return a();
}
```
```mermaid
block-beta
columns 3
addr1["0x1000"]:1
frame1[" "]:2
addr2["0x1040"]:1
frame2[" "]:2
addr3["0x1080"]:1
frame3[" "]:2
addr4["0x10C0"]:1
frame4[" "]:2
addr5["0x1200"]:1
frame5[" "]:2
addr6["0x1240"]:1
frame6[" "]:2
addr7["0x1280"]:1
frame7[" "]:2
addr8["0x12C0"]:1
frame8[" "]:2
addr9["0x1300"]:1
frame9["return address for b()"]:2
addr10["0x1340"]:1
frame10["int x = 5"]:2
addr11["0x1380"]:1
frame11["return address for a()"]:2
addr12["0x13C0"]:1
frame12["return address for main()"]:2
space:3
space callstack("Call Stack") space
space:3
space:3
reg1["PC"]:1
val1["line 14, b(x)"]:2
reg2["SP"]:1
val2["0x12C0"]:2
reg3["RAX"]:1
val3[" "]:2
space:3
space registers("Registers") space
```
Note:
* Before c() can be invoked, we need to call b() first.
* Put the return address for b() on the stack.
* That return address will be the address of the call c() instruction on line 14.
--
```c++ [14,6]
int c(int j, int k)
{
return j + k;
}
int b(int i)
{
return i * 2;
}
int a()
{
int x{5};
return c(b(x), x);
}
int main()
{
return a();
}
```
```mermaid
block-beta
columns 3
addr1["0x1000"]:1
frame1[" "]:2
addr2["0x1040"]:1
frame2[" "]:2
addr3["0x1080"]:1
frame3[" "]:2
addr4["0x10C0"]:1
frame4[" "]:2
addr5["0x1200"]:1
frame5[" "]:2
addr6["0x1240"]:1
frame6[" "]:2
addr7["0x1280"]:1
frame7[" "]:2
addr8["0x12C0"]:1
frame8["int i = x = 5"]:2
addr9["0x1300"]:1
frame9["return address for b()"]:2
addr10["0x1340"]:1
frame10["int x = 5"]:2
addr11["0x1380"]:1
frame11["return address for a()"]:2
addr12["0x13C0"]:1
frame12["return address for main()"]:2
space:3
space callstack("Call Stack") space
space:3
space:3
reg1["PC"]:1
val1["line 14, b(x)"]:2
reg2["SP"]:1
val2["0x1280"]:2
reg3["RAX"]:1
val3[" "]:2
space:3
space registers("Registers") space
```
Note:
* b() takes an integer as argument.
* Function arguments are stored on the stack!
--
```c++ [8]
int c(int j, int k)
{
return j + k;
}
int b(int i)
{
return i * 2;
}
int a()
{
int x{5};
return c(b(x), x);
}
int main()
{
return a();
}
```
```mermaid
block-beta
columns 3
addr1["0x1000"]:1
frame1[" "]:2
addr2["0x1040"]:1
frame2[" "]:2
addr3["0x1080"]:1
frame3[" "]:2
addr4["0x10C0"]:1
frame4[" "]:2
addr5["0x1200"]:1
frame5[" "]:2
addr6["0x1240"]:1
frame6[" "]:2
addr7["0x1280"]:1
frame7[" "]:2
addr8["0x12C0"]:1
frame8["int i = 5"]:2
addr9["0x1300"]:1
frame9["return address for b()"]:2
addr10["0x1340"]:1
frame10["int x = 5"]:2
addr11["0x1380"]:1
frame11["return address for a()"]:2
addr12["0x13C0"]:1
frame12["return address for main()"]:2
space:3
space callstack("Call Stack") space
space:3
space:3
reg1["PC"]:1
val1["line 8, i * 2"]:2
reg2["SP"]:1
val2["0x1280"]:2
reg3["RAX"]:1
val3[" "]:2
space:3
space registers("Registers") space
```
Note:
* The next step is to calculate the value of i * 2.
--
```c++ [8]
int c(int j, int k)
{
return j + k;
}
int b(int i)
{
return i * 2;
}
int a()
{
int x{5};
return c(b(x), x);
}
int main()
{
return a();
}
```
```mermaid
block-beta
columns 3
addr1["0x1000"]:1
frame1[" "]:2
addr2["0x1040"]:1
frame2[" "]:2
addr3["0x1080"]:1
frame3[" "]:2
addr4["0x10C0"]:1
frame4[" "]:2
addr5["0x1200"]:1
frame5[" "]:2
addr6["0x1240"]:1
frame6[" "]:2
addr7["0x1280"]:1
frame7[" "]:2
addr8["0x12C0"]:1
frame8[" "]:2
addr9["0x1300"]:1
frame9["return address for b()"]:2
addr10["0x1340"]:1
frame10["int x = 5"]:2
addr11["0x1380"]:1
frame11["return address for a()"]:2
addr12["0x13C0"]:1
frame12["return address for main()"]:2
space:3
space callstack("Call Stack") space
space:3
space:3
reg1["PC"]:1
val1["line 8, return"]:2
reg2["SP"]:1
val2["0x12C0"]:2
reg3["RAX"]:1
val3["i * 2 = 10"]:2
space:3
space registers("Registers") space
```
Note:
* After the calculation is done, i is no longer needed. It is removed from the stack.
* The return instruction is next. The calculated value is stored in register RAX.
--
```c++ [9]
int c(int j, int k)
{
return j + k;
}
int b(int i)
{
return i * 2;
}
int a()
{
int x{5};
return c(b(x), x);
}
int main()
{
return a();
}
```
```mermaid
block-beta
columns 3
addr1["0x1000"]:1
frame1[" "]:2
addr2["0x1040"]:1
frame2[" "]:2
addr3["0x1080"]:1
frame3[" "]:2
addr4["0x10C0"]:1
frame4[" "]:2
addr5["0x1200"]:1
frame5[" "]:2
addr6["0x1240"]:1
frame6[" "]:2
addr7["0x1280"]:1
frame7[" "]:2
addr8["0x12C0"]:1
frame8[" "]:2
addr9["0x1300"]:1
frame9[" "]:2
addr10["0x1340"]:1
frame10["int x = 5"]:2
addr11["0x1380"]:1
frame11["return address for a()"]:2
addr12["0x13C0"]:1
frame12["return address for main()"]:2
space:3
space callstack("Call Stack") space
space:3
space:3
reg1["PC"]:1
val1["line 14, c()"]:2
reg2["SP"]:1
val2["0x1300"]:2
reg3["RAX"]:1
val3["10"]:2
space:3
space registers("Registers") space
```
Note:
* The end of function b() is reached.
* Its return address is removed from the stack and assigned to the program counter.
--
```c++ [14]
int c(int j, int k)
{
return j + k;
}
int b(int i)
{
return i * 2;
}
int a()
{
int x{5};
return c(b(x), x);
}
int main()
{
return a();
}
```
```mermaid
block-beta
columns 3
addr1["0x1000"]:1
frame1[" "]:2
addr2["0x1040"]:1
frame2[" "]:2
addr3["0x1080"]:1
frame3[" "]:2
addr4["0x10C0"]:1
frame4[" "]:2
addr5["0x1200"]:1
frame5[" "]:2
addr6["0x1240"]:1
frame6[" "]:2
addr7["0x1280"]:1
frame7[" "]:2
addr8["0x12C0"]:1
frame8[" "]:2
addr9["0x1300"]:1
frame9["return address for c()"]:2
addr10["0x1340"]:1
frame10["int x = 5"]:2
addr11["0x1380"]:1
frame11["return address for a()"]:2
addr12["0x13C0"]:1
frame12["return address for main()"]:2
space:3
space callstack("Call Stack") space
space:3
space:3
reg1["PC"]:1
val1["line 14, c()"]:2
reg2["SP"]:1
val2["0x12C0"]:2
reg3["RAX"]:1
val3["10"]:2
space:3
space registers("Registers") space
```
Note:
* The next step is to invoke c().
* Its return address (address of the return instruction on line 14) is stored on the stack.
--
```c++ [14,1]
int c(int j, int k)
{
return j + k;
}
int b(int i)
{
return i * 2;
}
int a()
{
int x{5};
return c(b(x), x);
}
int main()
{
return a();
}
```
```mermaid
block-beta
columns 3
addr1["0x1000"]:1
frame1[" "]:2
addr2["0x1040"]:1
frame2[" "]:2
addr3["0x1080"]:1
frame3[" "]:2
addr4["0x10C0"]:1
frame4[" "]:2
addr5["0x1200"]:1
frame5[" "]:2
addr6["0x1240"]:1
frame6[" "]:2
addr7["0x1280"]:1
frame7["int k = x = 5"]:2
addr8["0x12C0"]:1
frame8["int j = RAX = 10"]:2
addr9["0x1300"]:1
frame9["return address for c()"]:2
addr10["0x1340"]:1
frame10["int x = 5"]:2
addr11["0x1380"]:1
frame11["return address for a()"]:2
addr12["0x13C0"]:1
frame12["return address for main()"]:2
space:3
space callstack("Call Stack") space
space:3
space:3
reg1["PC"]:1
val1["line 14, c()"]:2
reg2["SP"]:1
val2["0x1240"]:2
reg3["RAX"]:1
val3["10"]:2
space:3
space registers("Registers") space
```
Note:
* c() takes two arguments.
* Both are stored on the stack.
--
```c++ [3]
int c(int j, int k)
{
return j + k;
}
int b(int i)
{
return i * 2;
}
int a()
{
int x{5};
return c(b(x), x);
}
int main()
{
return a();
}
```
```mermaid
block-beta
columns 3
addr1["0x1000"]:1
frame1[" "]:2
addr2["0x1040"]:1
frame2[" "]:2
addr3["0x1080"]:1
frame3[" "]:2
addr4["0x10C0"]:1
frame4[" "]:2
addr5["0x1200"]:1
frame5[" "]:2
addr6["0x1240"]:1
frame6[" "]:2
addr7["0x1280"]:1
frame7["int k = x = 5"]:2
addr8["0x12C0"]:1
frame8["int j = RAX = 10"]:2
addr9["0x1300"]:1
frame9["return address for c()"]:2
addr10["0x1340"]:1
frame10["int x = 5"]:2
addr11["0x1380"]:1
frame11["return address for a()"]:2
addr12["0x13C0"]:1
frame12["return address for main()"]:2
space:3
space callstack("Call Stack") space
space:3
space:3
reg1["PC"]:1
val1["line 3, j + k"]:2
reg2["SP"]:1
val2["0x1240"]:2
reg3["RAX"]:1
val3["10"]:2
space:3
space registers("Registers") space
```
Note:
* Calculate the result of j + k.
--
```c++ [3]
int c(int j, int k)
{
return j + k;
}
int b(int i)
{
return i * 2;
}
int a()
{
int x{5};
return c(b(x), x);
}
int main()
{
return a();
}
```
```mermaid
block-beta
columns 3
addr1["0x1000"]:1
frame1[" "]:2
addr2["0x1040"]:1
frame2[" "]:2
addr3["0x1080"]:1
frame3[" "]:2
addr4["0x10C0"]:1
frame4[" "]:2
addr5["0x1200"]:1
frame5[" "]:2
addr6["0x1240"]:1
frame6[" "]:2
addr7["0x1280"]:1
frame7[" "]:2
addr8["0x12C0"]:1
frame8[" "]:2
addr9["0x1300"]:1
frame9["return address for c()"]:2
addr10["0x1340"]:1
frame10["int x = 5"]:2
addr11["0x1380"]:1
frame11["return address for a()"]:2
addr12["0x13C0"]:1
frame12["return address for main()"]:2
space:3
space callstack("Call Stack") space
space:3
space:3
reg1["PC"]:1
val1["line 3, return"]:2
reg2["SP"]:1
val2["0x12C0"]:2
reg3["RAX"]:1
val3["j + k = 15"]:2
space:3
space registers("Registers") space
```
Note:
* The variables j and k are no longer needed, they are removed from the stack.
* The return instruction is executed, the result of j + k is assigned to RAX.
--
```c++ [4]
int c(int j, int k)
{
return j + k;
}
int b(int i)
{
return i * 2;
}
int a()
{
int x{5};
return c(b(x), x);
}
int main()
{
return a();
}
```
```mermaid
block-beta
columns 3
addr1["0x1000"]:1
frame1[" "]:2
addr2["0x1040"]:1
frame2[" "]:2
addr3["0x1080"]:1
frame3[" "]:2
addr4["0x10C0"]:1
frame4[" "]:2
addr5["0x1200"]:1
frame5[" "]:2
addr6["0x1240"]:1
frame6[" "]:2
addr7["0x1280"]:1
frame7[" "]:2
addr8["0x12C0"]:1
frame8[" "]:2
addr9["0x1300"]:1
frame9[" "]:2
addr10["0x1340"]:1
frame10["int x = 5"]:2
addr11["0x1380"]:1
frame11["return address for a()"]:2
addr12["0x13C0"]:1
frame12["return address for main()"]:2
space:3
space callstack("Call Stack") space
space:3
space:3
reg1["PC"]:1
val1["line 14, return"]:2
reg2["SP"]:1
val2["0x1300"]:2
reg3["RAX"]:1
val3["15"]:2
space:3
space registers("Registers") space
```
Note:
* The end of the c() function is reached.
* Its return address is removed from the stack and assigned to the program counter.
--
```c++ [14]
int c(int j, int k)
{
return j + k;
}
int b(int i)
{
return i * 2;
}
int a()
{
int x{5};
return c(b(x), x);
}
int main()
{
return a();
}
```
```mermaid
block-beta
columns 3
addr1["0x1000"]:1
frame1[" "]:2
addr2["0x1040"]:1
frame2[" "]:2
addr3["0x1080"]:1
frame3[" "]:2
addr4["0x10C0"]:1
frame4[" "]:2
addr5["0x1200"]:1
frame5[" "]:2
addr6["0x1240"]:1
frame6[" "]:2
addr7["0x1280"]:1
frame7[" "]:2
addr8["0x12C0"]:1
frame8[" "]:2
addr9["0x1300"]:1
frame9[" "]:2
addr10["0x1340"]:1
frame10[" "]:2
addr11["0x1380"]:1
frame11["return address for a()"]:2
addr12["0x13C0"]:1
frame12["return address for main()"]:2
space:3
space callstack("Call Stack") space
space:3
space:3
reg1["PC"]:1
val1["line 14, return"]:2
reg2["SP"]:1
val2["0x1340"]:2
reg3["RAX"]:1
val3["15"]:2
space:3
space registers("Registers") space
```
Note:
* The return instruction on line 14 is the next instruction to execute.
* Local variable x is no longer needed and is removed from the stack.
* The result of the call to c() is assigned to the RAX register.
--
```c++ [15]
int c(int j, int k)
{
return j + k;
}
int b(int i)
{
return i * 2;
}
int a()
{
int x{5};
return c(b(x), x);
}
int main()
{
return a();
}
```
```mermaid
block-beta
columns 3
addr1["0x1000"]:1
frame1[" "]:2
addr2["0x1040"]:1
frame2[" "]:2
addr3["0x1080"]:1
frame3[" "]:2
addr4["0x10C0"]:1
frame4[" "]:2
addr5["0x1200"]:1
frame5[" "]:2
addr6["0x1240"]:1
frame6[" "]:2
addr7["0x1280"]:1
frame7[" "]:2
addr8["0x12C0"]:1
frame8[" "]:2
addr9["0x1300"]:1
frame9[" "]:2
addr10["0x1340"]:1
frame10[" "]:2
addr11["0x1380"]:1
frame11[" "]:2
addr12["0x13C0"]:1
frame12["return address for main()"]:2
space:3
space callstack("Call Stack") space
space:3
space:3
reg1["PC"]:1
val1["line 19, return"]:2
reg2["SP"]:1
val2["0x1380"]:2
reg3["RAX"]:1
val3["15"]:2
space:3
space registers("Registers") space
```
Note:
* The end of a() is reached.
* Its return address is removed from the stack and assigned to the program counter.
--
```c++ [19]
int c(int j, int k)
{
return j + k;
}
int b(int i)
{
return i * 2;
}
int a()
{
int x{5};
return c(b(x), x);
}
int main()
{
return a();
}
```
```mermaid
block-beta
columns 3
addr1["0x1000"]:1
frame1[" "]:2
addr2["0x1040"]:1
frame2[" "]:2
addr3["0x1080"]:1
frame3[" "]:2
addr4["0x10C0"]:1
frame4[" "]:2
addr5["0x1200"]:1
frame5[" "]:2
addr6["0x1240"]:1
frame6[" "]:2
addr7["0x1280"]:1
frame7[" "]:2
addr8["0x12C0"]:1
frame8[" "]:2
addr9["0x1300"]:1
frame9[" "]:2
addr10["0x1340"]:1
frame10[" "]:2
addr11["0x1380"]:1
frame11[" "]:2
addr12["0x13C0"]:1
frame12["return address for main()"]:2
space:3
space callstack("Call Stack") space
space:3
space:3
reg1["PC"]:1
val1["line 19, return"]:2
reg2["SP"]:1
val2["0x1380"]:2
reg3["RAX"]:1
val3["15"]:2
space:3
space registers("Registers") space
```
Note:
* The final instruction in the demo program is the return instruction on line 19.
* The result of invoking a() is stored in the RAX register.
--
```c++ [20]
int c(int j, int k)
{
return j + k;
}
int b(int i)
{
return i * 2;
}
int a()
{
int x{5};
return c(b(x), x);
}
int main()
{
return a();
}
```
```mermaid
block-beta
columns 3
addr1["0x1000"]:1
frame1[" "]:2
addr2["0x1040"]:1
frame2[" "]:2
addr3["0x1080"]:1
frame3[" "]:2
addr4["0x10C0"]:1
frame4[" "]:2
addr5["0x1200"]:1
frame5[" "]:2
addr6["0x1240"]:1
frame6[" "]:2
addr7["0x1280"]:1
frame7[" "]:2
addr8["0x12C0"]:1
frame8[" "]:2
addr9["0x1300"]:1
frame9[" "]:2
addr10["0x1340"]:1
frame10[" "]:2
addr11["0x1380"]:1
frame11[" "]:2
addr12["0x13C0"]:1
frame12[" "]:2
space:3
space callstack("Call Stack") space
space:3
space:3
reg1["PC"]:1
val1["return address for main()"]:2
reg2["SP"]:1
val2["0x13C0"]:2
reg3["RAX"]:1
val3["15"]:2
space:3
space registers("Registers") space
```
Note:
* The end of the main() function is reached.
* Its return address is removed from the stack and assigned to the program counter.
* The startup code will terminate the application.
---
A **stack frame** for a function is the portion of the stack that belongs to that function.
---
A stack frame typically contains:
* Return address.
* Function arguments.
* Local variables.
* (Saved registers.)
---
When a function is called, a new stack frame is pushed onto the stack.
---
When the function exits, its stack frame is popped off, restoring the previous function's context.
---
Only a limited amount of memory is reserved for the call stack.
---
| Platform | Stack size |
|:------------|--------------:|
| Linux/macOS | 8 MiB |
| Windows | 4 MiB |
| Android | 1 MiB |
| FreeRTOS | 128 B - 8 KiB |
Note:
* Typical stack sizes.
* Can be configured in the os or using a compiler flag.
---
A **stack overflow** occurs when the stack runs out of memory.
---
```c++ []
int fibonacci(int n)
{
if (n <= 1) { return n; }
return fibonacci(n-1) + fibonacci(n-2);
}
int main()
{
return fibonacci(100'000);
}
```
```sh [3]
AddressSanitizer:DEADLYSIGNAL
=================================================================
==4609==ERROR: AddressSanitizer: stack-overflow on address 0x7fff7a319ff0 (pc 0x56499fbc3d0c bp 0x7fff7a31a010 sp 0x7fff7a319fc0 T0)
```
Note:
* Stack overflow because of deep recursion.
* So many stack frames that it no longers fits in the stack memory.
*
---
```c++ []
using std;
int main()
{
std::array a_lot_of_numbers{};
for (auto const& number : a_lot_of_numbers)
{
std::println("{}", number);
}
}
```
```sh [3]
AddressSanitizer:DEADLYSIGNAL
=================================================================
==1==ERROR: AddressSanitizer: stack-overflow on address 0x7ffd339b8108 (pc 0x5617600a805d bp 0x000000000001 sp 0x7ffd339b8110 T0)
#0 0x5617600a805d in main /app/example.cpp:6:36
#1 0x774586829d8f (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f) (BuildId: 490fef8403240c91833978d494d39e537409b92e)
#2 0x774586829e3f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x29e3f) (BuildId: 490fef8403240c91833978d494d39e537409b92e)
#3 0x56175ffc0534 in _start (/app/output.s+0x2f534)
SUMMARY: AddressSanitizer: stack-overflow /app/example.cpp:6:36 in main
==1==ABORTING
```
Note:
* Stack overflow because of large amount of data.
* One huge stack frame that does not fit.
* 10.000.000 * sizeof(double) = 80MB
*
---
The call stack is not a suitable place to store large amounts of data!
---
### The Heap
aka the free store
---
The heap is a region of memory for dynamic memory allocation. It allows programs to allocate memory at runtime.
---
Heap memory is limited only by system resources (available RAM).
---
The heap is not automatically managed. Memory is allocated and deallocated explicitly by the program.
---
A memory leak occurs when a program does not deallocate memory it has allocated.
---
Luckily C++ has RAII to automatically manage resources!
---
```c++ []
using std;
int main()
{
auto int_on_the_heap = std::make_unique(5);
std::println("Heap says hello {}!", *int_on_the_heap);
*int_on_the_heap = 10;
std::println("New value is {}", *int_on_the_heap);
}
```
```text
Heap says hello 5
New value is 10
```
Note:
* std::make_unique creates a std::unique_ptr.
* std::unique_ptr automatically frees its memory when it goes out of scope.
* The arguments passed to std::make_unique are forwarded to the constructor. In this case an int with value 5 is created.
* Access the underlying value using the dereference operator `*`.
*
---
```c++
// create an array of 10 million doubles
auto a_lot_of_numbers =
std::make_unique>();
```
```c++
// set all the items to pi
std::ranges::fill(*a_lot_of_numbers, std::numbers::pi);
```
```c++
// print the array
for (auto const& number : *a_lot_of_numbers)
{
std::print("{} ", number);
}
```
Note:
* Plenty of room in RAM to allocate 80 MB worth of doubles.
*
---
```c++
std::vector a_lot_of_numbers(
10'000'000, std::numbers::pi
);
```
```c++
for (auto const& number : a_lot_of_numbers)
{
std::print("{} ", number);
}
```
Or use a container such as std::vector!
Note:
* Previous slide and this slide are pretty much the same.
---
It's possible for the heap to get fragmented!
---
```mermaid
packet-beta
0-31: "Free Space (32 Bytes)"
```
Start with an empty heap of 32 Bytes.
--
```mermaid
packet-beta
0-31: "Free Space (32 Bytes)"
```
Allocate 16 Bytes.
--
```mermaid
packet-beta
0-15: "Block 1 (16 Bytes)"
16-31: "Free Space (16 Bytes)"
```
16 Bytes used, 16 Bytes free
--
```mermaid
packet-beta
0-15: "Block 1 (16 Bytes)"
16-31: "Free Space (16 Bytes)"
```
Allocate 8 Bytes.
--
```mermaid
packet-beta
0-15: "Block 1 (16 Bytes)"
16-23: "Block 2 (8 Bytes)"
24-31: "Free Space (8 Bytes)"
```
24 Bytes used, 8 Bytes free
--
```mermaid
packet-beta
0-15: "Block 1 (16 Bytes)"
16-23: "Block 2 (8 Bytes)"
24-31: "Free Space (8 Bytes)"
```
Free Block 1.
--
```mermaid
packet-beta
0-15: "Free Space (16 Bytes)"
16-23: "Block 2 (8 Bytes)"
24-31: "Free Space (8 Bytes)"
```
8 Bytes used, 24 Bytes free
--
```mermaid
packet-beta
0-15: "Free Space (16 Bytes)"
16-23: "Block 2 (8 Bytes)"
24-31: "Free Space (8 Bytes)"
```
Allocate 4 Bytes.
--
```mermaid
packet-beta
0-3: "Block 3 (4 Bytes)"
4-15: "Free Space (12 Bytes)"
16-23: "Block 2 (8 Bytes)"
24-31: "Free Space (8 Bytes)"
```
12 Bytes used, 20 Bytes free
--
```mermaid
packet-beta
0-3: "Block 3 (4 Bytes)"
4-15: "Free Space (12 Bytes)"
16-23: "Block 2 (8 Bytes)"
24-31: "Free Space (8 Bytes)"
```
Allocate 4 Bytes.
--
```mermaid
packet-beta
0-3: "Block 3 (4 Bytes)"
4-7: "Block 4 (4 Bytes)"
8-15: "Free Space (8 Bytes)"
16-23: "Block 2 (8 Bytes)"
24-31: "Free Space (8 Bytes)"
```
16 Bytes used, 16 Bytes free
--
```mermaid
packet-beta
0-3: "Block 3 (4 Bytes)"
4-7: "Block 4 (4 Bytes)"
8-15: "Free Space (8 Bytes)"
16-23: "Block 2 (8 Bytes)"
24-31: "Free Space (8 Bytes)"
```
Allocate 12 Bytes.
--
```mermaid
packet-beta
0-3: "Block 3 (4 Bytes)"
4-7: "Block 4 (4 Bytes)"
8-15: "Free Space (8 Bytes)"
16-23: "Block 2 (8 Bytes)"
24-31: "Free Space (8 Bytes)"
```
There's still 16 Bytes of free space on the heap.
--
```mermaid
packet-beta
0-3: "Block 3 (4 Bytes)"
4-7: "Block 4 (4 Bytes)"
8-15: "Free Space (8 Bytes)"
16-23: "Block 2 (8 Bytes)"
24-31: "Free Space (8 Bytes)"
```
But it's fragmented into two blocks of 8 Bytes.
--
```mermaid
packet-beta
0-3: "Block 3 (4 Bytes)"
4-7: "Block 4 (4 Bytes)"
8-15: "Free Space (8 Bytes)"
16-23: "Block 2 (8 Bytes)"
24-31: "Free Space (8 Bytes)"
```
The allocation fails!
---
Be mindful about heap fragmentation!
---
The longer your program runs, the more fragmented memory will become.
---
It's probably not something to worry about when writing an application for a system with gigabytes of memory.
---
But it is a very real concern for embedded applications that keep running for many years on a micro controller with only a few
kilobytes of memory.
Note:
* And also the reason why in such applications the heap is often not used!
---
### Best practices
---
* Use the stack for small local variables.
* Use the heap for large data and runtime variables.
* Be careful when writing recursive functions.
* By mindful about heap fragmentation.
---
## Dynamic memory for polymorphisms
Deciding at runtime which object to create.
---
```c++ []
// Interface A
struct A
{
virtual ~A() = default;
virtual void print() const = 0;
};
// Implementations B and C
struct B : A { void print() const override { std::println("B"); } };
struct C : A { void print() const override { std::println("C"); } };
```
```c++ []
std::unique_ptr some_sort_of_a{}; // starts out empty!
if (/*some runtime condition*/) {
some_sort_of_a = std::make_unique();
} else {
some_sort_of_a = std::make_unique();
}
some_sort_of_a->print(); // prints B or prints C
```
Note:
* A unique_ptr<> to concrete class can be stored in a unique_ptr<> to base class.
* This is how we can use polymorphisms for objects created at runtime.
*
* Be careful not to dereference some_sort_of_a if it is empty!
```c++
if (some_sort_of_a)
{
// points to something valid
// it is safe to call the print() method
}
```
---
This is why virtual destructors are important! ๐
---
```c++
struct A {
A() { std::println("A::A()"); }
~A() { std::println("A::~A()"); }
};
struct B : A {
B() { std::println("B::B()"); }
~B() { std::println("B::~B()"); }
};
```
```c++
int main() {
std::unique_ptr ptr = std::make_unique();
}
```
```text
A::A()
B::B()
A::~A()
```
Note:
* We construct a B object (which first calls the parent constructor A() and then runs B's constructor).
* When the object goes out-of-scope, we expect the destructor of B to be called (which should first run its own code and then
call the ~A() destructor).
* But since we call the destructor through a pointer to A and the A destructor is not virtual, only ~A() is called.
*
---
```c++
struct A {
A() { std::println("A::A()"); }
virtual ~A() { std::println("A::~A()"); }
};
struct B : A {
B() { std::println("B::B()"); }
~B() { std::println("B::~B()"); }
};
```
```c++
int main() {
std::unique_ptr ptr = std::make_unique();
}
```
```text
A::A()
B::B()
B::~B()
A::~A()
```
Note:
* We added virtual to A's destructor.
* B's destructor is now correctly called!
*
*
---
Don't forget to add a virtual destructor to base classes with at least one virtual methodโผ๏ธ
---
### Vector runtime Animal objects
---
```c++
class Animal
{
public:
virtual ~Animal() = default;
void speak() const { /* uses speak_impl() */ }
// ...
private:
virtual std::string speak_impl() const = 0;
};
```
```c++
class Dog : public Animal { /*...*/ };
class Cat : public Animal { /*...*/ };
class Bear : public Animal { /*...*/ };
class Hamster : public Animal { /*...*/ };
```
Note:
* The example we were using.
---
```c++
std::vector<โ> animals{};
```
```c++
if (animal == "dog")
{
// create dog object
animals.push_back(/*dog object*/);
}
```
```c++
// cat, bear, hamster, ...
```
```c++
for (auto const& animal : animals)
{
// call speak() on animal
}
```
Note:
* What we want to be able to do.
---
Animal objects should stay alive as long as the vector.
---
Make vector the owner of animal objects.
---
```c++
std::vector> animals{};
```
```c++
if (animal == "dog")
{
animals.push_back(std::make_unique());
}
if (animal == "cat")
{
animals.push_back(std::make_unique());
}
// bear, hamster, ...
```
```c++
for (auto const& animal : animals)
{
animal->speak();
}
```
Note:
*
---
```c++
void speak(โ animal)
{
animal->speak();
}
```
```c++
std::vector> animals{};
```
```c++
for (auto const& animal : animals)
{
speak(/* animal */);
}
```
What if I want to call a function instead?
---
```c++
void speak(std::unique_ptr animal)
{
animal->speak();
}
```
```c++
for (auto const& animal : animals)
{
speak(animal);
}
```
Does this work?
---
```sh []
:115:15: error: call to deleted constructor of 'std::unique_ptr'
115 | speak(animal);
| ^~~~~~
/opt/compiler-explorer/gcc-14.2.0/lib/gcc/x86_64-linux-gnu/14.2.0/../../../../include/c++/14.2.0/bits/unique_ptr.h:516:7: note: 'unique_ptr' has been explicitly marked deleted here
516 | unique_ptr(const unique_ptr&) = delete;
| ^
:78:36: note: passing argument to parameter 'animal' here
78 | void speak(std::unique_ptr animal)
| ^
1 error generated.
Compiler returned: 1
```
No it does not work!
Note:
* We are trying to make a copy of the unique pointer.
* It would not have been unique if it were possible to make a copy!
*
---
A unique_ptr would not be unique if it can be copied!
---
```c++
void speak(std::unique_ptr animal)
{
animal->speak();
}
```
```c++
for (auto&& animal : animals)
{
speak(std::move(animal)); // transfer ownership
}
```
Does this work?
---
```sh []
ASM generation compiler returned: 0
Execution build compiler returned: 0
Program returned: 0
Which animal do you want to create?
bear says roar.
```
It appears to...
Note:
*
---
```c++
void speak(std::unique_ptr animal)
{
animal->speak();
}
```
```c++
for (auto&& animal : animals)
{
speak(std::move(animal)); // transfer ownership
}
```
```c++
// use animals again
for (auto const& animal : animals)
{
animal->speak();
}
```
But how about this?
---
```sh [3]
AddressSanitizer:DEADLYSIGNAL
=================================================================
==1==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x624c8a2ca29f bp 0x7ffcab51e9f0 sp 0x7ffcab51e920 T0)
==1==The signal is caused by a READ memory access.
==1==Hint: address points to the zero page.
#0 0x624c8a2ca29f in Animal::speak() const /app/example.cpp:21:44
#1 0x624c8a2ca29f in main /app/example.cpp:120:17
#2 0x70d22e829d8f (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f) (BuildId: 490fef8403240c91833978d494d39e537409b92e)
#3 0x70d22e829e3f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x29e3f) (BuildId: 490fef8403240c91833978d494d39e537409b92e)
#4 0x624c8a1e1534 in _start (/app/output.s+0x2f534)
==1==Register values:
rax = 0x0000000000000000 rbx = 0x00007ffcab51e920 rcx = 0x00000d9a45901200 rdx = 0x0000000000000002
rdi = 0x00006cd22c809060 rsi = 0x000070d22e7e70d0 rbp = 0x00007ffcab51e9f0 rsp = 0x00007ffcab51e920
r8 = 0x0000000000000028 r9 = 0x00006f122da20000 r10 = 0x00007fffffffff01 r11 = 0x0000000000000001
r12 = 0x00006cf22da20010 r13 = 0x0000000000000000 r14 = 0x00000d9ec5b3c002 r15 = 0x00006cf22da20010
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV /app/example.cpp:21:44 in Animal::speak() const
==1==ABORTING
```
No it does not!
Note:
*
---
```c++
void speak(std::unique_ptr animal)
{
animal->speak();
} // unique_ptr goes out of scope, object destroyed
```
```c++
for (auto&& animal : animals)
{
// transfer ownership to speak()
// replaced by empty unique_ptr in vector
speak(std::move(animal));
}
```
```c++
// use animals again
for (auto const& animal : animals)
{
animal->speak(); // dereference empty unique_ptr ๐ฃ
}
```
---
Vector should keep the ownership!
---
```c++
void speak(Animal const& animal)
{
animal.speak();
}
```
```c++
for (auto const& animal : animals)
{
speak(*animal);
}
```
Keep it simple. Pass by reference.
Note:
*
---
### Best practices
---
* If a function or class only needs to use the object, just pass it by reference.
* Only use std::move() on std::unique_ptr<> to transfer ownership!
---
## Exercises