The stack is a block of memory that is structured as a last-in first-out buffer, like a stack of cards, which the CPU uses to keep track of
return addresses for
calls to functions or subroutines.
Code:
#include <stdio.h>
static int n;
void function1(void)
{
printf("One\n");
n = 1;
}
void function2(void)
{
printf("Two\n");
n = 2;
}
I've defined two functions here that both call printf(). When the CPU executes either of these functions, it needs to transfer control to the printf() function. This is done using a
call. When the CPU finishes executing the printf() function code, it needs to return to the statement that immediately follows the place it was called from. If printf() was called from inside function1(), it needs to return to the statement n = 1. If printf() was called from inside function2(), it needs to return to the statement n = 2. This is done using
call and
return instructions.
In function1(), printf() is invoked using a
call instruction. As part of executing the call instruction, the CPU "pushes" the address of the following code (the code that implements n = 1) onto the top of the stack, then jumps to the printf() function code. When the printf() function code is finished, it executes a
return or
ret instruction. The CPU "pops" the address that's at the top of the stack and puts that into the program counter, so execution continues from that point.
In function2(), printf() is invoked in the same way, but the return address is the address of the code that implements n = 2, and that's where execution continues when printf() executes its return instruction.
Pushing and popping are two complementary operations that access the stack structure through a register called the stack pointer (SP), which is part of the CPU/MCU core. The stack pointer typically points to the data item at the top of the stack. A push increments SP then stores the return address at the location now pointed to by SP; a pop reads the location pointed to by SP and copies it into the program counter then decrements SP.
There are architecture-specific variations on the stack implementation. On some devices, SP points just past the top item, so a push does a store then increment, and a pop does a decrement then read. Also, stacks on most architectures actually build downwards in memory, not upwards. Also, in a few architectures, the value pushed onto the stack is not the exact return address; it may be one or two less than the return address. Those are the main variations.
This system allows any function or subroutine to be called from anywhere. When the function or subroutine ends with a return instruction, execution continues from the place immediately following the call, regardless of where the function was called from.
Because functions can call other functions, which can call other functions, and so on, the stack is a list of nested return addresses.
In general C implementations, the stack is also used to pass parameters to functions, and for local variables used by functions, since it's a convenient place for temporary storage. The stack is normally placed in an area of main RAM that has been reserved for this purpose, and return addresses, parameters, and temporary variables can all be accessed in memory using one or more kinds of
stack-relative addressing.
But the 8-bit PIC family doesn't have a proper stack in main RAM. The stack is actually internal to the core, and can't be accessed directly. Ditto the stack pointer. In fact the only way to access it is through the call instruction and various versions of the return instruction. (It is also used when an interrupt is accepted.) This means that function parameters and temporary variables cannot be placed on the stack and must be allocated in main RAM by the compiler, and it means that recursion is impossible without special tricks.
The worst thing about the 8-bit PICs' stack architecture is the size of the stack. The very smallest ones only have two levels! That means that function A can call function B, then function B can call function C, but function C can't call any functions! In other words, the device only supports two levels of function nesting. This is acceptable for very simple programs and programs that use tricks to work around this limitation, but it's a big nasty limitation. Even with the larger 8-bit PIC variants you have to be aware of the limited stack size.
If you're working from a C background, you probably haven't even considered that there might be a limitation on the nesting depth of functions. One function can call another function, and that function can call other functions, and those functions can call more functions, and so on; the nesting depth can be pretty large. Not so when you're working with a PIC.
The number of instructions is the number of different assembly language instructions that the device supports. It's not very important really; manufacturers with competing microcontroller architectures like to boast about how many instructions their devices support, or how few instructions "you'll have to learn", but it's all hot air really. Other factors are much more important.
That book would be good, I'm sure. But you don't need to buy a book to learn about the library functions available. They are documented with the compiler (since they are actually part of the supporting resources for the compiler).
Different compilers use different approaches to library support. The standard, free (for the non-optimising version) 8-bit PIC compiler, XC8, which used to be Hi-Tech C for PIC, has relatively few library functions; if you want to initialise a peripheral, or modify an I/O pin, you have to access the registers directly, i.e. you have to "show your work".
Another PIC compiler provides dozens of special functions for these things; this can make your program look tidy and compact, but it hides the detail, can introduce invisible bugs, and makes for a lot of work if you want to port the code to a different compiler.
That book uses the MPLAB X IDE (and presumably the XC8 compiler), which is Microchip's official development environment, and that's what I would recommend.