O que é desenrolamento de pilha? Pesquisou, mas não conseguiu encontrar uma resposta esclarecedora!
O que é desenrolamento de pilha? Pesquisou, mas não conseguiu encontrar uma resposta esclarecedora!
Respostas:
O desenrolamento de pilha é geralmente mencionado em conexão com o tratamento de exceções. Aqui está um exemplo:
void func( int x )
{
char* pleak = new char[1024]; // might be lost => memory leak
std::string s( "hello world" ); // will be properly destructed
if ( x ) throw std::runtime_error( "boom" );
delete [] pleak; // will only get here if x == 0. if x!=0, throw exception
}
int main()
{
try
{
func( 10 );
}
catch ( const std::exception& e )
{
return 1;
}
return 0;
}
Aqui, a memória alocada para pleak
será perdida se uma exceção for lançada, enquanto a memória alocada para s
será liberada corretamente pelo std::string
destruidor em qualquer caso. Os objetos alocados na pilha são "desenrolados" quando o escopo é encerrado (aqui o escopo é da funçãofunc
). Isso é feito pelo compilador inserindo chamadas aos destruidores de variáveis automáticas (da pilha).
Agora, esse é um conceito muito poderoso que leva à técnica chamada RAII , ou seja , Aquisição de recursos é inicialização , que nos ajuda a gerenciar recursos como memória, conexões com bancos de dados, descritores de arquivos abertos etc. em C ++.
Agora isso nos permite fornecer garantias de segurança excepcionais .
delete [] pleak;
só é alcançado se x == 0.
Tudo isso se refere ao C ++:
Definição : À medida que você cria objetos estaticamente (na pilha, em vez de alocá-los na memória heap) e executa chamadas de função, eles são "empilhados".
Quando um escopo (qualquer coisa delimitada por {
e }
) é encerrado (usandoreturn XXX;
, atingindo o final do escopo ou lançando uma exceção) tudo dentro desse escopo é destruído (os destruidores são chamados para tudo). Esse processo de destruição de objetos locais e de chamada de destruidores é chamado de desenrolamento de pilha.
Você tem os seguintes problemas relacionados ao desenrolamento de pilhas:
evitando vazamentos de memória (qualquer coisa alocada dinamicamente que não seja gerenciada por um objeto local e limpa no destruidor será vazada) - consulte RAII referido por Nikolai e a documentação para boost :: scoped_ptr ou este exemplo do uso de boost :: mutex :: scoped_lock .
consistência do programa: as especificações do C ++ afirmam que você nunca deve lançar uma exceção antes que qualquer exceção existente seja tratada. Isso significa que o processo de desenrolamento de pilha nunca deve gerar uma exceção (use apenas código garantido para não gerar destruidores ou envolva tudo nos destruidores com try {
e } catch(...) {}
).
Se algum destruidor lança uma exceção durante o desenrolamento da pilha, você acaba em um comportamento indefinido que pode levar o seu programa a terminar inesperadamente (comportamento mais comum) ou o universo a terminar (teoricamente possível, mas ainda não foi observado na prática).
Em um sentido geral, uma pilha "desenrolar" é praticamente sinônimo do final de uma chamada de função e do subsequente surgimento da pilha.
No entanto, especificamente no caso do C ++, o desenrolamento da pilha tem a ver com a maneira como o C ++ chama os destruidores dos objetos alocados desde o início de qualquer bloco de código. Os objetos que foram criados dentro do bloco são desalocados na ordem inversa de sua alocação.
try
blocos. Objetos de pilha alocados em qualquer bloco ( try
sujeito ou não) estão sujeitos a desenrolar quando o bloco sai.
O desenrolamento de pilha é um conceito principalmente de C ++, que trata de como os objetos alocados à pilha são destruídos quando o escopo é encerrado (normalmente, ou através de uma exceção).
Digamos que você tenha este fragmento de código:
void hw() {
string hello("Hello, ");
string world("world!\n");
cout << hello << world;
} // at this point, "world" is destroyed, followed by "hello"
Ainda não sei se você leu isso, mas o artigo da Wikipedia sobre a pilha de chamadas tem uma explicação decente.
Desenrolamento:
Retornar da função chamada exibirá o quadro superior da pilha, talvez deixando um valor de retorno. O ato mais geral de remover um ou mais quadros da pilha para retomar a execução em outro local do programa é chamado de desenrolamento de pilha e deve ser executado quando estruturas de controle não locais forem usadas, como aquelas usadas para manipulação de exceções. Nesse caso, o quadro de pilha de uma função contém uma ou mais entradas que especificam manipuladores de exceção. Quando uma exceção é lançada, a pilha é desenrolada até que seja encontrado um manipulador preparado para manipular (capturar) o tipo da exceção lançada.
Algumas linguagens possuem outras estruturas de controle que requerem desenrolamento geral. Pascal permite que uma instrução goto global transfira o controle de uma função aninhada para uma função externa anteriormente invocada. Esta operação requer que a pilha seja desenrolada, removendo quantos quadros de pilha forem necessários para restaurar o contexto apropriado para transferir o controle para a instrução de destino na função externa que o envolve. Da mesma forma, C possui as funções setjmp e longjmp que atuam como gotos não locais. O Lisp comum permite controlar o que acontece quando a pilha é desenrolada usando o operador especial de proteção contra desenrolamento.
Ao aplicar uma continuação, a pilha é (logicamente) desenrolada e depois rebobinada com a pilha da continuação. Esta não é a única maneira de implementar continuações; por exemplo, usando várias pilhas explícitas, a aplicação de uma continuação pode simplesmente ativar sua pilha e dar um valor a ser passado. A linguagem de programação Scheme permite que thunks arbitrários sejam executados em pontos especificados ao "desenrolar" ou "rebobinar" a pilha de controle quando uma continuação é chamada.
Inspeção
Eu li um post que me ajudou a entender.
O que é desenrolamento de pilha?
Em qualquer idioma que suporte funções recursivas (ou seja, praticamente tudo, exceto Fortran 77 e Brainf * ck), o tempo de execução do idioma mantém uma pilha das funções que estão sendo executadas no momento. Desenrolar a pilha é uma maneira de inspecionar e possivelmente modificar essa pilha.
Por que você gostaria de fazer isso?
A resposta pode parecer óbvia, mas existem várias situações relacionadas, mas sutilmente diferentes, em que o desenrolar é útil ou necessário:
- Como um mecanismo de controle de fluxo de tempo de execução (exceções C ++, C longjmp (), etc).
- Em um depurador, para mostrar ao usuário a pilha.
- Em um criador de perfil, para obter uma amostra da pilha.
- Do próprio programa (como de um manipulador de falhas para mostrar a pilha).
Estes têm requisitos sutilmente diferentes. Alguns deles são críticos para o desempenho, outros não. Alguns requerem a capacidade de reconstruir registros a partir do quadro externo, outros não. Mas entraremos em tudo isso em um segundo.
Você pode encontrar o post completo aqui .
Todo mundo falou sobre o tratamento de exceções em C ++. Mas acho que há outra conotação para o desenrolar da pilha e isso está relacionado à depuração. Um depurador precisa desbobinar a pilha sempre que deve ir para um quadro anterior ao quadro atual. No entanto, isso é uma espécie de desenrolamento virtual, pois precisa retroceder quando voltar ao quadro atual. O exemplo para isso pode ser comandos up / down / bt no gdb.
Na IMO, o diagrama abaixo deste artigo explica lindamente o efeito do desenrolamento de pilha na rota da próxima instrução (a ser executada quando uma exceção é lançada e não capturada):
Na foto:
No segundo caso, quando ocorre uma exceção, a pilha de chamadas de função é pesquisada linearmente para o manipulador de exceções. A pesquisa termina na função com o manipulador de exceções, ou seja, main()
com o try-catch
bloco anexo , mas não antes de remover todas as entradas anteriores da pilha de chamadas de função.
O tempo de execução do C ++ destrói todas as variáveis automáticas criadas entre o throw & catch. Neste exemplo simples abaixo, lances de f1 () e capturas principais (), entre objetos do tipo B e A são criados na pilha nessa ordem. Quando f1 () joga, os destruidores de B e A são chamados.
#include <iostream>
using namespace std;
class A
{
public:
~A() { cout << "A's dtor" << endl; }
};
class B
{
public:
~B() { cout << "B's dtor" << endl; }
};
void f1()
{
B b;
throw (100);
}
void f()
{
A a;
f1();
}
int main()
{
try
{
f();
}
catch (int num)
{
cout << "Caught exception: " << num << endl;
}
return 0;
}
A saída deste programa será
B's dtor
A's dtor
Isso ocorre porque a pilha de chamadas do programa quando f1 () lança parece
f1()
f()
main()
Portanto, quando f1 () é acionada, a variável automática b é destruída e, quando f () é acionada, a variável automática a é destruída.
Espero que isso ajude, codificação feliz!
Quando uma exceção é lançada e o controle passa de um bloco try para um manipulador, o tempo de execução do C ++ chama destruidores para todos os objetos automáticos construídos desde o início do bloco try. Esse processo é chamado de desenrolamento de pilha. Os objetos automáticos são destruídos na ordem inversa de sua construção. (Objetos automáticos são objetos locais que foram declarados automaticamente ou registrados, ou não declarados estáticos ou externos. Um objeto automático x é excluído sempre que o programa sai do bloco em que x é declarado.)
Se uma exceção for lançada durante a construção de um objeto que consiste em subobjetos ou elementos da matriz, os destruidores serão chamados apenas para esses subobjetos ou elementos da matriz construídos com sucesso antes da exceção ser lançada. Um destruidor para um objeto estático local será chamado apenas se o objeto tiver sido construído com êxito.
Na pilha Java, desatar ou desenrolar não é muito importante (com coletor de lixo). Em muitos documentos sobre manipulação de exceções, eu vi esse conceito (desenrolamento de pilha), em especial esses criadores lidam com a manipulação de exceções em C ou C ++. com try catch
blocos que não devemos esquecer: pilha livre de todos os objetos após blocos locais .