Оптимизирующие память рекурсивные вызовы в C
у меня есть рекурсивная функция, которая может быть записана как:
void func(TypeName *dataStructure, LL_Node **accumulator) {
func(datastructure->left, accumulator);
func(datastructure->right, accumulator);
{
char buffer[1000];
// do some stuff
}
return;
}
Я знаю, что на самом деле буфер выделяется в начале функции и помещает оператор во вложенный блок области фактически не использует новый кадр стека. Но я не хочу, чтобы компилятор выделял экспоненциальное число 1000-байтовых буферов сразу, когда они могут быть выделены и выброшены по одному по мере возвращения каждого уровня.
должен ли я использовать снаружи глобальные переменные? Вызов вспомогательной функции для принудительного выделения буфера после рекурсивного вызова? То, что я действительно ловлю здесь,-это совет о самом чистом, самом с-идиоматическом способе принуждения этого поведения.
Edit: один дополнительный вопрос. Если то же самое accumulator
будет передаваться на каждый вызов func
, это неслыханно, чтобы оставить accumulator
указатель в глобальной переменной, а не толкать его в стек с каждым вызовом?
5 ответов
поскольку он используется только одним вызовом за раз, вы можете просто предварительно выделить его и передать во все вызовы через операнд:
void func(TypeName *dataStructure, LL_Node **accumulator, char *buffer) {
func(datastructure->left, accumulator, buffer);
func(datastructure->right, accumulator, buffer);
{
// do some stuff
}
return;
}
один из вариантов-разбить функцию на нерекурсивную "общедоступную" функцию, которая устанавливает буфер и вызывает частную рекурсивную функцию, которая требует передачи буфера:
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;
}
таким образом, пользователям функции не нужно беспокоиться о том, как управляется память, используемая рекурсивной частью функции. Таким образом, вы можете легко изменить его, чтобы использовать глобальный или динамически выделенный буфер, если станет очевидно, что такое изменение необходимо или имело бы смысл.
вы можете либо передать ссылку на буфер, либо использовать глобальную переменную.
когда вы используете ссылку, как в
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);
}
это до вас, чтобы выбрать один. Второй толкает меньше параметров в стек при каждом рекурсивном вызове, но увеличивает размер программы. Первый из них более элегантен для некоторых, но немного медленнее, возможно, даже не заметен.
Я бы лично выделил буфер в куче в этом сценарии примерно так:
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)
Кажется, что это было бы проще всего для вашего приложения.
Я считаю, что ваш средний компилятор может оптимизировать так называемые "хвостовые рекурсивные функции", где в основном последняя инструкция в вашей функции является рекурсивным вызовом этой функции. В этом частном случае функция будет просто повторно использовать кадр стека с каждой рекурсией (поэтому все ваши переменные, выделенные стеком, не получают un/re-allocated!) Если вы можете нажать все ваши инструкции перед рекурсивными вызовами, и у вас есть приличный компилятор, он должен работать-иначе я просто передам это вокруг как ссылочная переменная.