C++: Function variable declarations, how does it work internally? - c++

This has been bothering me for a long time now: Lets say i have a function:
void test(){
int t1, t2, t3;
int t4 = 0;
int bigvar[10000];
// do something
}
How does the computer handle the memory allocations for the variables?
I've always thought that the variables space is saved in the .exe which the computer will then read, is this correct? But as far as i know, the bigvar array doesnt take 10000 int elements space in the .exe, since its uninitialized. So how does its memory allocation work when i call the function ?

Local variables like those are generally implemented using the processor's stack. That means that the only thing that the compiler needs to do is to compute the size of each variable, and add them together. The total sum is the amount to change the stack pointer with at the entry to the function, and to change back on exit. Each variable is then accessed with its relative offset into that block of memory on the stack.
Your code, when compiled in Linux, ends up looking like this in x86 assembler:
test:
pushl %ebp
movl %esp, %ebp
subl $40016, %esp
movl $0, -4(%ebp)
leave
ret
In the above, the constant $40016 is the space needed for the four 32-bit ints t1, t2, t3 and t4, while the remaining 40000 bytes account for the 10000-element array bigvar.

I can't add much to what have already been said, except for a few notes. You can actually put local variables into executable file and have them allocated in the data segment (and initialized) instead of the stack segment. To do that, declare them as static. But then all the invocations of the function would share the same variables while in stack each invocation creates a new set of variables. This can lead to a lot of troubles when the function is called simultaneously by several thread or when there is a recursion (try to imagine that). That's why most languages use stack for local variables and static is rarely used.

On some old compilers, I've met the behavior that the array is statically allocated. Meaning that it sets aside memory for it when it loads the program, and uses that space after that. This behavior is not safe (See Sergey's answer), nor do I expect it to be permitted according to the standards, but I have encountered it in the wild. (I have no memory of what compiler it was.)
For the most part, local variables are kept on the stack, together with return addresses and all that other stuff. This means the uninitialized values may contain sensitive information. This also includes arrays, as per unwind's answer.
Another valid implementation is that the variable found on the stack is a pointer, and that the compiler does the allocation and deallocation (presumably in an exception-safe manner) under the hood. This will conserve stack space (which has to be allocated before the program starts, and cannot easily be extended for x86 architectures) and is also quite useful for C standard VLA (variable length array, aka poor mans std::vector)

Related

How much memory is allocated to call stack?

Previously I had seen assembly of many functions in C++. In gcc, all of them start with these instructions:
push rbp
mov rbp, rsp
sub rsp, <X> ; <X> is size of frame
I know that these instructions store the frame pointer of previous function and then sets up a frame for current function. But here, assembly is neither asking for mapping memory (like malloc) and nor it is checking weather the memory pointed by rbp is allocated to the process.
So it assumes that startup code has mapped enough memory for the entire depth of call stack. So exactly how much memory is allocated for call stack? How does startup code can know the maximum depth of call stack?
It also means that, I can access array out of bound for a long distance since although it is not in current frame, it mapped to the process. So I wrote this code:
int main() {
int arr[3] = {};
printf("%d", arr[900]);
}
This is exiting with SIGSEGV when index is 900. But surprisingly not when index is 901. Similarly, it is exiting with SIGSEGV for some random indices and not for some. This behavior was observed when compiled with gcc-x86-64-11.2 in compiler explorer.
How does startup code can know the maximum depth of call stack?
It doesn't.
In most common implementation, the size of the stack is constant.
If the program exceeds the constant sized stack, that is called a stack overflow. This is why you must avoid creating large objects (which are typically, but not necessarily, arrays) in automatic storage, and why you must avoid recursion with linear depth (such as recursive linked list algorithms).
So exactly how much memory is allocated for call stack?
On most desktop/server systems it's configurable, and defaults to one to few megabytes. It can be much less on embedded systems.
This is exiting with SIGSEGV when index is 900. But surprisingly not when index is 901.
In both cases, the behaviour of the program is undefined.
Is it possible to know the allocated stack size?
Yes. You can read the documentation of the target system. If you intend to write a portable program, then you must assume the minimum of all target systems. For desktop/server, 1 megabyte that I mentioned is reasonable.
There is no standard way to acquire the size within C++.

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

What creates the stack?

Suppose in a program we have implemented a stack. But who creates the stack ? Is it the processor, or operating system, or compiler?
Are you confusing the programs execution stack with the stack container?
You can't "implement" the execution stack, the OS will give you Virtual Address Space and locate there your stack pointer, so you just push and pop from it, you don't "create it", its there when you start.
If you mean the data structure: The processor executes the code. The code makes calls to the operating system to get the memory for the stack, and then manipulates it to form it into a stack. The compiler just turns the code you wrote into code the processor can understand.
If you mean the execution stack: The OS is responsible for loading a process into memory and setting up its memory space to form the stack.
Your program... it performs the required assembly. That assembly was inserted by the compiler in place of the function/function call based on the calling convention being used.
Learning about calling conventions would probably be the most effective way to answer your question.
None of the above. YOU created it when you implemented it. The compiler only translates your thoughts (expressed in a programming language) into machine or assembly code. The processor only runs that program that you wrote. The operating system (assuming one exists), provides mechanisms to facilitate giving you an execution space and memory to do it, but YOUR PROGRAM determines what happens in that execution space and memory.
If you want to implement a stack of your own, try using std::stack<>. If you're talking about the stack that local variables are on, that's created by the C++ runtime system.
"Suppose in a program we have implemented a stack."
Then you implemented it on the underlying, low level data structure like for example array. Your stack = array + functions (push(), pop()) working on an array to provide stack functionality.
"But who creates the stack ? Is it the processor, or operating system, or compiler?"
And who creates functions and array? Functions are created by you, then compiler translates functions to machine instructions and keeps this code in executable. Additionaly it produces a set of instructions to allocate some space in the memory for your array. So your program is a mix of instructions and space for an array. Then operating system loads your program and sends instructions to the processor. Processor performs this instructions and reads/writes data to your array.
Say you have a test C program:
int square( int val ) {
int result;
result = val * val;
return( result );
}
int main( void ) {
int store;
store = square( 3 );
return( 0 );
}
then you can produce the assembler output produced by the compiler using the command gcc -S test.c -o test.s (if you're on a Linux platform).
Looking at the generated code for just the square() function we get:
square:
pushl %ebp
movl %esp, %ebp
subl $16, %esp
movl 8(%ebp), %eax
imull 8(%ebp), %eax
movl %eax, -4(%ebp)
movl -4(%ebp), %eax
leave
ret
You can see that the compiler has generated code to move the stack pointer for the local variables in the routine.
The initialisation code for your program will have allocated a certain amount of memory for "the stack" by calling system (Operating Systems) memory allocation functions. Then it is up to the compiled program to choose how to utilise that area of memory.
Fortunately for you all of this is effectively handled by the compiler without you having to think about it (unless, of course, you're likely to have local variables that are too big for a standard stack size, in which case you may have to instruct your compiler, or thread library, to allocate more stack from the system).
Suppose in a program we have implemented a stack. But who creates the stack ?
Well if you implemented it, then by definition you created it. You need to be more specific w.r.t. context.
The standard runtime library or the linker-loader creates the stack. It is done in a little section of code that runs before your main. This code is inserted automatically by the linker when you link and it runs at runtime, setting up various things before your main is called, for example any statically initialized global variables. It usually sets up the stack too, although some OSes put this into OS code (the linker-loader) because they want to standardize stack implementation/shape on their systems.
the stack is embedded in the processor, it is the esp register, you need to learn a little win32 assembly programming in order to understad the stack
A stack is a Last In First Out (LIFO) list data structure. The stack is created by the program execution where the variables are stored, deleted as per the program execution requirement.