Remember how I told you that memory is basically just a giant array of bytes with addresses at various offsets?
That's true, but it also has some additional structure. In particular, memory is divided into two main regions: the stack and the heap. We'll cover the heap later.
The stack is where local variables are stored. When a function is called, a new stack frame is created in memory to store the function's parameters and local variables. When the function returns, its entire stack frame is deallocated.
The stack is aptly named: it is a stack (the "Last In, First Out" data structure) of memory frames. Each time a function is called, a new frame is pushed onto the stack. When the function returns, its frame is popped off the stack.
Take a look at this example function:
void create_typist(int uses_nvim) {
int wpm = 150;
char name[4] = {'t', 'e', 'e', 'j'};
}
Say we call create_typist(1). Before the call, our stack memory might look like this, with the next memory address to be used 0x0004:

Once called, the stack pointer is moved to make room for:

and the local variables are stored in the stack frame:

When the function returns, the stack frame is deallocated by resetting the stack pointer to where the frame began.
See how with each successive nested function call (printMessageOne, which calls printMessageTwo, which calls printMessageThree) the memory addresses allocate more and more space?
The offsets printed by printStackPointerDiff should now be different from before. The printStackPointerDiff() calls should remain at the start of each function.
The print message functions should not call each other because that creates a new stack frame on top of the existing one. They should be called sequentially from the main function.