Addressing stack variables - c++

As far as I understand, stack variables are stored using an absolute offset to the stack frame pointer.
But how are those variables addressed later?
Consider the following code:
#include <iostream>
int main()
{
int a = 0;
int b = 1;
int c = 2;
std::cout << b << std::endl;
}
How does the compiler know where to find b? Does it store its offset to the stack frame pointer? And if so, where is this information stored? And does that mean that int needs more than 4 bytes to be stored?

The location (relative to the stack pointer) of stack variables is a compile-time constant.
The compiler always knows how many things it's pushed to the stack since the beginning of the function and therefore the relative position of any one of them within the stack frame. (Unless you use alloca or VLAs1.)
On x86 this is usually achieved by addressing relative to the ebp or esp registers, which are typically used to represent the "beginning" and "end" of the stack frame. The offsets themselves don't need to be stored anywhere as they are built into the instruction as part of the addressing scheme.
Note that local variables are not always stored on the stack.
The compiler is free to put them wherever it wants, so long as it behaves as if it were allocated on the stack.
In particular, small objects like integers may simply stay in a register for the full duration of their lifespans (or until the compiler is forced to spill them onto the stack), constants may be stored in read-only memory, or any other optimization that the compiler deems fit.
Footnote 1: In functions that use alloca or a VLA, the compiler will use a separate register (like RBP in x86-64) as a "frame pointer" even in an optimized build, and address locals relative to the frame pointer, not the stack pointer. The amount of named C variables is known at compile time, so they can go at the top of the stack frame where the offset from them to the frame pointer is constant. Multiple VLAs can just work as pointers to space allocated as if by alloca. (That's one typical implementation strategy).

Related

Function return pointers [duplicate]

Recently, I came across this question in an interview: How can we determine how much storage on the stack a particular function is consuming?
The "stack" is famously an implementation detail of the platform that is not inspectable or in any way queryable from within the language itself. It is essentially impossible to guarantee within any part of a C or C++ program whether it will be possible to make another function call. The "stack size", or maybe better called "function call and local variable storage depth", is one of the implementation limits whose existence is acknowledged by the language standard but considered out of scope. (E.g. for C++ see [implimits], Annex B.)
Individual platforms may offer APIs to allow programs to introspect the platform limitations, but neither C nor C++ specify that or how this should be possible.
Exceeding the implementation-defined resource limits leads to undefined behaviour, and you cannot know whether you will exceed the limits.
It's completely implementation defined - the standard does not in any way impose requirements on the possible underlying mechanisms used by a program.
On a x86 machine, one stack frame consists of a return address (4/8 byte), parameters and local variables.
The parameters, if e.g. scalars, may be passed through registers, so we can't say for sure whether they contribute to the storage taken up. The locals may be padded (and often are); We can only deduce a minimum amount of storage for these.
The only way to know for sure is to actually analyze the assembler code a compiler generates, or look at the absolute difference of the stack pointer values at runtime - before and after a particular function was called.
E.g.
#include <iostream>
void f()
{
register void* foo asm ("esp");
std::cout << foo << '\n';
}
int main()
{
register void* foo asm ("esp");
std::cout << foo << '\n';
f();
}
Now compare the outputs. GCC on Coliru gives
0x7fffbcefb410
0x7fffbcefb400
A difference of 16 bytes. (The stack grows downwards on x86.)
As stated by other answers, the program stack is a concept which is not specified within the language itself. However with a knowledge how typical implementation works, you can assume that the address of the first argument of a function is the beginning of its stack frame. The address of the first argument of a next called function is the beginning of the next stack frame. So, they probably wanted to see a code like:
void bar(void *b) {
printf("Foo stack frame is around %lld bytes\n", llabs((long long)b - (long long)&b));
}
void foo(int x) {
bar(&x);
}
The size increase of the stack, for those implementations that use a stack, is:
size of variables that don't fit in the available registers
size of variables declared in the function declared upfront that live for the life of the function
size of other local variables declared along the way or in statement blocks
the maximum stack size used by functions called by this function
everything above * the number of recursive calls
size of the return address
Return Address
Most implementations push the return address on the stack before any other data. So this address takes up space.
Available Registers
Some processors have many registers; however, only a few may be available for passing variables. For example, if the convention allows for 2 variables but there are 5 parameters, 3 parameters will be placed on the stack.
When large objects are passed by value, they will take up space on the stack.
Function Local Variables
This is tricky to calculate, because variables may be pushed onto the stack and then popped off when not used.
Some variables may not be pushed onto the stack until they are declared. So if a function returns midway through, it may not use the remaining variables, so the stack size won't increase for those variables.
The compiler may elect to use registers to hold values or place constants directly into the executable code. In this case, they don't add any length to the stack.
Calling Other Functions
The function may call other functions. Each called function may increase the amount of data on the stack. Those functions that are called may call other functions, and so on.
This again, depends on the snapshot in time of the execution. However, one can produce an approximate maximum increase of the stack by the other called functions.
Recursion
As with calling other functions, a recursive call may increase the size of the stack. A recursive call at the end of the function may increase the stack more than a recursive call near the beginning.
Register Value Saving
Sometimes, the compiler may need more space for data than the allocated registers allow. Thus the compiler may push variables on the stack.
The compiler may push registers on the stack for convenience, such as swapping registers or changing the value's order.
Summary
The exact size of stack space required for a function is very difficult to calculate and may depend on where the execution is. There are many items to consider in stack size calculation, such as parameter quantity and size as well as any other functions called. Due to the variability, most stack size measurements are based on a maximum size, or worst case size. Stack allocation is usually based on the worst case scenario.
For an interview question, I would mention all of the above, which usually makes the interviewer want to move on to the next question quickly.

Confusion about stack growth and addressing

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?
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.)
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.

C++ how are variables accessed in memory?

When I create a new variable in a C++ program, eg a char:
char c = 'a';
how does C++ then have access to this variable in memory? I would imagine that it would need to store the memory location of the variable, but then that would require a pointer variable, and this pointer would again need to be accessed.
See the docs:
When a variable is declared, the memory needed to store its value is
assigned a specific location in memory (its memory address).
Generally, C++ programs do not actively decide the exact memory
addresses where its variables are stored. Fortunately, that task is
left to the environment where the program is run - generally, an
operating system that decides the particular memory locations on
runtime. However, it may be useful for a program to be able to obtain
the address of a variable during runtime in order to access data cells
that are at a certain position relative to it.
You can also refer this article on Variables and Memory
The Stack
The stack is where local variables and function parameters reside. It
is called a stack because it follows the last-in, first-out principle.
As data is added or pushed to the stack, it grows, and when data is
removed or popped it shrinks. In reality, memory addresses are not
physically moved around every time data is pushed or popped from the
stack, instead the stack pointer, which as the name implies points to
the memory address at the top of the stack, moves up and down.
Everything below this address is considered to be on the stack and
usable, whereas everything above it is off the stack, and invalid.
This is all accomplished automatically by the operating system, and as
a result it is sometimes also called automatic memory. On the
extremely rare occasions that one needs to be able to explicitly
invoke this type of memory, the C++ key word auto can be used.
Normally, one declares variables on the stack like this:
void func () {
int i; float x[100];
...
}
Variables that are declared on the stack are only valid within the
scope of their declaration. That means when the function func() listed
above returns, i and x will no longer be accessible or valid.
There is another limitation to variables that are placed on the stack:
the operating system only allocates a certain amount of space to the
stack. As each part of a program that is being executed comes into
scope, the operating system allocates the appropriate amount of memory
that is required to hold all the local variables on the stack. If this
is greater than the amount of memory that the OS has allowed for the
total size of the stack, then the program will crash. While the
maximum size of the stack can sometimes be changed by compile time
parameters, it is usually fairly small, and nowhere near the total
amount of RAM available on a machine.
Assuming this is a local variable, then this variable is allocated on the stack - i.e. in the RAM. The compiler keeps track of the variable offset on the stack. In the basic scenario, in case any computation is then performed with the variable, it is moved to one of the processor's registers and the CPU performs the computation. Afterwards the result is returned back to the RAM. Modern processors keep whole stack frames in the registers and have multiple levels of registers, so it can get quite complex.
Please note the "c" name is no more mentioned in the binary (unless you have debugging symbols). The binary only then works with the memory locations. E.g. it would look like this (simple addition):
a = b + c
take value of memory offset 1 and put it in the register 1
take value of memory offset 2 and put in in the register 2
sum registers 1 and 2 and store the result in register 3
copy the register 3 to memory location 3
The binary doesn't know "a", "b" or "c". The compiler just said "a is in memory 1, b is in memory 2, c is in memory 3". And the CPU just blindly executes the commands the compiler has generated.
C++ itself (or, the compiler) would have access to this variable in terms of the program structure, represented as a data structure. Perhaps you're asking how other parts in the program would have access to it at run time.
The answer is that it varies. It can be stored either in a register, on the stack, on the heap, or in the data/bss sections (global/static variables), depending on its context and the platform it was compiled for: If you needed to pass it around by reference (or pointer) to other functions, then it would likely be stored on the stack. If you only need it in the context of your function, it would probably be handled in a register. If it's a member variable of an object on the heap, then it's on the heap, and you reference it by an offset into the object. If it's a global/static variable, then its address is determined once the program is fully loaded into memory.
C++ eventually compiles down to machine language, and often runs within the context of an operating system, so you might want to brush up a bit on Assembly basics, or even some OS principles, to better understand what's going on under the hood.
Lets say our program starts with a stack address of 4000000
When, you call a function, depending how much stack you use, it will "allocate it" like this
Let's say we have 2 ints (8bytes)
int function()
{
int a = 0;
int b = 0;
}
then whats gonna happen in assembly is
MOV EBP,ESP //Here we store the original value of the stack address (4000000) in EBP, and we restore it at the end of the function back to 4000000
SUB ESP, 8 //here we "allocate" 8 bytes in the stack, which basically just decreases the ESP addr by 8
so our ESP address was changed from
4000000
to
3999992
that's how the program knows knows the stack addresss for the first int is "3999992" and the second int is from 3999996 to 4000000
Even tho this pretty much has nothing to do with the compiler, it's really important to know because when you know how stack is "allocated", you realize how cheap it is to do things like
char my_array[20000];
since all it's doing is just doing sub esp, 20000 which is a single assembly instruction
but if u actually use all those bytes like memset(my_array,20000) that's a different history.
how does C++ then have access to this variable in memory?
It doesn't!
Your computer does, and it is instructed on how to do that by loading the location of the variable in memory into a register. This is all handled by assembly language. I shan't go into the details here of how such languages work (you can look it up!) but this is rather the purpose of a C++ compiler: to turn an abstract, high-level set of "instructions" into actual technical instructions that a computer can understand and execute. You could sort of say that assembly programs contain a lot of pointers, though most of them are literals rather than "variables".

c/c++ allocate on stack

I was reading [1) about stack pointers and the need of knowing both ebp (start of the stack for the function) and esp (end). The article said that you need to know both because the stack can grow, but I don't see how this can be possible in c/c++. (Im not talking about another function call because to my mind this would make the stack grow, do some stuff, then recursively be popped and back to state before call)
I have done a little bit of research and only saw people saying that new allocates on the heap. But the pointer will be a local variable, right ? And this is known at compile time and reserved in the stack at the time the function is called.
I started to think that maybe with loops you have an uncontrolled number of local variables
int a;
for (int i = 0; i < n; ++i)
int b = i + 3;
but no, this doesn't allocate n times b, and only 1 int is reserved just as it is for a.
So... any example ?
[1): http://en.wikibooks.org/wiki/X86_Disassembly/Functions_and_Stack_Frames
You can allocate memory on the stack with alloca function from stdlib. I don't recommend to use this function in production code. It's to easy to corrupt your stack or get stack overflow.
The use of EBP is more for convenience. It is possible to just use ESP. The problem with that is, as parameters to functions are pushed onto the stack, the address relative to ESP of all the variables and parameters changes.
By setting EBP to a fixed, known position on the stack, usually between the function parameters and the local variables, the address of all these elements remains constant relative to EBP throughout the lifetime of the function. It can also help with debugging, as the value of ESP at the end of the function should equal the value of EBP.
The only way I know of to grow the stack in an indeterminate way at compile time, is to use alloca repeatedly.
A pointer is indeed allocated on the stack. And the size is usually 4 or 8 bytes on 32 and 64bit architectures respectively. So you know the size of a pointer statically, at compile time, and you can keep them on the stack if you choose to do so.
This pointer can point to free store, and you can allocate memory to it dynamically - without having to know the size beforehand. Moreover, it is usually a good idea to keep stack frames empty, and compilers will even "enforce" this with (adjustable) limits. MSVC has 1MB if I recall correctly.
So no, there is no way you can create a stack frame of size that is unknown at compile time. Your stack frame of the code you posted will have room for 3 integers (a, b, i). (And possibly some padding, shadow space, etc, not relevant.) (It is TECHNICALLY possible to extend the size of stackframes at run time but you just don't want to do that almost never.)

Where is the memory address of a c/c++ pointer held?

If I do the following:
int i, *p = &i;
int **p2p = &p;
I get the address of i (on the stack) and pass it to p, I then get the address of p (on the stack) and pass that to p2p.
My question is, we know the value of i is kept in the memory address p and so forth but how does the operating system know where that address is? I suppose their addresses are being kept organized in the stack. Is each declared variable (identifier) treated like an offset from the current position of the stack? What about global variables, how does the operating system and/or the compiler deal with addressing these during execution? How does the OS and the compiler work to 'remember' the addresses of each identifier without using memory? Are all variables just entered (pushed) in-order into the stack and their names are replaced with their offsets? if so, what about conditional code that can change the order of declaration?
I used to be an assembly language programmer, so I know the answer for the CPUs that I used to work with. The main point is that one of the CPU's registers is used as the stack pointer, called SP (or esp on x86 CPUs these days). The compiler references the variables (i, p and p2p in your case) relative to SP. In other words, the compiler decides what the offset of each variable should be from SP, and produces machine code accordingly.
Conceptually, data can be stored in 4 different areas of memory, depending on its scope and whether it's constant or variable. I say "conceptually" because memory allocation is very platform-dependent, and the strategy can become extremely complicated in order to wring out as much efficiency as modern architectures can provide.
It's also important to realize that, with few exceptions, the OS doesn't know or care where variables reside; the CPU does. It's the CPU that processes each operation in a program, calculates addresses, and reads and writes memory. In fact, the OS itself is just a program, with its own variables, that the CPU executes.
In general, the compiler decides which type of memory (e.g. stack, heap, register) to allocate for each variable. If it chooses a register, it also decides which register to allocate. If it chooses another type of memory, it calculates the variable's offset from the beginning of that section of memory. It creates an "object" file that still references these variables as offsets from the start of their sections.
Then the linker reads each of the object files, combines and sorts their variables into the appropriate sections, and then "fixes up" the offsets. (That's the technical term. Really.)
Constant data
What is it?
Since this data never changes it's typically stored alongside the program itself in an area of read-only memory. In an embedded system, like a microwave oven, this may be in (traditionally inexpensive) ROM instead of (more expensive) RAM. On a PC, it's a segment of RAM that's been designated as ready-only by the OS, so an attempt to write to it will cause a segmentation fault and stop the program before it "illegally" changes something it shouldn't.
How is it accessed?
The compiler typically references constant data as an offset from the beginning of the constant data segment. It's the linker that knows where the segment actually resides, so it fixes the starting address of the segment.
Global and static data
What is it?
This data must be available throughout the entire life of the running program, so it must reside on a "heap" of memory that's been allocated to the program. Since the data can change, the heap cannot reside in read-only memory as does constant data; it must reside in writable RAM.
How is it accessed?
The CPU accesses global and static data in the same way as constant data: it's referenced as an offset from the start of the heap, with the heap's starting address fixed by the linker.
Local data
What is it?
These are variables that exist only while an enclosing function is active. They reside in RAM that is allocated dynamically and then returned to the system immediately when the function exits. Conceptually, they're allocated from a "stack" that grows as functions are called and create variables; it shrinks as each function returns. The stack also holds the "return address" for each function call: the CPU records its current location in the program and "pushes" that address onto the stack before it calls a function; then, when the function returns, it "pops" the address off the stack so it can resume from wherever it was before the function call. But again, the actual implementation depends on the architecture; the important thing is to remember that a function's local data becomes invalid, and should therefore never be referenced, after the function returns.
How is it accessed?
Local data is accessed by its offset from the beginning of the stack. The compiler knows the next available stack address when it enters a function, and ignoring some esoteric cases, it also knows how much memory it needs for local variables, so it moves the "stack pointer" to skip over that memory. It then references each local variable by calculating its address within the stack.
Registers
What are they?
A register is a small area of memory within the CPU itself. All calculations occur within registers, and register operations are very fast. The CPU contains a relatively small number of registers, so they're a limited resource.
How are they accessed?
The CPU can access registers directly, which makes register operations very quick. The compiler may choose to allocate a register to a variable as an optimization, so it won't need to wait while it fetches or writes the data to RAM. Generally, only local data is assigned to registers. For example, a loop counter may reside in a register, and the stack pointer is itself a register.
The answer to your question:
When you declare a variable on the stack, the compiler calculates its size and assigns memory for it, beginning at the next available location on the stack. Let's look at your example, making the following assumptions:
1. When the function is called, SP is the next available address in the stack, which grows downward.
2. sizeof(int) = 2 (just to make it different from the size of a pointer).
3. sizeof(int *) = sizeof(int **) = 4 (that is, all pointers are the same size).
Then: int i, *p = &i;
int **p2p = &p;
You're declaring 3 variables:
i: Addr = SP, size = 2, contents = uninitialized
p: Addr = SP-2, size = 4, contents = SP (address of i)
p2p: Addr = SP-6, size = 4, contents = SP-2 (address of p)
The operating system is not concerned about the addresses your programs use. Whenever a system call is issued that needs to use a buffer within your address space, your program provides the address of the buffer.
Your compiler presents a stack frame for each of your functions.
push ebp
mov ebp,esp
Then, any function parameters or local variables can be addressed relative to the value of the EBP register which is then the base address of that stack frame. This is taken care of by the compiler via reference tables specific to your compiler.
Upon exiting the function, the compiler tears down the stack frame:
mov esp,ebp
pop ebp
At low level, the CPU only works with literal BYTE/WORD/DWORD/etc values and addresses (that are the same, but used differently).
A memory address that's needed is either stored in a named buffer (e.g. global var) that the compiler substitutes with its known address at compile time or in a register of the CPU (quite simplified, but still true)
Being into OS development, I'd gladly explain anything I know in more depth if you like, but that's for sure out of scope for SOF so we need to find another channel if you're interested.
the value of i is kept in the memory address p and so forth but how does the operating system know where that address is?
The OS doesn't know nor care where the variables are.
I suppose [variables'] addresses are being kept organized in the stack.
The stack does not organize variables' addresses. It simply contains/holds the values of the variables.
Is each declared variable (identifier) treated like an offset from the current position of the stack?
That may indeed hold true for some local variables. However, optimization can either move variables into CPU registers or eliminate them altogether.
What about global variables, how does the operating system and/or the compiler deal with addressing these during execution?
The compiler does not deal with the variables when the program has already been compiled. It has finished its job.
How does the OS and the compiler work to 'remember' the addresses of each identifier without using memory?
The OS does not remember any of that. It doesn't even know anything about your program's variables. To the OS your program is just a collection of somewhat amorphous code and data. Names of variables are meaningless and rarely available in compiled programs. They are only needed for programmers and compilers. Neither the CPU nor the OS needs them.
Are all variables just entered (pushed) in-order into the stack and their names are replaced with their offsets?
That would be a reasonable simplified model for local variables.
if so, what about conditional code that can change the order of declaration?
That's what the compiler has to deal with. Once the program's compiled, all has been taken care of.
as #Stochastically explained:
The compiler references the variables (i, p and p2p in your case)
relative to SP. In other words, the compiler decides what the offset
of each variable should be from SP, and produces machine code
accordingly.
maybe this example explains it additionally to you. It is on amd64, thus size of pointer is 8 bytes. As you can see there are no variables, only offsets from register.
#include <cstdlib>
#include <stdio.h>
using namespace std;
/*
*
*/
int main(int argc, char** argv) {
int i, *p = &i;
int **p2p = &p;
printf("address 0f i: %p",p);//0x7fff4d24ae8c
return 0;
}
disassembly:
!int main(int argc, char** argv) {
main(int, char**)+0: push %rbp
main(int, char**)+1: mov %rsp,%rbp
main(int, char**)+4: sub $0x30,%rsp
main(int, char**)+8: mov %edi,-0x24(%rbp)
main(int, char**)+11: mov %rsi,-0x30(%rbp)
!
! int i, *p = &i;
main(int, char**)+15: lea -0x4(%rbp),%rax
main(int, char**)+19: mov %rax,-0x10(%rbp) //8(pointer)+4(int)=12=0x10-0x4
! int **p2p = &p;
main(int, char**)+23: lea -0x10(%rbp),%rax
main(int, char**)+27: mov %rax,-0x18(%rbp) //8(pointer)
! printf("address 0f i: %p",p);//0x7fff4d24ae8c
main(int, char**)+31: mov -0x10(%rbp),%rax //this is pointer
main(int, char**)+35: mov %rax,%rsi //get address of variable, value would be %esi
main(int, char**)+38: mov $0x4006fc,%edi
main(int, char**)+43: mov $0x0,%eax
main(int, char**)+48: callq 0x4004c0 <printf#plt>
! return 0;
main(int, char**)+53: mov $0x0,%eax
!}
main(int, char**)()
main(int, char**)+58: leaveq
main(int, char**)+59: retq