Reputation: 1111
I am trying to better understand items on a stack and how they are addressed. The article I found here seems to indicate that when MIPS stack is initialized, a fixed amount of memory is allocated and the stack grows down to the stack limit which would appear to be smaller addresses. I would assume that based on this logic a stack overflow would occur when 0x0000 was traversed?
I realize MIPS is big endian, but does that change how the stack grows? I wrote what I believed would be a quick way to observe this on an x86_64 machine, but the stack appears to grow up, as I originally assumed it did.
#include <iostream>
#include <vector>
int main() {
std::vector<int*> v;
for( int i = 0; i < 10; i++ ) {
v.push_back(new int);
std::cout << v.back() << std::endl;
}
}
I'm also confused by the fact that not all of the memory address's do not appear to be contiguous, which makes me think I did something stupid. Could somebody please clarify?
Upvotes: 0
Views: 800
Reputation: 12344
essentially there are 3 types of memory you use in programming: static, dynamic/heap and stack.
Static memory is pre-allocated by the compiler and consist of the constants and variables declared statically in your program.
Heap is the memory which you can freely allocate and release
Stack is the memory which gets allocated for all local variables declared in a function. This is important because every time you call the function a new memory for its variables is allocated. So, that every call to a function will assure that it has its own unique copy of the variables. And every time you return from the function the memory gets freed.
It absolutely does not matter how the stack is managed as soon as it follows the above rules. It is convenient however to have program memory to be allocated in the lower address space and grow up, and the stack to start from a top memory space and grow down. Most systems implement this scheme.
In general there is a stack pointer register/variable which points so the current stack address. when a function gets called it decrease this address by the number of bytes it needs for its variables. when it calls the next function, this new one will start with the new pointer already decreased by the caller. When the function returns it restores the pointer which it started from.
There could be different schemes but as far as I know, mips and i86 follow this one.
And essentially there is only one virtual memory space in the program. This is up to the operating system and/or compiler how to use it. The compiler will split the memory in the logical regions for its own use and handle them, hopefully, according to the calling conventions defined in the platform documents.
So, in our example, v
and i
are allocated on the function stack. cout
is static. every new int
allocate space in heap. v
is not a simple variable but a struct which contains fields which it needs to manage the list. it needs space for all these internals. So, every push_back
modifies those fields to point to the allocated 'int' in some way. push_back() and back() are function calls and allocate their own stacks for internal variables to not interfere with the top function.
Upvotes: 1
Reputation: 61969
The stack on x86 machines also grows downwards. Endianness is unrelated to the direction in which the stack grows.
The stack of a machine has absolutely nothing to do with std::vector<>
. Also, new int
allocates heap memory, so it tells you absolutely nothing about the stack.
In order to see in which direction the stack grows, you need to do something like this:
recursive( 5 );
void recursive( int n )
{
if( n == 0 )
return;
int a;
printf( "%p\n", &a );
recursive( n - 1 );
}
(Note that if your compiler is smart enough to optimize tail recursion, then you will need to tell it to not optimize it, otherwise the observations will be all wrong.)
Upvotes: 2