Como ocorre um estouro de pilha e quais são as melhores maneiras de garantir que isso não aconteça, ou formas de evitá-lo, principalmente em servidores da web, mas outros exemplos também seriam interessantes?
fonte
Como ocorre um estouro de pilha e quais são as melhores maneiras de garantir que isso não aconteça, ou formas de evitá-lo, principalmente em servidores da web, mas outros exemplos também seriam interessantes?
Uma pilha, neste contexto, é o último a entrar, primeiro a sair do buffer onde você coloca os dados enquanto o programa é executado. Último a entrar, primeiro a sair (LIFO) significa que a última coisa que você coloca é sempre a primeira coisa que você retira - se você empurrar 2 itens na pilha, 'A' e depois 'B', então a primeira coisa que você pop fora da pilha será 'B', e a próxima coisa é 'A'.
Quando você chama uma função em seu código, a próxima instrução após a chamada de função é armazenada na pilha e qualquer espaço de armazenamento que possa ser substituído pela chamada de função. A função que você chama pode usar mais pilha para suas próprias variáveis locais. Quando terminar, ele libera o espaço de pilha da variável local que usou e retorna à função anterior.
Um estouro de pilha é quando você usou mais memória para a pilha do que seu programa deveria usar. Em sistemas embarcados, você pode ter apenas 256 bytes para a pilha, e se cada função ocupar 32 bytes, então você só pode ter chamadas de função 8 profundas - a função 1 chama a função 2 que chama a função 3 quem chama a função 4 .... quem chama a função 8 que chama a função 9, mas a função 9 sobrescreve a memória fora da pilha. Isso pode substituir a memória, o código, etc.
Muitos programadores cometem esse erro ao chamar a função A que então chama a função B, que então chama a função C, que então chama a função A. Pode funcionar na maioria das vezes, mas apenas uma vez a entrada errada fará com que ela vá para aquele círculo para sempre até que o computador reconheça que a pilha está exagerada.
Funções recursivas também são uma causa para isso, mas se você estiver escrevendo recursivamente (ou seja, sua função chama a si mesma), você precisa estar ciente disso e usar variáveis estáticas / globais para evitar recursão infinita.
Geralmente, o sistema operacional e a linguagem de programação que você está usando gerenciam a pilha e ela está fora de seu controle. Você deve olhar seu gráfico de chamadas (uma estrutura de árvore que mostra o que cada função chama) para ver a profundidade de suas chamadas de função e para detectar ciclos e recursões indesejados. Os ciclos intencionais e a recursão precisam ser verificados artificialmente para que ocorram erros se eles se chamarem muitas vezes.
Além de boas práticas de programação, testes estáticos e dinâmicos, não há muito que você possa fazer nesses sistemas de alto nível.
No mundo incorporado, especialmente em código de alta confiabilidade (automotivo, aeronaves, espaço), você faz análises e verificações de código extensas, mas também faz o seguinte:
Mas em linguagens de alto nível executadas em sistemas operacionais:
Depende da 'caixa de areia' que você tem se você pode controlar ou mesmo ver a pilha. Há boas chances de você tratar os servidores da web como faria com qualquer outra linguagem e sistema operacional de alto nível - está em grande parte fora de suas mãos, mas verifique a linguagem e a pilha do servidor que está usando. Ele é possível explodir a pilha no seu servidor SQL, por exemplo.
-Adão
Um estouro de pilha em código real ocorre muito raramente. A maioria das situações em que ocorre são recursões em que o encerramento foi esquecido. No entanto, pode ocorrer raramente em estruturas altamente aninhadas, por exemplo, documentos XML particularmente grandes. A única ajuda real aqui é refatorar o código para usar um objeto de pilha explícito em vez da pilha de chamadas.
A maioria das pessoas dirá que um estouro de pilha ocorre com recursão sem um caminho de saída - embora seja verdade, se você trabalhar com estruturas de dados grandes o suficiente, mesmo um caminho de saída de recursão adequado não o ajudará.
Algumas opções neste caso:
Um estouro de pilha ocorre quando Jeff e Joel querem dar ao mundo um lugar melhor para obter respostas a perguntas técnicas. É tarde demais para evitar esse estouro de pilha. Esse "outro site" poderia ter evitado por não ser desagradável. ;)
A recursão infinita é uma maneira comum de obter um erro de estouro de pilha. Para prevenir - sempre certifique-se de que haja um caminho de saída que será atingido. :-)
Outra maneira de obter um estouro de pilha (em C / C ++, pelo menos) é declarar alguma variável enorme na pilha.
char hugeArray[100000000];
Isso basta.
Normalmente, um estouro de pilha é o resultado de uma chamada recursiva infinita (dada a quantidade usual de memória em computadores padrão hoje em dia).
Quando você faz uma chamada para um método, função ou procedimento, a maneira "padrão" ou fazer a chamada consiste em:
Então, normalmente isso leva alguns bytes dependendo do número e tipo dos parâmetros, bem como da arquitetura da máquina.
Você verá então que, se começar a fazer chamadas recursivas, a pilha começará a crescer. Agora, a pilha é normalmente reservada na memória de forma que ela cresça na direção oposta à pilha, então, dado um grande número de chamadas sem "voltar", a pilha começa a ficar cheia.
Agora, em tempos mais antigos, o estouro de pilha poderia ocorrer simplesmente porque você exauriu toda a memória disponível, simplesmente assim. Com o modelo de memória virtual (até 4 GB em um sistema X86) que estava fora do escopo, normalmente, se você receber um erro de estouro de pilha, procure uma chamada recursiva infinita.
fonte
O que? Ninguém tem amor por aqueles envolvidos por um loop infinito?
fonte
Além da forma de estouro de pilha que você obtém de uma recursão direta (por exemplo
Fibonacci(1000000)
), uma forma mais sutil que experimentei muitas vezes é uma recursão indireta, onde uma função chama outra função, que chama outra, e então uma de essas funções chama a primeira novamente.Isso pode ocorrer comumente em funções que são chamadas em resposta a eventos, mas que podem gerar novos eventos, por exemplo:
Nesse caso, a chamada para
ResizeWindow
pode fazer com que oWindowSizeChanged()
retorno de chamada seja disparado novamente, o que chamaResizeWindow
novamente, até que você fique sem pilha. Em situações como essas, você geralmente precisa adiar a resposta ao evento até que o frame da pilha seja retornado, por exemplo, postando uma mensagem.fonte
Considerando que isso foi marcado com "hacking", suspeito que o "estouro de pilha" a que ele se refere é um estouro de pilha de chamadas, em vez de um estouro de pilha de nível superior, como aqueles mencionados na maioria das outras respostas aqui. Na verdade, não se aplica a nenhum ambiente gerenciado ou interpretado como .NET, Java, Python, Perl, PHP, etc, nos quais os aplicativos da web são normalmente escritos, então seu único risco é o próprio servidor da web, que provavelmente está escrito em C ou C ++.
Confira esta discussão:
/programming/7308/what-is-a-good-starting-point-for-learning-buffer-overflow
fonte
Recriei o problema de estouro de pilha ao obter um número de Fibonacci mais comum, ou seja, 1, 1, 2, 3, 5 ..... portanto, cálculo para fib (1) = 1 ou fib (3) = 2 .. fib (n ) = ??.
para n, digamos que estamos interessados - e se n = 100.000, então qual será o número de Fibonacci correspondente ??
A abordagem de um loop é a seguinte -
isso é bastante simples e o resultado é -
Agora, outra abordagem que apliquei é por meio de Divide and Concur via recursão
ou seja, Fib (n) = fib (n-1) + Fib (n-2) e, em seguida, recursão adicional para n-1 & n-2 ..... até 2 e 1. que é programado como -
Quando executei o código para n = 100.000, o resultado é o seguinte -
Acima você pode ver que StackOverflowError foi criado. Agora, a razão para isso é muita recursão como -
Assim, cada entrada na pilha cria mais 2 entradas e assim por diante ... que é representado como -
Eventualmente, tantas entradas serão criadas que o sistema não será capaz de lidar com a pilha e StackOverflowError lançado.
Para Prevenção: Para a perspectiva do exemplo Acima - 1. Evite usar a abordagem de recursão ou reduza / limite a recursão novamente em uma divisão de nível, como se n for muito grande, então divida n para que o sistema possa lidar com seu limite. 2. Use outra abordagem, como a abordagem de loop que usei no primeiro exemplo de código. (Não tenho a intenção de degradar Divide & Concur ou Recursion, pois são abordagens lendárias em muitos algoritmos mais famosos ... minha intenção é limitar ou ficar longe da recursão se eu suspeitar de problemas de estouro de pilha)
fonte