|
1 | | -# 14 - Stack e Heap |
| 1 | +# 14 - Stack e Heap |
| 2 | + |
| 3 | +A memória que um programa usa é normalmente dividida em algumas áreas diferentes, chamadas segmentos: |
| 4 | + |
| 5 | +- O segmento de código (também chamado de segmento de texto), onde o programa compilado fica na memória. O segmento de código normalmente é somente leitura. |
| 6 | +- O segmento bss (também chamado de segmento de dados não inicializado), onde variáveis globais e estáticas inicializadas com zero são armazenadas. |
| 7 | +- O segmento de dados (também chamado de segmento de dados inicializado), onde as variáveis globais e estáticas inicializadas são armazenadas. |
| 8 | +- O heap, de onde variáveis alocadas dinamicamente são alocadas. |
| 9 | +- A stack(pilha) de chamadas, onde são armazenados os parâmetros da função, variáveis locais e outras informações relacionadas à função. |
| 10 | +Nesta lição, focaremos principalmente a heap e a stack, pois é aí que a maioria das coisas interessantes ocorre. |
| 11 | + |
| 12 | +O segmento de heap (também conhecido como "armazenamento gratuito") controla a memória usada para alocação dinâmica de memória. Utilizamos ela na seção passada: |
| 13 | + |
| 14 | +```cpp{0} |
| 15 | +int *ponteiro = new int; // ptr is assigned 4 bytes in the heap |
| 16 | +int *vetor = new int[10]; // array is assigned 40 bytes in the heap |
| 17 | +
|
| 18 | +delete ponteiro; |
| 19 | +delete vetor; |
| 20 | +``` |
| 21 | + |
| 22 | +Quando uma variável alocada dinamicamente é excluída, a memória é "retornada" para o heap e pode ser reatribuída à medida que futuras solicitações de alocação forem recebidas. Lembre-se de que excluir um ponteiro não exclui a variável, apenas retorna a memória no endereço associado ao sistema operacional. |
| 23 | + |
| 24 | +A stack tem vantagens e desvantagens: |
| 25 | + |
| 26 | +- A alocação de memória no heap é comparativamente lenta. |
| 27 | +- A memória alocada permanece alocada até que seja desalocada especificamente (cuidado com vazamentos de memória) ou o aplicativo termina (nesse ponto, o SO deve limpá-la). |
| 28 | +- A memória alocada dinamicamente deve ser acessada por meio de um ponteiro. Desreferenciar um ponteiro é mais lento do que acessar diretamente uma variável. |
| 29 | +Como o heap é um grande pool de memória, matrizes, estruturas ou classes grandes podem ser alocadas aqui. |
| 30 | + |
| 31 | +# A stack de chamadas |
| 32 | + |
| 33 | +A stack de chamadas (geralmente chamada de "a pilha") tem um papel muito mais interessante a desempenhar. A pilha de chamadas controla todas as funções ativas (aquelas que foram chamadas, mas ainda não foram finalizadas) desde o início do programa até o ponto de execução atual e lida com a alocação de todos os parâmetros de função e variáveis locais. |
| 34 | + |
| 35 | +A pilha de chamadas é implementada como uma estrutura de dados da pilha. Portanto, antes de podermos falar sobre como a pilha de chamadas funciona, precisamos entender o que é uma estrutura de dados da pilha. |
| 36 | + |
| 37 | +Uma estrutura de dados é um mecanismo de programação para organizar dados, para que possam ser usados com eficiência. Você já viu vários tipos de estruturas de dados, como matrizes e estruturas. Ambas as estruturas de dados fornecem mecanismos para armazenar dados e acessar esses dados de maneira eficiente. Existem muitas estruturas de dados adicionais que são comumente usadas em programação, algumas das quais são implementadas na biblioteca padrão e uma pilha é uma delas. |
| 38 | + |
| 39 | +# Funcionamento de uma Pilha |
| 40 | + |
| 41 | +- Olhe para o item superior da pilha(geralmente feito através de uma função chamada top(), mas às vezes chamada de peek()) |
| 42 | +- Retire o item principal da pilha(feito por meio de uma função chamada pop()) |
| 43 | +- Coloque um novo item no topo da pilha(feito através de uma função chamada push()) |
| 44 | + |
| 45 | +| Pilha | Saida | |
| 46 | +| ----- | ----- | |
| 47 | +| Stack | Vazia | |
| 48 | +| Push | 1 | |
| 49 | +| Stack | 1 | |
| 50 | +| Push | 2 | |
| 51 | +| Stack | 1 2 | |
| 52 | +| Push | 3 | |
| 53 | +| Stack | 1 2 3 | |
| 54 | +| Pop | | |
| 55 | +| Push | 1 2 | |
| 56 | +| Pop | | |
| 57 | +| Stack | 1 | |
| 58 | + |
| 59 | +# O segmento da stack de chamadas |
| 60 | + |
| 61 | +O segmento da pilha de chamadas mantém a memória usada para a pilha de chamadas. Quando o aplicativo é iniciado, a função main () é pressionada na pilha de chamadas pelo sistema operacional. Então o programa começa a executar. |
| 62 | + |
| 63 | +Quando uma chamada de função é encontrada, a função é enviada para a pilha de chamadas. Quando a função atual termina, essa função é retirada da pilha de chamadas. Assim, observando as funções pressionadas na pilha de chamadas, podemos ver todas as funções que foram chamadas para chegar ao ponto de execução atual. |
| 64 | + |
| 65 | +Nossa analogia da caixa de correio acima é bastante semelhante à maneira como a pilha de chamadas funciona. A própria pilha é um pedaço de tamanho fixo de endereços de memória. As caixas de correio são endereços de memória e os "itens" que estamos empurrando e popping na pilha são chamados de quadros de pilha. Um quadro de pilha controla todos os dados associados a uma chamada de função. Falaremos mais sobre os quadros de pilha em breve. O "marcador" é um registro (um pequeno pedaço de memória na CPU) conhecido como ponteiro da pilha (às vezes abreviado como "SP"). O ponteiro da pilha controla onde está atualmente a parte superior da pilha de chamadas. |
| 66 | + |
| 67 | +A única diferença entre nossa pilha hipotética de caixa de correio e a pilha de chamadas é que, quando retiramos um item da pilha de chamadas, não precisamos apagar a memória (o equivalente a esvaziar a caixa de correio). Podemos apenas deixá-lo sobrescrito pelo próximo item enviado para esse pedaço de memória. Como o ponteiro da pilha estará abaixo desse local da memória, sabemos que o local da memória não está na pilha. |
| 68 | + |
| 69 | +# A pilha de chamadas em ação |
| 70 | + |
| 71 | +Vamos examinar mais detalhadamente como a pilha de chamadas funciona. Aqui está a sequência de etapas que ocorre quando uma função é chamada: |
| 72 | + |
| 73 | +O programa encontra uma chamada de função. |
| 74 | +Um quadro de pilha é construído e empurrado na pilha. O quadro da pilha consiste em: |
| 75 | +O endereço da instrução além da chamada da função (chamado endereço de retorno). É assim que a CPU se lembra para onde retornar após a saída da função chamada. |
| 76 | +Todos os argumentos da função. |
| 77 | +Memória para quaisquer variáveis locais. |
| 78 | +Cópias salvas de quaisquer registros modificados pela função que precisam ser restauradas quando a função retornar |
| 79 | +A CPU pula para o ponto inicial da função. |
| 80 | +As instruções dentro da função começam a ser executadas. |
| 81 | +Quando a função termina, ocorrem as seguintes etapas: |
| 82 | + |
| 83 | +Os registros são restaurados da pilha de chamadas |
| 84 | +O quadro da pilha é retirado da pilha. Isso libera a memória para todas as variáveis e argumentos locais. |
| 85 | +O valor de retorno é tratado. |
| 86 | +A CPU retoma a execução no endereço de retorno. |
| 87 | +Os valores de retorno podem ser manipulados de várias maneiras diferentes, dependendo da arquitetura do computador. Algumas arquiteturas incluem o valor de retorno como parte do quadro da pilha. Outros usam registradores de CPU. |
| 88 | + |
| 89 | +Normalmente, não é importante conhecer todos os detalhes sobre como a pilha de chamadas funciona. No entanto, o entendimento de que as funções são efetivamente colocadas na pilha quando são chamadas e ativadas quando retornam fornece os fundamentos necessários para entender a recursão, além de alguns outros conceitos úteis na depuração. |
| 90 | + |
| 91 | +Uma observação técnica: em algumas arquiteturas, a pilha de chamadas cresce para longe do endereço de memória 0. Em outras, cresce para o endereço de memória 0. Como conseqüência, os novos quadros de pilha enviados podem ter um endereço de memória maior ou menor que os anteriores. |
| 92 | + |
| 93 | +Exemplo: |
| 94 | + |
| 95 | +```cpp{0} |
| 96 | +int foo(int x) |
| 97 | +{ |
| 98 | + // b |
| 99 | + return x; |
| 100 | +} // foo is popped off the call stack here |
| 101 | + |
| 102 | +int main() |
| 103 | +{ |
| 104 | + // a |
| 105 | + foo(5); // a função foo foi chamada no push() para a pilha |
| 106 | + // c |
| 107 | + |
| 108 | + return 0; |
| 109 | +} |
| 110 | +``` |
| 111 | + |
| 112 | +Estouro de pilha(Não é piada com o site. juro.) |
| 113 | + |
| 114 | +A pilha tem um tamanho limitado e, consequentemente, pode conter apenas uma quantidade limitada de informações. No Windows, o tamanho padrão da pilha é de 1 MB. Em algumas máquinas unix, pode ter até 8 MB. Se o programa tentar colocar muitas informações na pilha, resultará um excesso de pilha. O estouro de pilha acontece quando toda a memória da pilha foi alocada - nesse caso, alocações adicionais começam a transbordar para outras seções da memória. |
| 115 | + |
| 116 | +O estouro de pilha geralmente é o resultado da alocação de muitas variáveis na pilha e / ou de muitas chamadas de funções aninhadas (onde a função A chama a função B chama a função C chama a função D etc ...) Nos sistemas operacionais modernos, o excesso da pilha geralmente faça com que seu sistema operacional emita uma violação de acesso e encerre o programa. |
| 117 | + |
| 118 | +Aqui está um exemplo de programa que provavelmente causará um estouro de pilha. Você pode executá-lo em seu sistema e assisti-lo travar: |
| 119 | + |
| 120 | +```cpp{0} |
| 121 | +#include <iostream> |
| 122 | + |
| 123 | +int main() |
| 124 | +{ |
| 125 | + int stack[10000000]; |
| 126 | + std::cout << "hi"; |
| 127 | + return 0; |
| 128 | +} |
| 129 | +``` |
| 130 | + |
| 131 | +A classe `std::vector` faz muito bem do uso da pilha, recomendado a se utilizar em vés do vetor normal, assim como a classe `std::array` |
0 commit comments