Let's imagine a computer that has memory and four registers. The registers will consist of two general purpose scratchpad registers (A and B), a program counter(PC) and a stack pointer(SP). Let's start with a simple program source text consisting of two files, prog.c and sub.c. Here is prog.c
extern int a;
int c();
void main(){
int d = 5;
a = 23;
d = c();
}
Compiler
This can be compiled. The compiler is a translator that takes the source
code and turns it into code - an object file - for the linker to use. Call
the object file prog.obj. This code must keep any names that will be visible
outside the object file. So it would look something like this.
Now imagine waiting several days before writing the other source file; call it other.cpp.
int a;
int c(){
return a + 3;
}
We compile this, and we get an object file named other.obj that would
contain code like this.
| Address | Contents (symbolic form) | Comment |
| 0 | 7 | Address to start program |
| 1 | 5 | Size of stack |
| 2 | 0 | a variable |
| 3 | MOVE A,2 | move value at address 2 to A |
| 4 | ADD A,'3' | |
| 5 | PUSH A | |
| 6 | JUMP (SP-1) | return |
| 7 | PUSH 5 | Set up variable local to function |
| 8 | MOVE A,'23' | |
| 9 | MOVE 2,A | Finishes a = 23 |
| 10 | PUSH PC+2 | Push return address |
| 11 | JUMP 3 | Call of function |
| 12 | POP A | |
| 13 | POP | |
| 14 | MOVE (SP),A | Finishes d = c(5) |
| 15 | JUMP (SP-1) | Return to system |
Loader
The loader is responsible for loading a memory image of the executable
file and starting the program. The program would not be loaded at address
0, so the program might look like this in memory. The loader might
also be responsible for setting up the stack. I have put the stack in addresses
1014-1018.
| Address | Contents |
| 1000 | 0 |
| 1001 | MOVE A,2 |
| 1002 | ADD A,'3' |
| 1003 | PUSH A |
| 1004 | JUMP (SP-1) |
| 1005 | PUSH 5 |
| 1006 | MOVE A,'23' |
| 1007 | MOVE 2,A |
| 1008 | PUSH PC+2 |
| 1009 | JUMP 3 |
| 1010 | POP A |
| 1011 | POP |
| 1012 | MOVE (SP),A |
| 1013 | JUMP (SP-1) |
| 1014 | 200 |
| 1015 | 0 |
| 1016 | 0 |
| 1017 | 0 |
| 1018 | 0 |
The loader essentially does a function call to branch to the start location in the program, and then we have run-time.
Run-time
Each instruction changes our mythical machine's state - the
contents of its registers and memory. For example, if we take a snapshot
of the program and registers just after the instruction at address 1007
(which finishes a=23) is executed, we would see
| Address | Contents |
| 1000 | 23 |
| 1001 | MOVE A,2 |
| 1002 | ADD A,'3' |
| 1003 | PUSH A |
| 1004 | JUMP (SP-1) |
| 1005 | PUSH 5 |
| 1006 | MOVE A,'23' |
| 1007 | MOVE 2,A |
| 1008 | PUSH PC+2 |
| 1009 | JUMP 3 |
| 1010 | POP A |
| 1011 | POP |
| 1012 | MOVE (SP),A |
| 1013 | JUMP (SP-1) |
| 1014 | 200 |
| 1015 | 5 |
| 1016 | 0 |
| 1017 | 0 |
| 1018 | 0 |
Registers:
| A | B | PC | SP |
| 23 | 0 | 1008 | 1015 |