C中的内存优化递归调用
Posted
技术标签:
【中文标题】C中的内存优化递归调用【英文标题】:Memory-optimizing recursive calls in C 【发布时间】:2011-11-25 15:36:21 【问题描述】:我有一个递归函数,可以这样写:
void func(TypeName *dataStructure, LL_Node **accumulator)
func(datastructure->left, accumulator);
func(datastructure->right, accumulator);
char buffer[1000];
// do some stuff
return;
我知道实际上缓冲区是在函数开头分配的,并将语句放在嵌套范围块doesn't actually use a new stack frame 中。但我不希望编译器一次分配指数数量的 1000 字节缓冲区,因为每个级别返回时可以一次分配和丢弃一个。
我应该使用外部全局变量吗?调用辅助函数以强制在递归调用后分配缓冲区?我在这里真正想要的是关于强制这种行为的最干净、最符合 C 语言习惯的方式的建议。
编辑:一个附加问题。如果完全相同的accumulator
将传递给func
的每次调用,那么将accumulator
指针留在全局变量中而不是每次调用都将其压入堆栈是闻所未闻的吗?
【问题讨论】:
【参考方案1】:由于它一次只被一个调用使用,你可以预先分配它并通过一个操作数将它传递给所有调用:
void func(TypeName *dataStructure, LL_Node **accumulator, char *buffer)
func(datastructure->left, accumulator, buffer);
func(datastructure->right, accumulator, buffer);
// do some stuff
return;
【讨论】:
好的,它可以完美地进入我刚刚添加到原始问题中的编辑。当您可以将指针保留在全局变量中时,通过每一级递归传递完全相同的缓冲区指针是不好的做法吗? 实际上,使用全局变量并不是一个好主意。 (特别是如果你有多个线程)所以传入缓冲区是首选方法。 添加到 Mystical 的解决方案中,如果您的func
作为模块/应用程序的 API 的一部分公开,那么最好保留原始签名 void func(TypeName *dataStructure, LL_Node **accumulator)
并在该函数中声明一个本地 char buffer[10000]
并委托给一个内部 func_impl(dataStructure, accumulator, buffer)
以隐藏临时缓冲区空间的实现细节。这样客户端代码就有了一个更简单、更干净的 API 来处理。【参考方案2】:
一种选择是将函数分解为非递归“公共”函数,该函数设置缓冲区并调用需要传入缓冲区的私有递归函数:
struct func_buffer
char buffer[1000];
;
static
void func_private(TypeName *dataStructure, LL_Node **accumulator, struct func_buffer* buf)
func_private(datastructure->left, accumulator, buf);
func_private(datastructure->right, accumulator, buf);
// do some stuff with *buf
return;
void func(TypeName *dataStructure, LL_Node **accumulator)
struct func_buffer buffer;
func_private( dataStructure, accumulator, &buffer);
return;
这样,函数的用户不需要关心函数的递归部分使用的内存是如何管理的细节。因此,如果很明显这种更改是必要的或有意义的,您可以很容易地将其更改为使用全局或动态分配的缓冲区。
【讨论】:
【参考方案3】:您可以将引用传递给缓冲区或使用全局变量。
当你使用参考文献时
void func(TypeName *dataStructure, LL_Node **accumulator, char buffer[])
func(datastructure->left, accumulator, buffer);
func(datastructure->right, accumulator, buffer);
char buffer[1000];
// do some stuff
return;
void main()
char buffer[1000];
func (structure, accum, buffer);
你传递的是引用,只是指向数组开头的指针,所以你必须记住它的长度。
如果选择使用全局变量,其实不是在使用栈,而是在分配程序内存,一个代码和数据共存的共享空间(代码就是数据)。因此,如果您这样做,您将永远不会在调用中使用单个字节的额外内存:
char buffer[1000];
void func(TypeName *dataStructure, LL_Node **accumulator)
func(datastructure->left, accumulator);
func(datastructure->right, accumulator);
// do some stuff
return;
void main()
func (structure, accum);
您可以选择一个。第二个在每次递归调用时将较少的参数压入堆栈,但会扩大程序大小。第一个对某些人来说更优雅,但有点慢,甚至可能不明显。
【讨论】:
这是完全相同的代码两次 :) 你是说如果你分配一个 10,000 字节的全局变量,那么程序可执行文件本身的大小会大 10k 吗?磁盘上实际上留了一个空白空间吗?如果你把它放在 main() 中呢? 糟糕,非常感谢,复制粘贴错误。确切地说,当它被翻译成 ASM 时,全局变量将被放在section .data
中,这是为变量保留的空间。如果您放入 main,则会将 10000 字节的缓冲区推入其中,从而增加堆栈大小并减少函数可能的最大递归量。
如果你认为你需要一个更大的缓冲区,解决方案是在堆中分配它,使用具有必要大小的 malloc,并传递指针,就像在我的第一个版本中一样代码。【参考方案4】:
在这种情况下,我个人会在堆上分配缓冲区,有点像这样:
void func(TypeName *dataStructure, LL_Node **accumulator, char *buffer )
bool alloced = false;
if( buffer == 0 )
buffer = (char*) malloc( 1000 );
alloced = true;
func(datastructure->left, accumulator, buffer);
func(datastructure->right, accumulator, buffer);
// do some stuff
if( alloced )
free( buffer );
return;
如果您不介意 C++ 语法,您可以将缓冲区默认设置为零,或者您可以修改函数名称并添加
#define func(a,b) __mangledfunc__(a,b,0)
这对您的应用程序来说似乎是最简单的。
【讨论】:
我喜欢将缓冲区放在堆上的想法,但我认为你搞砸了实现。当只需要一个递归调用时,您为每个递归调用分配一个 1000 字节的内存块,这正是我试图避免的。 好吧,我修正了我在递归调用中省略附加缓冲区参数的小疏忽,但这个想法最初就在那里。如果您只公开宏调用,以便使用值 0 隐式初始化缓冲区,则该函数会检查它,如果它确实为零,那么它会分配一个新的缓冲区并将其传递给未来的调用。未来的调用会为缓冲区获取一个非零值,因此它们不会对缓冲区进行 malloc 而是使用原始的。【参考方案5】:我相信您的普通编译器可以优化所谓的“尾递归函数”,其中函数中的最后一条指令基本上是对该函数的递归调用。在这种特殊情况下,该函数将简单地在每次递归时重用堆栈帧(因此所有堆栈分配的变量都不会被取消/重新分配!)如果您可以在递归调用之前推送所有指令,并且您有一个不错的编译器,它应该可以工作——否则,我只是将它作为参考变量传递。
【讨论】:
以上是关于C中的内存优化递归调用的主要内容,如果未能解决你的问题,请参考以下文章