* Render the book in PDF using `pandoc` and LaTeX. * Fix installs. * Go the apt-get route * Another attempt * Avoid installing twice. * Re-order. * Add more packages. * Minimise deps. Fix link checker. * Missing package. * Missing package. * Missing package. * More packages. * Missing package. * Missing package. * More packages... * Remove. * Fix link checker. * Fix link checker. * Fix path. * Add subtitle. * Avoid running over the right margin. * Avoid running over the right margin. * Formatting
2.5 KiB
Memory layout
We’ve looked at ownership and references from an operational point of view—what you can and can’t do with them. Now it’s a good time to take a look under the hood: let’s talk about memory.
Stack and heap
When discussing memory, you’ll often hear people talk about the
stack and the heap.
These are two different memory regions used by programs to store
data.
Let’s start with the stack.
Stack
The stack is a LIFO (Last In, First
Out) data structure.
When you call a function, a new stack frame is added on
top of the stack. That stack frame stores the function’s arguments,
local variables and a few “bookkeeping” values.
When the function returns, the stack frame is popped off the stack1.
+-----------------+
| frame for func1 |
+-----------------+
|
| func2 is
| called
v
+-----------------+
| frame for func2 |
+-----------------+
| frame for func1 |
+-----------------+
|
| func2
| returns
v
+-----------------+
| frame for func1 |
+-----------------+
From an operational point of view, stack allocation/de-allocation is
very fast.
We are always pushing and popping data from the top of the stack, so we
don’t need to search for free memory. We also don’t have to worry about
fragmentation: the stack is a single contiguous block of memory.
Rust
Rust will often allocate data on the stack.
You have a u32 input argument in a function? Those 32 bits
will be on the stack.
You define a local variable of type i64? Those 64 bits will
be on the stack.
It all works quite nicely because the size of those integers is known at
compile time, therefore the compiled program knows how much space it
needs to reserve on the stack for them.
std::mem::size_of
You can verify how much space a type would take on the stack using
the std::mem::size_of
function.
For a u8, for example:
// We'll explain this funny-looking syntax (`::<u8>`) later on.
// Ignore it for now.
assert_eq!(std::mem::size_of::<u8>(), 1);1 makes sense, because a u8 is 8 bits long, or 1
byte.
If you have nested function calls, each function pushes its data onto the stack when it’s called but it doesn’t pop it off until the innermost function returns. If you have too many nested function calls, you can run out of stack space—the stack is not infinite! That’s called a stack overflow.↩︎