简体   繁体   English

如何知道消耗了多少堆栈函数?

[英]How to know how much stack function is consuming?

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.基本上不可能在 C 或 C++ 程序的任何部分保证是否可以进行另一个函数调用。 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. “堆栈大小”,或者更好地称为“函数调用和局部变量存储深度”,是语言标准承认其存在但被认为超出范围的实现限制之一。 (Eg for C++ see [implimits], Annex B.) (例如,对于 C++,请参阅 [implimits],附件 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.个别平台可能会提供 API 以允许程序自省平台限制,但 C 和 C++ 都没有指定或如何做到这一点。

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.在 x86 机器上,一个堆栈帧由返回地址(4/8 字节)、参数和局部变量组成。

The parameters, if eg 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.确定知道的唯一方法是实际分析编译器生成的汇编代码,或者查看运行时堆栈指针值的绝对差异 - 在调用特定函数之前和之后。

Eg例如

#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 givesColiru 上的 GCC给出

0x7fffbcefb410
0x7fffbcefb400

A difference of 16 bytes.相差 16 个字节。 (The stack grows downwards on x86.) (堆栈在 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.例如,如果约定允许 2 个变量但有 5 个参数,则将在堆栈中放置 3 个参数。

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.对于面试问题,我会提到以上所有问题,这通常会让面试官想快速进入下一个问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM