如何在C++中模拟堆栈帧
How can I emulate a stack frame in C++?
我正在编写一个容器,该容器在内部使用alloca
来分配堆栈上的数据。撇开使用alloca
的风险不谈,假设我必须将其用于我所在的域(这部分是围绕alloca
的学习练习,部分是为了研究动态大小的堆栈分配容器的可能实现)。
根据alloca
的man
页面(强调我的):
alloca() 函数在调用方的堆栈帧中分配大小字节的空间。当调用 alloca() 的函数返回给其调用方时,会自动释放此临时空间。
使用特定于实现的功能,我设法以这样的方式强制内联,即调用者堆栈用于此函数级"范围"。
但是,这意味着以下代码将在堆栈上分配大量内存(编译器优化除外):
for(auto iteration : range(0, 10000)) {
// the ctor parameter is the number of
// instances of T to allocate on the stack,
// it's not normally known at compile-time
my_container<T> instance(32);
}
如果不知道此容器的实现细节,则可能会期望它分配的任何内存在超出范围时instance
释放。事实并非如此,可能会导致在封闭函数期间出现堆栈溢出/高内存使用率。
我想到的一种方法是显式释放析构函数中的内存。除了对生成的程序集进行逆向工程之外,我还没有找到一种方法(另请参阅此内容)。
我想到的唯一另一种方法是在编译时指定最大大小,使用它来分配固定大小的缓冲区,在运行时指定实际大小并在内部使用固定大小的缓冲区。这样做的问题是它可能非常浪费(假设每个容器的最大字节为 256 字节,但大多数时候您只需要 32 个字节)。
因此,这个问题;我想找到一种方法来向此容器的用户提供这些范围语义。不可移植是可以的,只要它在平台上是可靠的,它的目标(例如,一些只适用于x86_64的文档编译器扩展就可以了)。
我知道这可能是一个XY问题,所以让我清楚地重申我的目标:
我- 正在编写一个容器,该容器必须始终在堆栈上分配其内存(据我所知,这排除了 C VLA)。
- 容器的大小在编译时未知。
- 我想保持内存的语义,就好像它是由容器内的
std::unique_ptr
持有一样。 - 虽然容器必须具有C++ API,但使用 C 编译器扩展是可以的。
- 代码现在只需要在x86_64上工作。
- 目标操作系统可以基于 Linux 或 Windows,它不需要同时在两者上运行。
我正在编写一个容器,该容器必须始终在堆栈上分配其内存(据我所知,这排除了 C VLA)。
大多数编译器中 C VLA 的正常实现都在堆栈上。 当然,ISO C++并没有说明如何在后台实现自动存储,但它(几乎?)对于普通机器(确实有调用+数据堆栈)上的C实现来说是通用的,可以将其用于所有自动存储,包括VLA。
如果您的VLA太大,您将获得堆栈溢出,而不是回退到malloc
/free
。
C 和 C++ 都没有指定alloca
;它只适用于具有像"普通"机器这样的堆栈的实现,即您可以期望 VLA 做你想做的事的相同机器。
所有这些条件都适用于 x86-64 上的所有主要编译器(MSVC 不支持 VLA 除外)。
如果您有支持 C99 VLA(如 GNU C++)的C++编译器,智能编译器可能会对具有循环作用域的 VLA 重用相同的堆栈内存。
在编译时指定最大大小,使用它来分配固定大小的缓冲区...浪费
对于您提到的特殊情况,您可以将固定大小的缓冲区作为对象的一部分(大小作为模板参数),如果它足够大,则可以使用它。 如果没有,请动态分配。 可以使用指针成员指向内部或外部缓冲区,并使用一个标志来记住是否在析构函数中delete
它。 (当然,您需要避免在作为对象一部分的数组上delete
。
// optionally static_assert (! (internalsize & (internalsize-1), "internalsize not a power of 2")
// if you do anything that's easier with a power of 2 size
template <type T, size_t internalsize>
class my_container {
T *data;
T internaldata[internalsize];
unsigned used_size;
int allocated_size; // intended for small containers: use int instead of size_t
// bool needs_delete; // negative allocated size means internal
}
allocated_size
只需要在它增长时进行检查,所以我让它签名为 int,这样我们就可以重载它,而不是需要一个额外的布尔成员。
通常容器使用 3 个指针而不是指针 + 2 个整数,但如果您不经常增长/收缩,那么我们会节省空间(在 x86-64 上,int
为 32 位,指针为 64 位),并允许此重载。
增长到需要动态分配的容器应继续使用该空间,但随后收缩应继续使用该动态空间,因此再次增长的成本更低,并避免复制回内部存储。 除非调用方使用函数释放未使用的多余存储空间,否则请复制回来。
移动构造函数可能应保持分配不变,但复制构造函数应尽可能复制到内部缓冲区中,而不是分配新的动态存储。
- 如何使用Google Mock来模拟gettimeofday()
- G锁定铸造到基础上会释放模拟行为
- 有什么好的方法可以让系统调用代理允许在单元测试中进行模拟
- 算法问题:查找从堆栈中弹出的所有序列
- 使用模板进行堆栈实现; "name followed by :: must be a class or namespace"
- 落砂模拟碰撞检测C++和SFML
- Visual Studio(或任何其他工具)能否将地址解释为调用堆栈(boost上下文)的开头
- 在gtest.中使用fff.h模拟系统API
- 谷歌模拟和覆盖关键字
- 用C#中的并集模拟C++嵌套结构
- 在同一模拟中使用静脉和静脉_ inet内容时出现运行时错误
- 为什么调用堆栈数组会导致内存泄漏
- 在模拟器中使用并集来模拟CPU寄存器有多合适
- gdb错误:Backtrace已停止:上一帧与此帧相同(堆栈已损坏?)
- 我写了一个C++程序来模拟Enigma机器.我没有得到输出
- 在 leetcode 上提交解决方案时出现堆栈缓冲区溢出错误
- 我的 int main() 中出现堆栈溢出错误
- 堆栈和队列是否像C++中的数组一样传递?
- 如何在C++中模拟堆栈帧
- 将递归模拟为堆栈返回错误