虚函数是否不太可能导致堆栈溢出?
Are virtual functions less likely to cause stack overflow?
在一次采访中有人问我函数参数是位于栈中还是堆中。我很确定这是作为示例给出的,以说明由于嵌入式内存规模的堆栈溢出风险如何避免递归函数。然而,这似乎是一个棘手的问题,因为我一直在审查虚函数,它允许 dynamic dispatch.
在搜索 之后,似乎普通老式常规函数参数的内存位置取决于实现。
其他答案对虚函数的说法几乎相同——无法保证虚函数参数在内存中的实现方式。
所以我想了解一下:
运行时函数实现如何以及在何处(堆栈?堆?两者都略知一二?)?
在对 ABI 所说的内容一无所知的情况下,了解此信息是否有用/是查找此类信息的好地方,还是有更好的地方可以查看?除了规格之外,是否有任何实验数据可以让我们“了解”内存在实践中的平均工作原理?
还有,先-post你好!
参数和 return 值是调用约定的保留,有许多调用约定,它们都有细微差别。
在 64 位 x86 计算中,只有 2 个调用约定值得考虑 Microsoft 和 SystemV。
两种调用约定都旨在尽可能多地传递寄存器。在我脑海中浮现的是 4 个整数参数和 4 个浮点参数。有特殊情况,但通常超出此范围的任何内容都会被推入堆栈。
您可以非虚拟地调用 virtual
函数。因此,决定虚拟性的不是函数的定义,而是调用的类型。因此,几乎不可能有不同的约定。
parameter/return 值的传递方式和位置由函数的调用约定定义,virtual
对此完全没有影响。一些调用约定仅使用堆栈,而其他约定混合使用堆栈和 CPU 寄存器。但从来没有堆。
virtual
所做的唯一一件事就是指示如何确定函数地址,以及在其隐藏的 this
参数中传递什么值。但是参数值(包括 this
)和 return 值仅根据调用约定传递。
在 32 位上,跨实现标准化的两个调用约定是 __cdecl
和 __stdcall
。其他调用约定是实现定义的,例如 __fastcall
和 __thiscall
.
在 64 位上,ABI 定义了所有实现都必须遵循的单一调用约定。忽略 32 位调用约定。
"calling a function" 的整个想法与堆栈有着内在的联系。调用约定可以将一些参数移到寄存器中,间接传递参数(通过指针或引用)可以减少实际传递的数据量,但 return 地址仍然必须压入堆栈。
没有堆栈影响的合法方法是内联函数,有人可能会争辩说这本身并不完全是 "function call",并且现在越来越难以保证 - 也就是说,决定是取决于编译器。
可以内联尾递归,将其变成无限循环,因此不会 运行 永远出栈。
最后,virtual
连接到任何一个的唯一方法是动态链接调用更难内联 - 它们需要先去虚拟化,这仍然不是不可能的。
总体结论好像是"if there is any connection at all, it's in favor of non-virtual
functions"。
在一次采访中有人问我函数参数是位于栈中还是堆中。我很确定这是作为示例给出的,以说明由于嵌入式内存规模的堆栈溢出风险如何避免递归函数。然而,这似乎是一个棘手的问题,因为我一直在审查虚函数,它允许 dynamic dispatch.
在搜索
其他答案对虚函数的说法几乎相同——无法保证虚函数参数在内存中的实现方式。
所以我想了解一下:
运行时函数实现如何以及在何处(堆栈?堆?两者都略知一二?)?
在对 ABI 所说的内容一无所知的情况下,了解此信息是否有用/是查找此类信息的好地方,还是有更好的地方可以查看?除了规格之外,是否有任何实验数据可以让我们“了解”内存在实践中的平均工作原理?
还有,先-post你好!
参数和 return 值是调用约定的保留,有许多调用约定,它们都有细微差别。 在 64 位 x86 计算中,只有 2 个调用约定值得考虑 Microsoft 和 SystemV。 两种调用约定都旨在尽可能多地传递寄存器。在我脑海中浮现的是 4 个整数参数和 4 个浮点参数。有特殊情况,但通常超出此范围的任何内容都会被推入堆栈。
您可以非虚拟地调用 virtual
函数。因此,决定虚拟性的不是函数的定义,而是调用的类型。因此,几乎不可能有不同的约定。
parameter/return 值的传递方式和位置由函数的调用约定定义,virtual
对此完全没有影响。一些调用约定仅使用堆栈,而其他约定混合使用堆栈和 CPU 寄存器。但从来没有堆。
virtual
所做的唯一一件事就是指示如何确定函数地址,以及在其隐藏的 this
参数中传递什么值。但是参数值(包括 this
)和 return 值仅根据调用约定传递。
在 32 位上,跨实现标准化的两个调用约定是 __cdecl
和 __stdcall
。其他调用约定是实现定义的,例如 __fastcall
和 __thiscall
.
在 64 位上,ABI 定义了所有实现都必须遵循的单一调用约定。忽略 32 位调用约定。
"calling a function" 的整个想法与堆栈有着内在的联系。调用约定可以将一些参数移到寄存器中,间接传递参数(通过指针或引用)可以减少实际传递的数据量,但 return 地址仍然必须压入堆栈。
没有堆栈影响的合法方法是内联函数,有人可能会争辩说这本身并不完全是 "function call",并且现在越来越难以保证 - 也就是说,决定是取决于编译器。
可以内联尾递归,将其变成无限循环,因此不会 运行 永远出栈。
最后,virtual
连接到任何一个的唯一方法是动态链接调用更难内联 - 它们需要先去虚拟化,这仍然不是不可能的。
总体结论好像是"if there is any connection at all, it's in favor of non-virtual
functions"。