What is the difference between Stack Pointer and Program Counter?
Asked Answered
O

2

16

As we always know the procedure of executing task by a microprocessor is just executing binary instructions from memory one by one and there is a program counter which holds the address of the next instruction. So this is how processor executes it's tasks if I am not wrong. But there is also another pointer named Stack Pointer which does almost same thing like the program counter. My question is why we need a Stack Pointer to point address of memory(Stack)? Can somebody tell me about the main difference between Stack Pointer and program counter?

Ogbomosho answered 20/8, 2018 at 9:57 Comment(8)
Program is code. Stack is data. Just like program counter points at the current instruction, stack pointer points at the top of the stack. Both are heavily used, both do their own thing. Asking what the difference between them is like asking what the difference between a todo list (program, kind of) and an address book (data, kind of) is - not really answerable sensibly, as they have quite different roles. Know both for what they do separately, and then learn how they cooperate in program execution.Ramentum
@Ramentum Thank you for your comment. But I didn't get it completely. What did you mean by "address book"?Ogbomosho
Nothing particular. I could have said “toaster” and “knife”, really. Two separate concepts cooperating to produce something amazing.Ramentum
If you were to write one of those entry level programming problems where you recurse through a maze, the program counter concept is what chooses what path you will take at the next decision point, the stack is a notepad that keeps track of all prior turns and the stack pointer tells you where the top of stack is so that when you want to back up and take another path you can use that against your notepad (the stack) to choose an uncharted path and let the program counter point you there.Noahnoak
The program counter is you driving the car to the grocery store and making decisions as to which way to turn or go straight. The stack is the list of items you will buy when you get there. Two completely separate things.Noahnoak
The program counter is used to navigate the instructions in executable order, for each instruction it is used to determine the next instruction (simplified answer, pipelines make it more complicated). The stack pointer is a way to have a dynamic and not fixed scratchpad for items the software chooses to use, a stack is not required for all programs, most useful programs yes but technically not required for some percentage of problems that can be solved in software.Noahnoak
If you look at it from a function perspective a stack provides the function its own easily allocatable memory that remains allocated to the function for its duration even when it calls child functions, only when it exits itself does the stack allocation need to be freed (for typical calling convention models).Noahnoak
To the downvoter(s): I think this is a legitimate question. To anyone with any knowledge of CPU architecture or low-level programming it may seem trivial, but that's not a reason for OP to feel bad about asking it! This site exists to help answer people's questions, not to criticise them for asking.Mitziemitzl
N
12
void show ( unsigned int );
unsigned int fun ( unsigned int x )
{
    if(x&1) show(x+1);
    return(x|1);
}

0000200c <fun>:
    200c:   e3100001    tst r0, #1
    2010:   e92d4010    push    {r4, lr}
    2014:   e1a04000    mov r4, r0
    2018:   1a000002    bne 2028 <fun+0x1c>
    201c:   e3840001    orr r0, r4, #1
    2020:   e8bd4010    pop {r4, lr}
    2024:   e12fff1e    bx  lr
    2028:   e2800001    add r0, r0, #1
    202c:   ebfffff5    bl  2008 <show>
    2030:   e3840001    orr r0, r4, #1
    2034:   e8bd4010    pop {r4, lr}
    2038:   e12fff1e    bx  lr

take a simple function, compile and disassemble using one of the arm instruction sets as you tagged arm on this question.

Lets assume a simple serial non-pipe old school type execution.

In order to get here a call (bl in this instruction set, branch and link) happened which modified the program counter to be 0x200C. The program counter is used to fetch that instruction 0xe3100001 then after fetch before execution the program counter is set to point at the next instruction 0x2010. As this program counter is described for this particular instruction set it fetches and stages the next instruction 0xe92d4010 and before execution of the 0x200C instruction the pc contains the value 0x2014, two instructions ahead. For demonstration purposes lets think old school we fetched 0xe3100001 from 0x200C the pc is now set to 0x2010 waiting for execution to complete and for the next fetch cycle.

This first instruction tests the lsbit of r0, the passed in parameter (x), the program counter is not modified so the next fetch reads 0xe92d4010 from 0x2010

The program counter now contains 0x2014, the 0x2010 instruction executes. This instruction is a push it uses the stack pointer. On entry into this function as a programmer we dont care what the exact value of the stack pointer is it could be 0x2468 it could be 0x4010, we dont care. So we will just say it contains the value/address sp_start. This push instruction is using the stack to save two things one is the link register lr, r14, the return address, when this function finishes we want to return to the calling function. And r4 which per the rules of the calling convention used by this compiler for this instruction set says that r4 must be preserved in that if you modify it you must return it to the value it was when called. So we are going to save that on the stack, instead of putting x on the stack and referring to x a number of times in this function, this compiler chooses to save whatever was in r4 (we dont care we just have to save it) and use r4 to hold x for the duration of this function as compiled. any function we call and they call, etc will preserve r4 so when anyone we call returns back to us r4 is whatever it was when we called. So the stack pointer itself changes to sp_start-8 and at sp_start-8 lives the saved copy of r4 and at sp_start-4 the saved copy of lr or r14, we can now modify r4 or lr as we wish we have a scratch pad (the stack) with a saved copy and a pointer for which we can do relative addressing to get at those values, and any calling functions that want to use the stack will grow down from sp_start-8 and not stomp on our scratch pad.

Now we fetch 0x2014 change the pc to 0x2018, this makes a copy of x (passed in in r0) in r4 so we can use it later in the function.

we fetch 0x2018 change the pc to 0x201C. This is a conditional branch so depending on the condition the pc will remain 0x201C or it will change to 0x2028. The flag in question was set during execution of tst r0,#1 the other instructions didnt touch that flag. So we have two paths to follow now, if the condition is not true then we use 0x201C to fetch

fetch from 0x201c change pc to 0x2020, this performs the x=x|1, r0 is the register that contains the return value for the function. This instruction does not modify the program counter

fetch from 0x2020 change the pc to 0x2024, execute the pop. we have not modified the stack pointer (another register that is preserved, you have to put it back where you found it) so sp is equal to sp_start-8 (which is sp+0) right now we read from sp_start-8 and put that value in r4, read from sp_start-4 (which is sp+4) and put that value in lr and add 8 to the stack pointer so it is now set to sp_start, the value it was when we started, put it back the way you found it.

fetch from 0x2024 change the pc to 0x2028. bx lr is a branch to r14 basically it is a return from the function, this modifies the program counter to point at the calling function, the instruction after that calling function called fun(). pc is modified execution continues from that function.

If the bne at 0x2018 did happen then the pc during the execution of bne changes to 0x2028 we fetch from 0x2028 and change the pc to 0x202c before execution. 0x2028 is an add instruction, does not modify the program counter.

we fetch from 0x202c and change the pc to 0x2030 before executing. the bl instruction does modify the program counter and the link register it sets the link register to 0x2030 in this case and the program counter to 0x2008.

the show function executes and returns with a fetch of 0x2030 changing the pc to 0x2034 the orr instruction at 0x2030 happens does not modify the program counter

fetch 0x2034 set pc to 0x2038 execute 0x2034, like 0x2020 this takes the value at address sp+0 and puts it in r4 takes sp+4 and puts it in the lr and then adds 8 to the stack pointer.

fetch 0x2038 set the pc to 0x203c. this does a return puts the callers return address in the program counter causing the next fetch to be from that address.

The program counter is used to fetch the current instruction and to point at the next instruction.

The stack pointer in this case does both jobs it shows where the top of stack is, where the free to use space starts as well as provides a relative address to access items in this function so for the duration of this function after the push the saved r4 register is at sp+0 as this code is designed and the return address at sp+8. if we had several other things on the stack then the stack pointer would have been moved further into the then free space and the items on the stack would be at sp+0, sp+4, sp+8, etc or other values for 8, 16, 32 or 64, bit items.

Some instruction sets and some compiler settings can also setup a frame pointer which is a second stack pointer. One job is to keep track of the boundary between used stack space and free stack space. The other job is to provide a pointer from which to do relative addressing. In this example the stack pointer itself r13 was used for both jobs. But we could tell the compiler and in other instruction sets you dont have a choice, we could have the frame pointer get saved to the stack then frame pointer = stack pointer. then we move the stack pointer in this case 8 bytes and the frame pointer would be used as fp-4 and fp-8 lets say to address the two items on the stack and sp would be used for callee functions to know where the free space starts. A frame pointer is generally a waste of a register, but some implementations use it by default and there are some instruction sets that you dont have a choice, to reach twice as far they will require stack accesses to be hardcoded using a specific register and the offset to be in only one direction add a positive offset or a negative. In arm in this case the push is actually a pseudo instruction for a generic store multiple in which the register r13 is encoded.

Some instruction sets you cant see the program counter it isnt visible to you in any way. Likewise some instruction sets you cannot see the stack pointer it is not visible to you in any way.

Noahnoak answered 20/8, 2018 at 17:29 Comment(1)
This is an awesome answer, can't explain how much i appreciate this. Thanks for every second you have spent when answering this.Handset
M
20

Well, they're fundamentally different concepts. They both contain memory addresses, but remember that both instructions and data are held in (effectively) the same memory space.

The program counter contains the address of the instruction that's currently executing. In fact the CPU uses the value in the program counter to fetch the instructions before it executes them. As instructions are executed, its value is incremented, and if the code branches it will have its value forcibly overwritten.

The stack pointer contains the address of the top of the hardware stack, which is a region of memory that the running code uses as a scratchpad. Values are stored there temporarily, arguments to functions are sometimes put there, and code addresses can be stored there too (for example when one function calls another).

Mitziemitzl answered 20/8, 2018 at 14:29 Comment(4)
Just a (nitpicking) note, not always the PC points to the current instruction: #24092066Simplify
traditionally the pc points to the instruction after (lets say for a number of instruction sets), but with pipelines there are a number of program counters not necessarily called that though, one or more for prefetching/prediction, at least one fake one that is used for pc based math or addressing and that one follows the illusion of the pc value for instruction set (at current instruction, at next instruction, two ahead, etc) computed as needed for instructions that might use it.Noahnoak
it points to the instruction after because it is/was used to actually fetch instructions, so you read one byte maybe that instruction set thats the opcode, early decoding indicates that opcode requires three more bytes, use the pc to address and fetch those three bytes, now the pc is sitting there pointing at the next thing after that instruction waiting for the execution to complete, execution may modify it or not.Noahnoak
Indeed, all of this is true. But given the level of the OP's question, I thought I'd skip some details. :-) I've used some architectures where a deliberate read of the PC does report the actual instruction address, though (internal hardware arithmetic magic) and others where the value depends on the pipeline length. But unless you're actually going to read and rely on the PC value, to all intents and purposes it's a pointer to the current instruction.Mitziemitzl
N
12
void show ( unsigned int );
unsigned int fun ( unsigned int x )
{
    if(x&1) show(x+1);
    return(x|1);
}

0000200c <fun>:
    200c:   e3100001    tst r0, #1
    2010:   e92d4010    push    {r4, lr}
    2014:   e1a04000    mov r4, r0
    2018:   1a000002    bne 2028 <fun+0x1c>
    201c:   e3840001    orr r0, r4, #1
    2020:   e8bd4010    pop {r4, lr}
    2024:   e12fff1e    bx  lr
    2028:   e2800001    add r0, r0, #1
    202c:   ebfffff5    bl  2008 <show>
    2030:   e3840001    orr r0, r4, #1
    2034:   e8bd4010    pop {r4, lr}
    2038:   e12fff1e    bx  lr

take a simple function, compile and disassemble using one of the arm instruction sets as you tagged arm on this question.

Lets assume a simple serial non-pipe old school type execution.

In order to get here a call (bl in this instruction set, branch and link) happened which modified the program counter to be 0x200C. The program counter is used to fetch that instruction 0xe3100001 then after fetch before execution the program counter is set to point at the next instruction 0x2010. As this program counter is described for this particular instruction set it fetches and stages the next instruction 0xe92d4010 and before execution of the 0x200C instruction the pc contains the value 0x2014, two instructions ahead. For demonstration purposes lets think old school we fetched 0xe3100001 from 0x200C the pc is now set to 0x2010 waiting for execution to complete and for the next fetch cycle.

This first instruction tests the lsbit of r0, the passed in parameter (x), the program counter is not modified so the next fetch reads 0xe92d4010 from 0x2010

The program counter now contains 0x2014, the 0x2010 instruction executes. This instruction is a push it uses the stack pointer. On entry into this function as a programmer we dont care what the exact value of the stack pointer is it could be 0x2468 it could be 0x4010, we dont care. So we will just say it contains the value/address sp_start. This push instruction is using the stack to save two things one is the link register lr, r14, the return address, when this function finishes we want to return to the calling function. And r4 which per the rules of the calling convention used by this compiler for this instruction set says that r4 must be preserved in that if you modify it you must return it to the value it was when called. So we are going to save that on the stack, instead of putting x on the stack and referring to x a number of times in this function, this compiler chooses to save whatever was in r4 (we dont care we just have to save it) and use r4 to hold x for the duration of this function as compiled. any function we call and they call, etc will preserve r4 so when anyone we call returns back to us r4 is whatever it was when we called. So the stack pointer itself changes to sp_start-8 and at sp_start-8 lives the saved copy of r4 and at sp_start-4 the saved copy of lr or r14, we can now modify r4 or lr as we wish we have a scratch pad (the stack) with a saved copy and a pointer for which we can do relative addressing to get at those values, and any calling functions that want to use the stack will grow down from sp_start-8 and not stomp on our scratch pad.

Now we fetch 0x2014 change the pc to 0x2018, this makes a copy of x (passed in in r0) in r4 so we can use it later in the function.

we fetch 0x2018 change the pc to 0x201C. This is a conditional branch so depending on the condition the pc will remain 0x201C or it will change to 0x2028. The flag in question was set during execution of tst r0,#1 the other instructions didnt touch that flag. So we have two paths to follow now, if the condition is not true then we use 0x201C to fetch

fetch from 0x201c change pc to 0x2020, this performs the x=x|1, r0 is the register that contains the return value for the function. This instruction does not modify the program counter

fetch from 0x2020 change the pc to 0x2024, execute the pop. we have not modified the stack pointer (another register that is preserved, you have to put it back where you found it) so sp is equal to sp_start-8 (which is sp+0) right now we read from sp_start-8 and put that value in r4, read from sp_start-4 (which is sp+4) and put that value in lr and add 8 to the stack pointer so it is now set to sp_start, the value it was when we started, put it back the way you found it.

fetch from 0x2024 change the pc to 0x2028. bx lr is a branch to r14 basically it is a return from the function, this modifies the program counter to point at the calling function, the instruction after that calling function called fun(). pc is modified execution continues from that function.

If the bne at 0x2018 did happen then the pc during the execution of bne changes to 0x2028 we fetch from 0x2028 and change the pc to 0x202c before execution. 0x2028 is an add instruction, does not modify the program counter.

we fetch from 0x202c and change the pc to 0x2030 before executing. the bl instruction does modify the program counter and the link register it sets the link register to 0x2030 in this case and the program counter to 0x2008.

the show function executes and returns with a fetch of 0x2030 changing the pc to 0x2034 the orr instruction at 0x2030 happens does not modify the program counter

fetch 0x2034 set pc to 0x2038 execute 0x2034, like 0x2020 this takes the value at address sp+0 and puts it in r4 takes sp+4 and puts it in the lr and then adds 8 to the stack pointer.

fetch 0x2038 set the pc to 0x203c. this does a return puts the callers return address in the program counter causing the next fetch to be from that address.

The program counter is used to fetch the current instruction and to point at the next instruction.

The stack pointer in this case does both jobs it shows where the top of stack is, where the free to use space starts as well as provides a relative address to access items in this function so for the duration of this function after the push the saved r4 register is at sp+0 as this code is designed and the return address at sp+8. if we had several other things on the stack then the stack pointer would have been moved further into the then free space and the items on the stack would be at sp+0, sp+4, sp+8, etc or other values for 8, 16, 32 or 64, bit items.

Some instruction sets and some compiler settings can also setup a frame pointer which is a second stack pointer. One job is to keep track of the boundary between used stack space and free stack space. The other job is to provide a pointer from which to do relative addressing. In this example the stack pointer itself r13 was used for both jobs. But we could tell the compiler and in other instruction sets you dont have a choice, we could have the frame pointer get saved to the stack then frame pointer = stack pointer. then we move the stack pointer in this case 8 bytes and the frame pointer would be used as fp-4 and fp-8 lets say to address the two items on the stack and sp would be used for callee functions to know where the free space starts. A frame pointer is generally a waste of a register, but some implementations use it by default and there are some instruction sets that you dont have a choice, to reach twice as far they will require stack accesses to be hardcoded using a specific register and the offset to be in only one direction add a positive offset or a negative. In arm in this case the push is actually a pseudo instruction for a generic store multiple in which the register r13 is encoded.

Some instruction sets you cant see the program counter it isnt visible to you in any way. Likewise some instruction sets you cannot see the stack pointer it is not visible to you in any way.

Noahnoak answered 20/8, 2018 at 17:29 Comment(1)
This is an awesome answer, can't explain how much i appreciate this. Thanks for every second you have spent when answering this.Handset

© 2022 - 2024 — McMap. All rights reserved.