How to know how much stack function is consuming?

后端 未结 4 1664
天涯浪人
天涯浪人 2021-01-22 06:04

Recently, I came across this question in an interview:
How can we determine how much storage on the stack a particular function is consuming?

相关标签:
4条回答
  • 2021-01-22 06:35

    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.

    0 讨论(0)
  • 2021-01-22 06:36

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

    0 讨论(0)
  • 2021-01-22 06:40

    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.

    0 讨论(0)
  • 2021-01-22 06:41

    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);
    }
    
    0 讨论(0)
提交回复
热议问题