【问题标题】:Allocating memory on the caller's stack在调用者的堆栈上分配内存
【发布时间】:2015-09-08 07:02:57
【问题描述】:

我正在使用 MSVC++ 2012 用 C++ 编写代码,我的代码针对 x86 平台。我有一种情况,编写一个函数(除其他外)可以在 CALLING 函数的堆栈上分配一些内存是有利的。我在这篇文章中并不是要讨论这样做的智慧,而只是考虑技术上的可行性。

我的实施计划是将我的函数编写为一个裸函数,并在内联汇编中使用自定义序言代码。在序言中,我将首先确定我需要多少内存,然后将返回地址、参数和该指针向下移动该数量。最后,我会将堆栈指针调整该数量。如果我没记错的话,这会在调用函数的堆栈上创建一个区域。

有人看到这个计划有什么漏洞吗?

【问题讨论】:

  • 不是真的,不,假设你没有搞砸汇编程序并且有足够的堆栈空间:) 我假设堆栈需要在返回后的某个时候修复以删除分配空间?
  • 类似于alloca 的作用?
  • 这可能是 XY 问题。 @CaptainObvlious 指出,alloca 做你想做的事,但没有任何背景,所有解决方案都可能毫无意义
  • 移动堆栈帧的一个明显问题是任何传递/存储到其中的变量的指针都是无效的。
  • @Josh - 如果 epb/rbp 用于保存/恢复堆栈指针,那么至少堆栈指针将被恢复。问题是参数和局部变量是否作为 esp/rsp(这里的问题)或 epb/rpb(应该没问题)的偏移量来处理。 Microsoft 编译器有一个禁用帧指针的选项,在这种情况下 ebp/rbp 不用于保存/恢复堆栈指针,在这种情况下,您会遇到问题。

标签: c++ memory-management stack calling-convention


【解决方案1】:

一个明显的缺点是 x64 不支持内联汇编,因此您将来会限制您的可移植性。

另一个明显的缺点是调用函数会期望堆栈指针位于相对于其本地变量的某个位置,但现在不再如此。我相信编译器生成的代码无法应对这种情况。这种不和谐的一个简单例子是生成的代码不知道要弹出多少堆栈空间。

我观察到的唯一方法是使用默认参数。你可以这样做,例如

int f(void* p = alloca(55)) {
}

从技术上讲,由于默认参数是在 调用 函数中计算的,这将从调用函数的堆栈中分配内存。但是,您可能会发现这里的核心问题是计算您需要多少空间。

【讨论】:

  • 这很聪明。我从来没想过这点。它在这种情况下不起作用,因为正如您所指出的,确定所需的大小是一个问题。但我必须在未来记住这项技术。
  • @Josh:明显的对策是定义一个宏,它可以有效地为您传递参数。
【解决方案2】:

几张海报提出了很多好的观点。我会在这里总结一下。

我描述的技术将起作用,但前提是调用函数是使用 CDECL 调用约定定义的,并且禁用堆栈帧指针的链接器开关未启用。调用函数必须是 CDECL,而不是 STDCALL 或 THISCALL,因为 STDCALL 和 THISCALL 不会正确恢复调用函数的 Epilog 中的堆栈指针,因为这些约定从当前 ESP 中减去而不是恢复原始值。他们会减去错误的数量,而不考虑增加的空间,因此堆栈会被破坏。禁用堆栈帧指针的链接器开关不能处于活动状态,因为如果调用函数将引用参数作为来自 ESP 而不是 EBP 的偏移量,因此偏移量将不正确。

除了上述限制之外,使用这种技术的后果是 x64 可移植性的损失和对性能优化的不可预测的影响。

鉴于局限性,我选择不继续使用这种技术。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-07-24
    • 2011-05-28
    • 2018-09-17
    • 2010-12-11
    • 1970-01-01
    • 2014-10-26
    • 1970-01-01
    相关资源
    最近更新 更多