It seems to me that the stack is essentially used for "proceedural" operations..ie push 4, followed by 4, add and pop 8 or 16 or whatever the operation is deemed to be. It would make no sense to store a string this way, ( well I hope so) that you would need to arbitrarily access. Contrast this with a heap, in which data is stored by address and **can** be arbitrarily accessed. Now, I supposed that a language like Obj-C does use the stack from time to time, but only in the constraint of some procedural endeavor.
Well, I hope this makes some kind of sense, as it has been exciting to discover something new.
This is a little off. What you are discussing is a means of writing a simple stack-based calculator with RPN data entry, which is an interesting task, but not exactly the way stack frames work during program execution. Times like these I wish I had some sort of digitizing whiteboard, because a few drawings would help describe this quite a bit. I'll try to make do with words and perhaps a few ascii illustrations.
Some of this is implementation-specific, so I'll try to speak in somewhat broad terms... a stack frame will generally contain a return address to the caller (where execution will begin once the current function/subroutine completes), the arguments passed to the subroutine, the local data for the subroutine, and normally some space to store the return value of the function if there is one. There are a few additional addresses involved that may be stored in a special register if the microarchitecture supports it. One is the base of the stack. This is generally unimportant to the current subroutine. The other is the current stack pointer, which is the position in memory where the current stack frame begins. The stack pointer for the previous stack frame must also be kept and restored when the current stack frame is popped.
Where arguments are placed in memory, where local storage is kept in memory, etc. are all based on an offset from the stack pointer, rather than an absolute position in memory. This means that when a function is called, a particular variable will always be located 32 bytes past the stack pointer. This way, no matter where the stack frame is in memory, a simple calculation can be used to find the address of this variable.
When a new subroutine is called, the stack pointer will be set just after the current stack frame (perhaps a little further for alignment purposes based on the machine), the arguments will be stored in the appropriate pre-determined positions offset from the stack pointer, the previous stack pointer and next address will be stored in a position offset from the new stack pointer, and the new subroutine can begin execution. Once it has finished, all of this is "popped", insofar as the stack pointer is set back to the proper position at the beginning of the previous stack frame. This process repeats at the end of each subroutine's execution, restoring the state to the previous stack frame.
In your post you mentioned storing a string in terms of a list of characters. In the case of a C-style string, you can certainly store the array of characters on the stack. There's simply a fixed number of bytes equaling the length of the character array set aside, with the base of the array starting at a fixed offset from the stack pointer. The "type" of data really isn't too important when it comes to laying out a stack frame, just the size.
It may not be of particular interest at this point, but depending on the machine, alignment in memory may be important. This might mean that there's some unused bytes on the stack that are simply padding. So:
Code:
void mySub() {
char a;
int b;
char c;
short d;
double e;
char f;
void *g;
//...
}
We'll assume (even if it's not always the case) that the compiler wants these variables laid out in the order the programmer declared them, and the machine requires no alignment for a char (1-byte), 2-byte alignment for a short (2-bytes), 4-byte alignment for int,float(4-byte), and 8 byte alignment for double, and *s(8-byte, assuming pointers are 8-byte on this machine). So, things might be laid out like this:
sp+0 = char a (uses 1 byte, next free is sp+1)
sp+4 = int b (needs 4-byte alignment, uses 4 bytes, next free is sp+8)
sp+8 = char c (next free is sp+9)
sp+10 = short d (needs 2-byte alignment, uses 2 bytes, next free is sp+13)
sp+16 = double e (needs 8-byte alignment, uses 8 bytes, next free is sp+24)
sp+24 = char f (next free is sp+25)
sp+32 = void *g (needs 8-byte alignment, uses 8 bytes, next free is sp+40)
So we have 25 bytes worth of data, but we used up 40 bytes of space to store them so they are aligned. If we were really worried about stack space, and the order the variables were laid out didn't matter, we could always arrange things from the items with the smallest size to items of the largest size, which would leave a lot less space for padding. In this case we'd have 3 chars, and need just 1 byte for padding before the short, 2 bytes of padding after the short before the int, the 4 bytes of padding before the double and void *. This would be only 7 bytes of padding instead of 15, and in some cases. This is possible with singletons, but if you have a structure that needs an absolute layout in memory, this kind of optimization is not possible. The programmer might optimize for less padding, but the compiler cannot.
All of that alignment business was a bit off topic, but at the point we're talking about how the stack frames are being laid out in memory I thought it might be worth mentioning.
-Lee