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.
Related
I think I might be asking a very wrong question, but I really tried to understand it by googling, but with no luck.
As we know, we have a stack and heap. Heap for the dynamically allocated ones, stack for local variables and e.t.c.
Let's say I have the following c++ code.
void bla(int v1, int v2, int v3) {
int g = v1 + v2+ v3;
}
void nice(int g){
int z = 20;
int k = 30;
bla(g, z, k);
}
int main(){
cout<<"Hello World";
nice(40);
}
Now, let's imagine there's a stack. I understand that for example values z,k,g will be stored on stack. But when I call the function nice which calls bla where are those stored ? I've read that each function execution causes call stack size to increase by 1. I'd say that even creating local variables also causes call stack to be increased by 1.
So, how are those(callstack, stack) related at all ?
Here is my assumption:
When we call nice, completely new stack gets created. In there, we store z and k. When nice calls bla , now another stack gets created for bla and this second stack stores v1,v2,v3,g. and so on. each function needs its own callstack,but we can call this stack too.
Each running process is allocated a chunk of memory which it calls "the stack." And, this area of memory is used both to represent the "call/return sequence" (through what are known as "stack frames"), and the so-called "local variables" that are used by each routine that is called. They are one and the same.
Usually, different CPU registers are used to point simultaneously to each thing. One register points to the "local variable" values, while an entirely different register points to the "stack frame." (In interpreters, a similar mechanism is used.) There's also a third register which indicates the current "top of stack."
At the beginning of executing each new function, the "top of stack" is simply moved ahead to account for the local variables, after the "local variables pointer" remembers what it used to be.
When a "return from subroutine" occurs, the "top-of-stack pointer" is reverted to some previous value, and everything that once existed "above it" is now, literally, forgotten. Because it no longer matters.
So, how are those(callstack, stack) related at all ?
They are very much related. They are the same thing. It is also called the execution stack.
This "callstack" is not to be confused with the general concept of "stack" data structure. The callstack called a stack because that describes the structure of the callstack.
causes call stack size to increase by 1
By "1" sure, but what it the unit of the increase? When you call a function, the stack pointer is incremented one stack frame. And the size (measured in bytes) of the stack frame varies. The frame of a function is big enough to contain all local variables (the parameters may also be stored on the stack).
So, if you wish to measure the increment in bytes, then it is not 1, but some number greater than or equal to 0.
I'd say that even creating local variables also causes call stack to be increased by 1.
As I described, having a local variable affects how the stack pointer is incremented when the function is called.
When we call nice, completely new stack gets created.
No, the same stack is shared by all function calls in the entire thread of execution.
Pretty much none of this is specified by the C++ language, but rather are implementation details that apply to most C++ implementations in typical case, but are simplified for easier understanding.
Stack denotes a data structure which consists of a set of usually similar items and has the following abilities:
push: adds an item to the "top" of the stack, which will become the new top
pop: removes the item from the top, thus the item which was previously the top will be the top again; this operation returns the item you remove
top: gets the top item of the stack, without modifying your stack
Your memory has a section called "the stack", where, as you correctly understood, functions are stored. That is the call stack. However, stack and call stack are not 100% equivalent, since stacks are used in many other cases, basically whenever you need a LIFO (Last In First Out) processing. It's used at the LIFO policy of the CPU, for example, or, when you do a depth-first search in a graph. In short, stack is a data structure, which can be applied in many cases, the call stack in memory being a prominent example.
So, why stack is in use to store function calls in memory. Let's take into consideration embedded function calls, like:
f1(f2(...(fn(x))...))
It's a rule of thumb that in order to evaluate fi, 1 <= i < n, you need to evaluate fj, where 1 < j <= n, assuming that i < j. As a result, f1 is the first to be called, but the last to be evaluated. Hence, you have a LIFO processing, which is best done via using a stack.
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".
During a C++ program execution does stack frame of a particular function always have a constant size or compiler is allowed to do dynamic stack management in some cases, something similar to what alloca() function does? to better describe it I mean offset of a particular local variable or object in the stack frame may change at different executions of function
At least in most typical implementations, the stack frame for a variadic function varies depending on how many variables are passed. For example:
printf("%d", 1); // stack frame contains 1 pointer, one int
printf("%d %d", 1, 2); // stack frame contains one pointer, 2 ints.
Whether the implementation is particularly similar to alloca depends on the implementation though (especially since alloca isn't standard, so how or even if it's implemented may vary).
The compiler is allowed to do as it wishes, after all it generates the code and as long as it does what the program in C++ says it is fine. In general, whenever possible, compilers calculate the total needed stack space for the function and reserve that upfront (reducing the number of times the stack register is written to), even if objects are created and destructed on demand.
In common implementations, local variables are placed on the stack frame. Some functions may have variables that are accommodated by registers, others will have variables placed on the stack.
Stack frames may also be expanded by non-static variables declared in statement blocks.
There is no standard minimum size for a stack frame. The maximum size of a stack frame depends on the platform and implementation. A common implementation is to have the stack to expand towards the heap and the heap expands towards the stack.
The standard does not say anything about it (it does not even require you to have a stack), and with C++14 likely something like alloca will be needed since it will likely get a "light" version of C99 VLAs.
I was asked this question in an interview- "how much memory does a function use?". So I tried to answer by saying you could add up all the memory taken by all the data variables , data structures it instantiates- for example add 4 bytes for long, 1 for char , 4 for int, 32 bits for a pointer on 32 bits system, and adding any inputs that were dynamically allotted. The interviewer was not happy with my answer.
I am learning C++, and will appreciate any insight.
Question is quite undefined. A function itself will occupy just the space for its activation record from the caller, for parameters and for its local variables on the stack. According to architecture the activation record will contain things like saved registers, address to return when the function is called and whatever.
But a function can allocate how much memory it requires on the heap so there is no a precise answer.
Oh in addition, if the function is recursive then it could use a lot of memory, always because of activation records which are needed between each call.
From point of view of static behavior,
1. Data used by it - Sum of all variables memory sizes
2. Size of instructions - Each instruction written inside a function will occupy some memory in binary. That is how size of your function will be identified. This is nothing but your compiled code size.
From point of view of dynamic behavior (run time),
1. Heap memory resulted because of a function call is function memory.
i think this guide on function footprints is what you were talking about. they were probably looking for "32/64 bits (integer) because its a pointer"...
I bet the right answer could be "Undefined". An empty function consumes nothing.
function func(){}
A chaining one takes more than we can actually estimate.
function funcA()
{
funcB();
funcC();
//...
}
A local object without being used in its scope will be optimized away by most compilers so it too takes zero memory in its container.
function func()
{
var IamIgnored=0;
//don't do anything with IamIgnored
}
And please don't miss the memory alignment so I think calculating memory used by an object or a function can't be simply done by accumulating all objects' memory sizes within their scopes.
I know C standards preceding C99 (as well as C++) says that the size of an array on stack must be known at compile time. But why is that? The array on stack is allocated at run-time. So why does the size matter in compile time? Hope someone explain to me what a compiler will do with size at compile time. Thanks.
The example of such an array is:
void func()
{
/*Here "array" is a local variable on stack, its space is allocated
*at run-time. Why does the compiler need know its size at compile-time?
*/
int array[10];
}
To understand why variably-sized arrays are more complicated to implement, you need to know a little about how automatic storage duration ("local") variables are usually implemented.
Local variables tend to be stored on the runtime stack. The stack is basically a large array of memory, which is sequentially allocated to local variables and with a single index pointing to the current "high water mark". This index is the stack pointer.
When a function is entered, the stack pointer is moved in one direction to allocate memory on the stack for local variables; when the function exits, the stack pointer is moved back in the other direction, to deallocate them.
This means that the actual location of local variables in memory is defined only with reference to the value of the stack pointer at function entry1. The code in a function must access local variables via an offset from the stack pointer. The exact offsets to be used depend upon the size of the local variables.
Now, when all the local variables have a size that is fixed at compile-time, these offsets from the stack pointer are also fixed - so they can be coded directly into the instructions that the compiler emits. For example, in this function:
void foo(void)
{
int a;
char b[10];
int c;
a might be accessed as STACK_POINTER + 0, b might be accessed as STACK_POINTER + 4, and c might be accessed as STACK_POINTER + 14.
However, when you introduce a variably-sized array, these offsets can no longer be computed at compile-time; some of them will vary depending upon the size that the array has on this invocation of the function. This makes things significantly more complicated for compiler writers, because they must now write code that accesses STACK_POINTER + N - and since N itself varies, it must also be stored somewhere. Often this means doing two accesses - one to STACK_POINTER + <constant> to load N, then another to load or store the actual local variable of interest.
1. In fact, "the value of the stack pointer at function entry" is such a useful value to have around, that it has a name of its own - the frame pointer - and many CPUs provide a separate register dedicated to storing the frame pointer. In practice, it is usually the frame pointer from which the location of local variables is calculated, rather than the stack pointer itself.
It is not an extremely complicated thing to support, so the reason C89 doesn't allow this is not because it was impossible back then.
There are however two important reasons why it is not in C89:
The runtime code will get less efficient if the array size is not known at compile-time.
Supporting this makes life harder for compiler writers.
Historically, it has been very important that C compilers should be (relatively) easy to write. Also, it should be possible to make compilers simple and small enough to run on modest computer systems (by 80s standards). Another important feature of C is that the generated code should consistently be extremely efficient, without any surprises,
I think it is unfortunate that these values no longer hold for C99.
The compiler has to generate the code to create the space for the frame on the stack to hold the array and other local local variables. For this it needs the size of the array.
Depends on how you allocate the array.
If you create it as a local variable, and specify a length, then it matters because the compiler needs to know how much space to allocate on the stack for the elements of the array. If you don't specify a size of the array, then it doesn't know how much space to set aside for the array elements.
If you create just a pointer to an array, then all you need to do is allocate the space for the pointer itself, and then you can dynamically create array elements during run time. But in this form of array creation, you're allocating space for the array elements in the heap, not on the stack.
In C++ this becomes even more difficult to implement, because variables stored on the stack must have their destructors called in the event of an exception, or upon returning from a given function or scope. Keeping track of the exact number/size of variables to be destroyed adds additional overhead and complexity. While in C it's possible to use something like a frame pointer to make freeing of the VLA implicit, in C++ that doesn't help you, because those destructors need to be called.
Also, VLAs can cause Denial of Service security vulnerabilities. If the user is able to supply any value which is eventually used as the size for a VLA, then they could use a sufficiently large value to cause a stack overflow (and therefore failure) in your process.
Finally, C++ already has a safe and effective variable length array (std::vector<t>), so there's little reason to implement this feature for C++ code.
Let's say you create a variable sized array on the stack. The size of the stack frame needed by the function won't be known at compile time. So, C assumed that some run time environments require this to be known up front. Hence the limitation. C goes back to the early 1970's. Many languages at the time had a "static" look and feel (like Fortran)