O que é desenrolamento de pilha?

193

O que é desenrolamento de pilha? Pesquisou, mas não conseguiu encontrar uma resposta esclarecedora!

Rajendra Uppal
fonte
76
Se ele não sabe o que é, como você pode esperar que ele saiba que eles não são iguais para C e C ++?
dreamlax
@dreamlax: Então, como o conceito de "pilha desenrolando" é diferente em C & C ++?
Destructor
2
@PravasiMeet: C não possui manipulação de exceção; portanto, o desenrolamento de pilha é muito direto; no entanto, em C ++, se uma exceção é lançada ou uma função sai, o desenrolamento de pilha envolve a destruição de qualquer objeto C ++ com duração de armazenamento automático.
dreamlax

Respostas:

150

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 pleakserá perdida se uma exceção for lançada, enquanto a memória alocada para sserá liberada corretamente pelo std::stringdestruidor 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 .

Nikolai Fetissov
fonte
Isso foi realmente esclarecedor! Então, entendi o seguinte: se meu processo travar inesperadamente durante a saída de QUALQUER bloco no momento em que a pilha estava sendo exibida, pode acontecer que o código após o código do manipulador de exceção não seja executado de maneira alguma e pode causar vazamento de memória, corrupção de pilha etc.
Rajendra Uppal
15
Se o programa "travar" (ou seja, terminar devido a um erro), qualquer vazamento de memória ou corrupção de heap é irrelevante, pois a memória é liberada no término.
Tyler McHenry
1
Exatamente. Obrigado. Eu só estou sendo um pouco disléxico hoje.
Nikolai Fetissov
11
@TylerMcHenry: O padrão não garante que recursos ou memória sejam liberados na finalização. No entanto, a maioria dos sistemas operacionais faz isso.
Mooing Duck
3
delete [] pleak;só é alcançado se x == 0.
Jib
71

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:

  1. 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 .

  2. 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).

utnapistim
fonte
2
Pelo contrário. Embora os gotos não devam ser abusados, eles causam o desenrolamento da pilha no MSVC (não no GCC, portanto é provavelmente uma extensão). setjmp e longjmp fazem isso de uma maneira multiplataforma, com um pouco menos de flexibilidade.
Patrick Niedzielski
10
Acabei de testar isso com o gcc e ele chama corretamente os destruidores quando você sai de um bloco de código. Consulte stackoverflow.com/questions/334780/… - conforme mencionado nesse link, isso também faz parte do padrão.
Damyan
1
lendo Nikolai, jrista e sua resposta nesta ordem, agora faz sentido!
N611x007
@sashoalm Você realmente acha que é necessário editar uma postagem sete anos depois?
David Hoelzer
41

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.

jrista
fonte
4
Não há nada de especial nos tryblocos. Objetos de pilha alocados em qualquer bloco ( trysujeito ou não) estão sujeitos a desenrolar quando o bloco sai.
Chris Jester-Young
Já faz um tempo desde que eu fiz muita codificação em C ++. Eu tive que cavar essa resposta das profundezas enferrujadas. ; P
jrista 25/02
Não se preocupe. Todo mundo tem "seu mal" ocasionalmente.
bitc 28/05
13

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"
Chris Jester-Young
fonte
Isso se aplica a algum bloco? Quero dizer, se houver apenas {// alguns objetos locais} #
Rajendra Uppal
@Rajendra: Sim, um bloco anônimo define uma área de escopo, por isso conta também.
Michael Myers
12

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

John Weldon
fonte
9

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:

  1. Como um mecanismo de controle de fluxo de tempo de execução (exceções C ++, C longjmp (), etc).
  2. Em um depurador, para mostrar ao usuário a pilha.
  3. Em um criador de perfil, para obter uma amostra da pilha.
  4. 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 .

L. Langó
fonte
7

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.

bbv
fonte
5
A ação do depurador é normalmente chamada de "Caminhada na pilha", que é simplesmente analisando a pilha. "Desenrolar a pilha" implica não apenas "Caminhar na pilha", mas também chamar os destruidores de objetos que existem na pilha.
Adisak 9/09/13
@ Adisak Eu não sabia, também é chamado de "stack walking". Eu sempre vi "pilha desenrolando" no contexto de todos os artigos do depurador e também dentro do código gdb. Eu senti que "desenrolar a pilha" é mais apropriado, pois não se trata apenas de espreitar as informações da pilha para todas as funções, mas envolve desenrolar as informações do quadro (cf. CFI no anão). Isso é processado na ordem de uma função por uma.
bbv
Eu acho que o "stack walking" se tornou mais famoso pelo Windows. Além disso, encontrei como exemplo code.google.com/p/google-breakpad/wiki/StackWalking, além do próprio documento do padrão anão, que usa o termo desenrolamento algumas vezes. Embora concorde, é descontraído virtual. Além disso, a pergunta parece estar pedindo todo significado possível que "desenrolar a pilha" possa sugerir.
bbv
7

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):

insira a descrição da imagem aqui

Na foto:

  • A primeira é uma execução normal de chamada (sem exceção).
  • Inferior quando uma exceção é lançada.

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-catchbloco anexo , mas não antes de remover todas as entradas anteriores da pilha de chamadas de função.

Saurav Sahu
fonte
Os diagramas são bons, mas a explicação é um pouco confusa. ... com anexando bloco try-catch, mas não antes de remover todas as entradas antes da pilha de chamada de função ...
Atul
3

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!

DigitalEye
fonte
2

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.

MK.
fonte
Você deve fornecer um link para o artigo original em que copiou esta resposta: IBM Knowledge Base
Desenrolamento de
0

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 catchblocos que não devemos esquecer: pilha livre de todos os objetos após blocos locais .

user2568330
fonte