Os livros de linguagens de programação explicam que os tipos de valor são criados na pilha e os tipos de referência são criados no heap , sem explicar o que são essas duas coisas. Eu não li uma explicação clara disso. Eu entendo o que é uma pilha . Mas,
- Onde e o que eles estão (fisicamente na memória de um computador real)?
- Até que ponto eles são controlados pelo SO ou pelo tempo de execução do idioma?
- Qual é o seu escopo?
- O que determina o tamanho de cada um deles?
- O que torna um mais rápido?
rlimit_stack
. Consulte também a Edição 1463241 daRespostas:
A pilha é a memória reservada como espaço temporário para um encadeamento de execução. Quando uma função é chamada, um bloco é reservado no topo da pilha para variáveis locais e alguns dados da contabilidade. Quando essa função retorna, o bloco fica sem uso e pode ser usado na próxima vez que uma função for chamada. A pilha é sempre reservada em uma ordem LIFO (último a entrar, primeiro a sair); o bloco reservado mais recentemente é sempre o próximo bloco a ser liberado. Isso torna muito simples acompanhar a pilha; libertar um bloco da pilha nada mais é do que ajustar um ponteiro.
O heap é a memória reservada para alocação dinâmica. Ao contrário da pilha, não há padrão imposto para a alocação e desalocação de blocos do heap; você pode alocar um bloco a qualquer momento e liberá-lo a qualquer momento. Isso torna muito mais complexo acompanhar quais partes do heap estão alocadas ou livres a qualquer momento; existem muitos alocadores de heap personalizados disponíveis para ajustar o desempenho do heap para diferentes padrões de uso.
Cada encadeamento obtém uma pilha, enquanto normalmente há apenas um heap para o aplicativo (embora não seja incomum ter vários heap para diferentes tipos de alocação).
Para responder suas perguntas diretamente:
O SO aloca a pilha para cada encadeamento no nível do sistema quando o encadeamento é criado. Normalmente, o sistema operacional é chamado pelo runtime do idioma para alocar o heap para o aplicativo.
A pilha é anexada a um encadeamento, portanto, quando o encadeamento sai, a pilha é recuperada. O heap geralmente é alocado na inicialização do aplicativo pelo tempo de execução e é recuperado quando o aplicativo (tecnicamente processo) sai.
O tamanho da pilha é definido quando um segmento é criado. O tamanho do heap é definido na inicialização do aplicativo, mas pode aumentar conforme o espaço necessário (o alocador solicita mais memória do sistema operacional).
A pilha é mais rápida porque o padrão de acesso torna trivial alocar e desalocar memória (um ponteiro / número inteiro é simplesmente incrementado ou decrementado), enquanto o heap possui contabilidade muito mais complexa envolvida em uma alocação ou desalocação. Além disso, cada byte na pilha tende a ser reutilizado com muita frequência, o que significa que tende a ser mapeado para o cache do processador, tornando-o muito rápido. Outro problema de desempenho para o heap é que o heap, sendo principalmente um recurso global, geralmente precisa ser seguro com vários threads, ou seja, cada alocação e desalocação precisa ser - normalmente - sincronizada com "todos" outros acessos de heap no programa.
Uma demonstração clara:
Fonte da imagem: vikashazrati.wordpress.com
fonte
Pilha:
Montão:
delete
,delete[]
oufree
.new
oumalloc
respectivamente.Exemplo:
fonte
C
idioma, conforme definido peloC99
padrão de idioma (disponível em open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf ), exija uma "pilha". De fato, a palavra 'pilha' nem aparece no padrão. Isso responde às afirmações de queC
o uso da pilha wrt / to é verdadeiro em geral, mas não é de forma alguma exigido pelo idioma. Veja knosof.co.uk/cbook/cbook.html para mais informações e, em particular, comoC
é implementado em arquiteturas odd-ball como en.wikipedia.org/wiki/Burroughs_large_systemsO ponto mais importante é que heap e stack são termos genéricos de maneiras pelas quais a memória pode ser alocada. Eles podem ser implementados de várias maneiras diferentes, e os termos se aplicam aos conceitos básicos.
Em uma pilha de itens, os itens ficam um em cima do outro na ordem em que foram colocados lá, e você só pode remover o superior (sem derrubar a coisa toda).
A simplicidade de uma pilha é que você não precisa manter uma tabela contendo um registro de cada seção da memória alocada; as únicas informações de estado necessárias são um único ponteiro para o final da pilha. Para alocar e desalocar, você apenas incrementa e diminui esse ponteiro único. Nota: às vezes, uma pilha pode ser implementada para iniciar no topo de uma seção da memória e se estender para baixo em vez de crescer para cima.
Em uma pilha, não há uma ordem específica para a maneira como os itens são colocados. Você pode acessar e remover itens em qualquer ordem, porque não há um item 'superior' claro.
A alocação de heap requer a manutenção de um registro completo de qual memória está alocada e o que não é, além de alguma manutenção adicional para reduzir a fragmentação, encontrar segmentos de memória contíguos grandes o suficiente para caber no tamanho solicitado e assim por diante. A memória pode ser desalocada a qualquer momento, deixando espaço livre. Às vezes, um alocador de memória executa tarefas de manutenção, como desfragmentar a memória movendo a memória alocada ou coletando lixo - identificando em tempo de execução quando a memória não está mais no escopo e desalocando-a.
Essas imagens devem fazer um bom trabalho ao descrever as duas maneiras de alocar e liberar memória em uma pilha e uma pilha. Yum!
Até que ponto eles são controlados pelo SO ou pelo tempo de execução do idioma?
Como mencionado, heap e stack são termos gerais e podem ser implementados de várias maneiras. Os programas de computador normalmente têm uma pilha chamado de pilha de chamadas que armazena informações relevantes para a função atual, como um ponteiro para qualquer função que foi chamado, e quaisquer variáveis locais. Como as funções chamam outras funções e depois retornam, a pilha cresce e diminui para reter informações das funções mais abaixo na pilha de chamadas. Um programa realmente não tem controle de tempo de execução; é determinado pela linguagem de programação, sistema operacional e até pela arquitetura do sistema.
Heap é um termo geral usado para qualquer memória que é alocada dinâmica e aleatoriamente; ou seja, fora de ordem. A memória é normalmente alocada pelo sistema operacional, com o aplicativo chamando funções da API para fazer essa alocação. Há um pouco de sobrecarga necessária no gerenciamento de memória alocada dinamicamente, que geralmente é tratada pelo código de tempo de execução da linguagem ou ambiente de programação usado.
Qual é o seu escopo?
A pilha de chamadas é um conceito de nível tão baixo que não se relaciona com 'escopo' no sentido de programação. Se você desmontar algum código, verá referências relativas ao estilo do ponteiro às partes da pilha, mas no que diz respeito a uma linguagem de nível superior, a linguagem impõe suas próprias regras de escopo. Um aspecto importante de uma pilha, no entanto, é que, quando uma função retorna, qualquer coisa local para essa função é imediatamente liberada da pilha. Isso funciona da maneira que você esperaria, dado o funcionamento de suas linguagens de programação. Em um monte, também é difícil de definir. O escopo é o que for exposto pelo sistema operacional, mas sua linguagem de programação provavelmente adiciona suas regras sobre o que é um "escopo" em seu aplicativo. A arquitetura do processador e o sistema operacional usam endereçamento virtual, que o processador converte em endereços físicos e há falhas de página, etc. Eles controlam quais páginas pertencem a quais aplicativos. Porém, você nunca precisa se preocupar com isso, porque apenas usa o método que sua linguagem de programação usa para alocar e liberar memória e verifica se há erros (se a alocação / liberação falhar por qualquer motivo).
O que determina o tamanho de cada um deles?
Novamente, depende do idioma, compilador, sistema operacional e arquitetura. Uma pilha geralmente é pré-alocada, porque, por definição, deve ser memória contígua. O compilador de idiomas ou o sistema operacional determinam seu tamanho. Você não armazena grandes quantidades de dados na pilha, por isso será grande o suficiente para nunca ser totalmente utilizado, exceto em casos de recursão indesejada e interminável (portanto, "estouro de pilha") ou outras decisões de programação incomuns.
Um heap é um termo geral para qualquer coisa que possa ser alocada dinamicamente. Dependendo de como você olha para ele, ele muda constantemente de tamanho. De qualquer maneira, nos processadores e sistemas operacionais modernos, a maneira exata como ela funciona é muito abstrata, então você normalmente não precisa se preocupar muito com a forma como ela funciona no fundo, exceto que (nos idiomas em que isso permite), você não deve usar memória que você ainda não alocou ou a memória liberada.
O que torna um mais rápido?
A pilha é mais rápida porque toda a memória livre é sempre contínua. Nenhuma lista precisa ser mantida de todos os segmentos da memória livre, apenas um ponteiro para o topo atual da pilha. Os compiladores geralmente armazenam esse ponteiro em um registro especial e rápido para esse fim. Além disso, as operações subsequentes em uma pilha geralmente são concentradas em áreas muito próximas da memória, o que em um nível muito baixo é bom para otimização pelos caches no processador.
fonte
(Eu mudei esta resposta de outra pergunta que era mais ou menos burra dessa.)
A resposta para sua pergunta é específica da implementação e pode variar entre os compiladores e as arquiteturas de processador. No entanto, aqui está uma explicação simplificada.
A pilha
new
oumalloc
) são atendidas criando um bloco adequado a partir de um dos blocos livres. Isso requer a atualização da lista de blocos na pilha. Essa meta-informação sobre os blocos na pilha também é armazenada na pilha frequentemente em uma pequena área logo à frente de cada bloco.A pilha
Não, os registros de ativação das funções (variáveis locais ou automáticas) são alocados na pilha usada não apenas para armazenar essas variáveis, mas também para controlar as chamadas de funções aninhadas.
Como o heap é gerenciado depende realmente do ambiente de tempo de execução. C usa
malloc
e C ++new
, mas muitas outras linguagens têm coleta de lixo.No entanto, a pilha é um recurso de nível mais baixo intimamente ligado à arquitetura do processador. Aumentar o heap quando não há espaço suficiente não é muito difícil, pois pode ser implementado na chamada da biblioteca que lida com o heap. No entanto, aumentar a pilha geralmente é impossível, pois o excesso de pilha só é descoberto quando é tarde demais; e desligar o encadeamento de execução é a única opção viável.
fonte
No seguinte código C #
Veja como a memória é gerenciada
Local Variables
isso só precisa durar enquanto a chamada da função for colocada na pilha. O heap é usado para variáveis cuja vida útil não conhecemos realmente, mas esperamos que elas durem um tempo. Na maioria dos idiomas, é fundamental sabermos, em tempo de compilação, qual o tamanho de uma variável, se queremos armazená-la na pilha.Os objetos (que variam em tamanho à medida que os atualizamos) ficam acumulados porque não sabemos no momento da criação quanto tempo eles durarão. Em muitos idiomas, o heap é coletado como lixo para localizar objetos (como o objeto cls1) que não possuem mais referências.
Em Java, a maioria dos objetos entra diretamente no heap. Em linguagens como C / C ++, estruturas e classes geralmente podem permanecer na pilha quando você não está lidando com ponteiros.
Mais informações podem ser encontradas aqui:
A diferença entre alocação de pilha e memória heap «timmurphy.org
e aqui:
Criando objetos na pilha e na pilha
Este artigo é a fonte da imagem acima: Seis conceitos importantes do .NET: Stack, heap, tipos de valor, tipos de referência, boxe e unboxing - CodeProject
mas esteja ciente de que pode conter algumas imprecisões.
fonte
A pilha Quando você chama uma função, os argumentos para essa função mais alguma outra sobrecarga são colocados na pilha. Algumas informações (como para onde ir no retorno) também são armazenadas lá. Quando você declara uma variável dentro de sua função, essa variável também é alocada na pilha.
Desalocar a pilha é bem simples, porque você sempre desaloca na ordem inversa em que aloca. O material da pilha é adicionado quando você insere funções, os dados correspondentes são removidos quando você os sai. Isso significa que você tende a permanecer em uma pequena região da pilha, a menos que chame muitas funções que chamam muitas outras funções (ou crie uma solução recursiva).
A pilha A pilha é um nome genérico para onde você coloca os dados que você cria em tempo real. Se você não souber quantas naves espaciais seu programa criará, provavelmente usará o novo operador (ou malloc ou equivalente) para criar cada nave espacial. Essa alocação permanecerá por um tempo, portanto é provável que liberemos as coisas em uma ordem diferente da que as criamos.
Assim, o heap é muito mais complexo, porque acaba havendo regiões de memória que não são utilizadas, intercaladas com pedaços que são - a memória fica fragmentada. Encontrar memória livre do tamanho necessário é um problema difícil. É por isso que o heap deve ser evitado (embora ainda seja usado com frequência).
Implementação A implementação da pilha e da pilha geralmente depende do tempo de execução / SO. Muitas vezes, jogos e outros aplicativos críticos para o desempenho criam suas próprias soluções de memória que capturam uma grande parte da memória da pilha e a distribuem internamente para evitar a dependência de memória do sistema operacional.
Isso só é prático se o uso da memória for bastante diferente da norma - ou seja, para jogos nos quais você carrega um nível em uma grande operação e pode jogar tudo em outra grande operação.
Localização física na memória Isso é menos relevante do que você pensa devido a uma tecnologia chamada Memória virtual que faz com que seu programa pense que você tem acesso a um determinado endereço onde os dados físicos estão em outro lugar (mesmo no disco rígido!). Os endereços que você obtém para a pilha estão em ordem crescente à medida que sua árvore de chamadas fica mais profunda. Os endereços da pilha são imprevisíveis (ou seja, específicos da implementação) e francamente não são importantes.
fonte
Para esclarecer, esta resposta tem informações incorretas ( thomas corrigiu sua resposta após comentários, legal :)). Outras respostas evitam explicar o que significa alocação estática. Então, explicarei as três principais formas de alocação e como elas geralmente se relacionam com o heap, a pilha e o segmento de dados abaixo. Também mostrarei alguns exemplos em C / C ++ e Python para ajudar as pessoas a entender.
Variáveis "estáticas" (AKA alocadas estaticamente) não são alocadas na pilha. Não assuma isso - muitas pessoas fazem isso apenas porque "estático" soa muito como "pilha". Na verdade, eles não existem nem na pilha nem na pilha. Eles fazem parte do chamado segmento de dados .
No entanto, geralmente é melhor considerar " escopo " e " tempo de vida " em vez de "empilhar" e "heap".
Escopo refere-se a quais partes do código podem acessar uma variável. Geralmente pensamos no escopo local (somente pode ser acessado pela função atual) versus no escopo global (pode ser acessado em qualquer lugar), embora o escopo possa se tornar muito mais complexo.
Vida útil refere-se a quando uma variável é alocada e desalocada durante a execução do programa. Geralmente pensamos em alocação estática (a variável persistirá durante toda a duração do programa, tornando-a útil para armazenar as mesmas informações em várias chamadas de função) versus alocação automática (a variável persiste apenas durante uma única chamada para uma função, tornando-a útil para armazenar informações que são usadas apenas durante a sua função e que podem ser descartadas quando você terminar) versus alocação dinâmica (variáveis cuja duração é definida no tempo de execução, em vez do tempo de compilação como estático ou automático).
Embora a maioria dos compiladores e intérpretes implementem esse comportamento da mesma forma em termos de uso de pilhas, pilhas, etc., um compilador às vezes pode quebrar essas convenções se desejar, desde que o comportamento esteja correto. Por exemplo, devido à otimização, uma variável local pode existir apenas em um registro ou ser removida totalmente, mesmo que a maioria das variáveis locais exista na pilha. Como foi apontado em alguns comentários, você é livre para implementar um compilador que nem usa pilha ou heap, mas alguns outros mecanismos de armazenamento (raramente são feitos, pois pilhas e pilhas são ótimas para isso).
Fornecerei um código C anotado simples para ilustrar tudo isso. A melhor maneira de aprender é executar um programa em um depurador e observar o comportamento. Se você preferir ler python, pule para o final da resposta :)
Um exemplo particularmente pungente de por que é importante distinguir entre vida útil e escopo é que uma variável pode ter escopo local, mas estático - por exemplo, "someLocalStaticVariable" no exemplo de código acima. Tais variáveis podem tornar nossos hábitos de nomenclatura comuns, mas informais, muito confusos. Por exemplo, quando dizemos " local ", geralmente queremos dizer " variável alocada automaticamente com escopo local " e quando dizemos global, geralmente queremos dizer " variável alocada estaticamente com escopo global ". Infelizmente, quando se trata de " variáveis alocadas estaticamente no escopo do arquivo ", muitas pessoas dizem ... " hein ??? ".
Algumas opções de sintaxe no C / C ++ exacerbam esse problema - por exemplo, muitas pessoas pensam que as variáveis globais não são "estáticas" devido à sintaxe mostrada abaixo.
Observe que colocar a palavra-chave "estático" na declaração acima impede que var2 tenha escopo global. No entanto, o var1 global tem alocação estática. Isso não é intuitivo! Por esse motivo, tento nunca usar a palavra "estática" ao descrever o escopo e, em vez disso, digo algo como o escopo "arquivo" ou "arquivo limitado". No entanto, muitas pessoas usam a frase "estático" ou "escopo estático" para descrever uma variável que só pode ser acessada a partir de um arquivo de código. No contexto da vida útil, "estático" sempre significa que a variável é alocada no início do programa e desalocada quando o programa é encerrado.
Algumas pessoas pensam nesses conceitos como específicos de C / C ++. Eles não são. Por exemplo, o exemplo do Python abaixo ilustra todos os três tipos de alocação (existem algumas diferenças sutis possíveis em linguagens interpretadas que não abordarei aqui).
fonte
PostScript
têm várias pilhas, mas possuem um "heap" que se comporta mais como uma pilha.Outros responderam muito bem aos traços largos, então vou dar alguns detalhes.
Pilha e pilha não precisam ser singulares. Uma situação comum em que você tem mais de uma pilha é se você tiver mais de um encadeamento em um processo. Nesse caso, cada thread tem sua própria pilha. Você também pode ter mais de um heap, por exemplo, algumas configurações de DLL podem resultar em DLLs diferentes alocadas de heaps diferentes, e é por isso que geralmente é uma má idéia liberar memória alocada por uma biblioteca diferente.
Em C, você pode obter o benefício da alocação de comprimento variável através do uso de alloca , que é alocado na pilha, em oposição à alocação, que é alocada no heap. Essa memória não sobreviverá à sua declaração de retorno, mas é útil para um buffer temporário.
Criar um buffer temporário enorme no Windows que você não usa muito não é gratuito. Isso ocorre porque o compilador gera um loop de detecção de pilha que é chamado toda vez que sua função é inserida para garantir que a pilha exista (porque o Windows usa uma única página de proteção no final da pilha para detectar quando precisa aumentar a pilha. Se você acessar a memória em mais de uma página no final da pilha, irá travar). Exemplo:
fonte
alloca
?Outros responderam diretamente à sua pergunta, mas, ao tentar entender a pilha e a pilha, acho útil considerar o layout da memória de um processo UNIX tradicional (sem threads e
mmap()
alocadores baseados em). A página da web Glossário de gerenciamento de memória possui um diagrama desse layout de memória.A pilha e o heap estão tradicionalmente localizados em extremidades opostas do espaço de endereço virtual do processo. A pilha cresce automaticamente quando acessada, até um tamanho definido pelo kernel (que pode ser ajustado com
setrlimit(RLIMIT_STACK, ...)
). O heap cresce quando o alocador de memória chama a chamadabrk()
ousbrk()
system, mapeando mais páginas de memória física no espaço de endereço virtual do processo.Em sistemas sem memória virtual, como alguns sistemas incorporados, o mesmo layout básico geralmente se aplica, exceto que a pilha e o heap são de tamanho fixo. No entanto, em outros sistemas incorporados (como os baseados nos microcontroladores Microchip PIC), a pilha de programas é um bloco de memória separado que não é endereçável por instruções de movimentação de dados e só pode ser modificado ou lido indiretamente através das instruções de fluxo de programa (chamada, retorno etc.). Outras arquiteturas, como os processadores Intel Itanium, têm várias pilhas . Nesse sentido, a pilha é um elemento da arquitetura da CPU.
fonte
O que é uma pilha?
Uma pilha é uma pilha de objetos, normalmente um que está organizado de maneira ordenada.
O que é uma pilha?
Uma pilha é uma coleção desarrumada de coisas empilhadas ao acaso.
Ambos juntos
O que é mais rápido - a pilha ou a pilha? E porque?
Para quem é iniciante em programação, provavelmente é uma boa ideia usar a pilha, pois é mais fácil.
Como a pilha é pequena, você poderá usá-la quando souber exatamente quanta memória precisará para seus dados ou se souber que o tamanho dos dados é muito pequeno.
É melhor usar o heap quando você sabe que precisará de muita memória para seus dados ou simplesmente não tem certeza da quantidade de memória necessária (como em uma matriz dinâmica).
Modelo de Memória Java
A pilha é a área da memória em que as variáveis locais (incluindo parâmetros do método) são armazenadas. Quando se trata de variáveis de objeto, essas são apenas referências (ponteiros) para os objetos reais na pilha.
Toda vez que um objeto é instanciado, um pedaço de memória heap é separado para armazenar os dados (estado) desse objeto. Como os objetos podem conter outros, alguns desses dados podem conter referências a esses objetos aninhados.
fonte
A pilha é uma parte da memória que pode ser manipulada através de várias instruções da linguagem de montagem de chaves, como 'pop' (remover e retornar um valor da pilha) e 'push' (enviar um valor para a pilha), mas também chamar ( chamar uma sub-rotina - isso pressiona o endereço para retornar à pilha) e retornar (retornar de uma sub-rotina - isso retira o endereço da pilha e salta para ele). É a região da memória abaixo do registro do ponteiro da pilha, que pode ser configurada conforme necessário. A pilha também é usada para passar argumentos para sub-rotinas e também para preservar os valores nos registradores antes de chamar sub-rotinas.
O heap é uma parte da memória fornecida a um aplicativo pelo sistema operacional, geralmente por meio de um syscall como malloc. Nos sistemas operacionais modernos, essa memória é um conjunto de páginas às quais apenas o processo de chamada tem acesso.
O tamanho da pilha é determinado no tempo de execução e geralmente não aumenta após o lançamento do programa. Em um programa C, a pilha precisa ser grande o suficiente para armazenar todas as variáveis declaradas em cada função. O heap crescerá dinamicamente conforme necessário, mas o sistema operacional fará a chamada (geralmente aumentará o heap em mais do que o valor solicitado pelo malloc, para que pelo menos alguns mallocs futuros não precisem voltar ao kernel para obter mais memória. Esse comportamento geralmente é personalizável)
Como você alocou a pilha antes de iniciar o programa, nunca é necessário fazer malloc antes de poder usá-la, portanto, essa é uma pequena vantagem. Na prática, é muito difícil prever o que será rápido e o que será lento nos sistemas operacionais modernos que possuem subsistemas de memória virtual, porque o modo como as páginas são implementadas e onde são armazenadas é um detalhe da implementação.
fonte
Penso que muitas outras pessoas lhe deram respostas corretas sobre esse assunto.
Um detalhe que foi esquecido, no entanto, é que o "heap" provavelmente deve ser chamado de "free store". O motivo dessa distinção é que o armazenamento gratuito original foi implementado com uma estrutura de dados conhecida como "pilha binomial". Por esse motivo, a alocação de implementações iniciais de malloc () / free () foi alocada a partir de um heap. No entanto, atualmente, a maioria das lojas gratuitas é implementada com estruturas de dados muito elaboradas que não são pilhas binomiais.
fonte
C
idioma. Esse é um equívoco comum, embora seja o paradigma (de longe) dominante para a implementaçãoC99 6.2.4 automatic storage duration objects
(variáveis). Na verdade, a palavra "pilha" nem sequer aparece noC99
padrão de linguagem: open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdfVocê pode fazer algumas coisas interessantes com a pilha. Por exemplo, você tem funções como alloca (supondo que você possa passar por copiosos avisos sobre seu uso), que é uma forma de malloc que usa especificamente a pilha, e não a pilha, para memória.
Dito isto, erros de memória baseados em pilha são alguns dos piores que já experimentei. Se você usar memória heap e ultrapassar os limites do bloco alocado, terá uma chance decente de acionar uma falha no segmento. (Não é 100%: seu bloco pode ser incidentalmente contíguo com outro que você alocou anteriormente.) Mas, como as variáveis criadas na pilha são sempre contíguas, a escrita fora dos limites pode alterar o valor de outra variável. Aprendi que sempre que sinto que meu programa parou de obedecer às leis da lógica, provavelmente é um estouro de buffer.
fonte
alloca
? Por exemplo, isso funciona no Windows? É apenas para sistemas operacionais semelhantes ao Unix?Simplesmente, a pilha é onde as variáveis locais são criadas. Além disso, toda vez que você chama uma sub-rotina, o contador do programa (ponteiro para a próxima instrução da máquina) e quaisquer registros importantes, e algumas vezes os parâmetros são pressionados na pilha. Em seguida, quaisquer variáveis locais dentro da sub-rotina são empurradas para a pilha (e usadas a partir daí). Quando a sub-rotina termina, tudo isso volta à pilha. Os dados do PC e do registro são colocados e colocados de volta onde estavam, para que seu programa possa seguir em frente.
O heap é a área da memória em que as alocações de memória dinâmica são feitas (chamadas explícitas "novas" ou "alocadas"). É uma estrutura de dados especial que pode rastrear blocos de memória de tamanhos variados e seu status de alocação.
Nos sistemas "clássicos", a RAM era projetada de tal forma que o ponteiro da pilha começava na parte inferior da memória, o ponteiro da pilha começava na parte superior e cresciam um para o outro. Se eles se sobrepõem, você está sem memória RAM. Porém, isso não funciona com sistemas operacionais multithread modernos. Cada encadeamento precisa ter sua própria pilha e elas podem ser criadas dinamicamente.
fonte
Do WikiAnwser.
Pilha
Quando uma função ou método chama outra função que, por sua vez, chama outra função, etc., a execução de todas essas funções permanece suspensa até a última função retornar seu valor.
Essa cadeia de chamadas de função suspensas é a pilha, porque os elementos na pilha (chamadas de função) dependem um do outro.
É importante considerar a pilha no tratamento de exceções e nas execuções de encadeamento.
Montão
A pilha é simplesmente a memória usada pelos programas para armazenar variáveis. O elemento da pilha (variáveis) não tem dependências entre si e sempre pode ser acessado aleatoriamente a qualquer momento.
fonte
Pilha
Montão
fonte
OK, simplesmente e em poucas palavras, eles significam ordenado e não ordenado ...!
Pilha : Nos itens da pilha, as coisas se misturam, significa que será mais rápido e mais eficiente para ser processado! ...
Portanto, sempre há um índice para apontar o item específico, o processamento também será mais rápido, também há relação entre os itens! ...
Montão : nenhuma ordem, o processamento será mais lento e os valores serão alterados sem ordem ou índice específico ... existem aleatórios e não há relação entre eles ... portanto, o tempo de execução e uso pode variar ...
Também crio a imagem abaixo para mostrar como eles podem se parecer:
fonte
Em resumo
Uma pilha é usada para alocação de memória estática e uma pilha para alocação de memória dinâmica, ambas armazenadas na RAM do computador.
Em detalhe
A pilha
A pilha é uma estrutura de dados "LIFO" (última entrada, primeira saída), que é gerenciada e otimizada pela CPU de perto. Toda vez que uma função declara uma nova variável, ela é "empurrada" para a pilha. Toda vez que uma função sai, todas as variáveis colocadas na pilha por essa função são liberadas (ou seja, são excluídas). Depois que uma variável de pilha é liberada, essa região de memória fica disponível para outras variáveis de pilha.
A vantagem de usar a pilha para armazenar variáveis é que a memória é gerenciada por você. Você não precisa alocar a memória manualmente ou liberá-la quando não precisar mais dela. Além disso, como a CPU organiza a memória da pilha com tanta eficiência, a leitura e a gravação das variáveis da pilha são muito rápidas.
Mais pode ser encontrado aqui .
The Heap
O heap é uma região da memória do computador que não é gerenciada automaticamente para você e não é gerenciada com tanta rigidez pela CPU. É uma região de memória mais flutuante (e é maior). Para alocar memória no heap, você deve usar malloc () ou calloc (), que são funções C internas. Depois de alocar memória no heap, você é responsável por usar free () para desalocar a memória quando não precisar mais dela.
Se você não conseguir fazer isso, seu programa terá o que é conhecido como vazamento de memória. Ou seja, a memória no heap ainda será reservada (e não estará disponível para outros processos). Como veremos na seção de depuração, existe uma ferramenta chamada Valgrind que pode ajudá-lo a detectar vazamentos de memória.
Diferentemente da pilha, o heap não possui restrições de tamanho em tamanho variável (além das óbvias limitações físicas do seu computador). A memória da pilha é um pouco mais lenta para ser lida e gravada, porque é necessário usar ponteiros para acessar a memória na pilha. Falaremos sobre indicadores em breve.
Diferentemente da pilha, as variáveis criadas no heap são acessíveis por qualquer função, em qualquer lugar do seu programa. Variáveis de heap são essencialmente globais em escopo.
Mais pode ser encontrado aqui .
As variáveis alocadas na pilha são armazenadas diretamente na memória e o acesso a essa memória é muito rápido, e sua alocação é tratada quando o programa é compilado. Quando uma função ou método chama outra função que, por sua vez, chama outra função, etc., a execução de todas essas funções permanece suspensa até a última função retornar seu valor. A pilha é sempre reservada em uma ordem LIFO, o bloco reservado mais recente é sempre o próximo bloco a ser liberado. Isso torna muito simples acompanhar a pilha, liberar um bloco da pilha nada mais é do que ajustar um ponteiro.
As variáveis alocadas no heap têm sua memória alocada em tempo de execução e o acesso a essa memória é um pouco mais lento, mas o tamanho do heap é limitado apenas pelo tamanho da memória virtual. Os elementos da pilha não têm dependências entre si e sempre podem ser acessados aleatoriamente a qualquer momento. Você pode alocar um bloco a qualquer momento e liberá-lo a qualquer momento. Isso torna muito mais complexo acompanhar quais partes do heap estão alocadas ou livres a qualquer momento.
Você pode usar a pilha se souber exatamente a quantidade de dados que precisa alocar antes do tempo de compilação e ela não for muito grande. Você pode usar o heap se não souber exatamente quantos dados precisará no tempo de execução ou se precisar alocar muitos dados.
Em uma situação multithread, cada thread terá sua própria pilha completamente independente, mas eles compartilharão o heap. A pilha é específica do segmento e a pilha é específica do aplicativo. É importante considerar a pilha no tratamento de exceções e nas execuções de encadeamento.
Cada encadeamento obtém uma pilha, enquanto normalmente há apenas um heap para o aplicativo (embora não seja incomum ter vários heap para diferentes tipos de alocação).
No tempo de execução, se o aplicativo precisar de mais heap, ele poderá alocar memória da memória livre e se a pilha precisar de memória, poderá alocar memória da memória alocada para o aplicativo.
Ainda, mais detalhes são dados aqui e aqui .
Agora chegue às respostas da sua pergunta .
O SO aloca a pilha para cada encadeamento no nível do sistema quando o encadeamento é criado. Normalmente, o sistema operacional é chamado pelo runtime do idioma para alocar o heap para o aplicativo.
Mais pode ser encontrado aqui .
Já entregue no topo.
Mais pode ser encontrado aqui .
O tamanho da pilha é definido pelo SO quando um encadeamento é criado. O tamanho do heap é definido na inicialização do aplicativo, mas pode aumentar conforme o espaço necessário (o alocador solicita mais memória do sistema operacional).
A alocação de pilha é muito mais rápida, pois tudo o que realmente faz é mover o ponteiro da pilha. Usando pools de memória, você pode obter desempenho comparável com a alocação de heap, mas isso vem com uma leve complexidade adicional e suas próprias dores de cabeça.
Além disso, pilha versus pilha não é apenas uma consideração de desempenho; também informa muito sobre a vida útil esperada dos objetos.
Detalhes podem ser encontrados aqui .
fonte
Na década de 1980, o UNIX propagou-se como coelhos, com grandes empresas produzindo seus próprios produtos. A Exxon tinha um, assim como dezenas de nomes de marcas perdidos na história. Como a memória foi distribuída ficou a critério de muitos implementadores.
Um programa C típico foi colocado na memória, com a oportunidade de aumentar, alterando o valor brk (). Normalmente, o HEAP estava logo abaixo desse valor de brk e o aumento de brk aumentava a quantidade de heap disponível.
A PILHA única era tipicamente uma área abaixo do HEAP, que era uma área de memória que não continha nada de valor até o topo do próximo bloco fixo de memória. Esse próximo bloco era frequentemente CODE, que podia ser sobrescrito pelos dados da pilha em um dos hacks famosos de sua época.
Um bloco de memória típico era o BSS (um bloco de valores zero) que acidentalmente não foi zerado na oferta de um fabricante. Outro foi o DATA contendo valores inicializados, incluindo strings e números. Um terceiro era o CODE contendo CRT (tempo de execução C), principal, funções e bibliotecas.
O advento da memória virtual no UNIX altera muitas das restrições. Não há razão objetiva para que esses blocos precisem ser contíguos, de tamanho fixo ou ordenados de uma maneira específica agora. Obviamente, antes do UNIX havia Multics, que não sofria com essas restrições. Aqui está um esquema que mostra um dos layouts de memória daquela época.
fonte
pilha , pilha e dados de cada processo na memória virtual:
fonte
Alguns centavos: eu acho que será bom desenhar a memória gráfica e mais simples:
Setas - mostram onde a pilha de crescimento e a pilha, o tamanho da pilha de processos tem limite, definido no SO, os limites de tamanho da pilha de encadeamentos pelos parâmetros na API de criação de encadeamentos geralmente. Heap geralmente limitando pelo tamanho máximo da memória virtual do processo, por 32 bits de 2 a 4 GB, por exemplo.
Maneira simples: o heap do processo é geral para o processo e todos os threads internos, usando para alocação de memória em casos comuns com algo como malloc () .
A pilha é uma memória rápida para armazenamento em ponteiros e variáveis de retorno de função de caso comum, processados como parâmetros na chamada de função, variáveis de função local.
fonte
Uma vez que algumas respostas foram acertadas, vou contribuir com meu ácaro.
Surpreendentemente, ninguém mencionou que várias pilhas de chamadas (isto é, não relacionadas ao número de threads em execução no sistema operacional) devem ser encontradas não apenas em linguagens exóticas (PostScript) ou plataformas (Intel Itanium), mas também em fibras , threads verdes e algumas implementações de corotinas .
Fibras, fios verdes e corotinas são de muitas maneiras semelhantes, o que leva a muita confusão. A diferença entre fibras e fios verdes é que os primeiros usam multitarefa cooperativa, enquanto os últimos podem apresentar um cooperativo ou preventivo (ou ambos). Para a distinção entre fibras e corotinas, veja aqui .
De qualquer forma, o objetivo de ambas as fibras, linhas verdes e corotinas é ter várias funções executadas simultaneamente, mas não em paralelo (consulte esta questão do SO para a distinção) em um único segmento no nível do sistema operacional, transferindo o controle para frente e para trás de forma organizada.
Ao usar fibras, linhas verdes ou corotinas, normalmente você tem uma pilha separada por função. (Tecnicamente, não apenas uma pilha, mas todo o contexto de execução é por função. Mais importante, a CPU é registrada.) Para cada encadeamento, há tantas pilhas quanto há funções em execução simultaneamente, e o encadeamento está alternando entre a execução de cada função. de acordo com a lógica do seu programa. Quando uma função é executada até o fim, sua pilha é destruída. Portanto, o número e a vida útil das pilhas são dinâmicos e não são determinados pelo número de threads no nível do SO!
Note que eu disse " normalmente tem uma pilha separada por função". Há dois somos stackful e Stackless implementações de couroutines. As implementações C ++ empilháveis mais notáveis são Boost.Coroutine e Microsoft PPL 's
async/await
. (No entanto, as funções recuperáveis do C ++ (também conhecidas como "async
eawait
"), propostas para o C ++ 17, provavelmente usarão corotinas sem pilha.)A proposta de fibras para a biblioteca padrão C ++ está próxima. Além disso, existem algumas bibliotecas de terceiros . Os threads verdes são extremamente populares em idiomas como Python e Ruby.
fonte
Tenho algo a compartilhar, embora os principais pontos já estejam cobertos.
Pilha
Montão
Nota interessante:
fonte
Uau! Tantas respostas e eu não acho que uma delas acertou ...
1) Onde e o que eles estão (fisicamente na memória de um computador real)?
A pilha é a memória que começa como o endereço de memória mais alto alocado para a imagem do programa e depois diminui de valor a partir daí. É reservado para os parâmetros de função chamados e para todas as variáveis temporárias usadas nas funções.
Existem dois montes: público e privado.
O heap privado começa em um limite de 16 bytes (para programas de 64 bits) ou em um limite de 8 bytes (para programas de 32 bits) após o último byte de código no seu programa e, em seguida, aumenta o valor a partir daí. Também é chamado de heap padrão.
Se o heap particular ficar muito grande, ele se sobrepõe à área da pilha, assim como a pilha se sobrepõe ao heap se ficar muito grande. Como a pilha começa em um endereço mais alto e desce para o endereço mais baixo, com hackers adequados, você pode tornar a pilha tão grande que invadirá a área de heap privada e se sobreporá à área de código. O truque é sobrepor o suficiente da área do código para que você possa conectar-se ao código. É um pouco complicado de fazer e você corre o risco de travar um programa, mas é fácil e muito eficaz.
O heap público reside em seu próprio espaço de memória fora do espaço de imagem do programa. É essa memória que será desviada para o disco rígido se os recursos de memória ficarem escassos.
2) Até que ponto eles são controlados pelo SO ou pelo tempo de execução do idioma?
A pilha é controlada pelo programador, a pilha privada é gerenciada pelo sistema operacional e a pilha pública não é controlada por ninguém porque é um serviço do sistema operacional - você faz solicitações e elas são concedidas ou negadas.
2b) Qual é o seu escopo?
Eles são todos globais para o programa, mas seu conteúdo pode ser privado, público ou global.
2c) O que determina o tamanho de cada um deles?
O tamanho da pilha e o heap privado são determinados pelas opções de tempo de execução do compilador. O heap público é inicializado em tempo de execução usando um parâmetro de tamanho.
2d) O que torna um mais rápido?
Eles não foram projetados para serem rápidos, foram projetados para serem úteis. Como o programador as utiliza determina se elas são "rápidas" ou "lentas"
REF:
https://norasandler.com/2019/02/18/Write-a-Compiler-10.html
https://docs.microsoft.com/en-us/windows/desktop/api/heapapi/nf-heapapi-getprocessheap
https://docs.microsoft.com/en-us/windows/desktop/api/heapapi/nf-heapapi-heapcreate
fonte
Muitas respostas estão corretas como conceitos, mas devemos observar que uma pilha é necessária pelo hardware (ou seja, microprocessador) para permitir sub-rotinas de chamada (CALL na linguagem assembly). (OOP caras chamam isso de métodos )
Na pilha, você salva os endereços de retorno e chama → pressionar / reter → pop é gerenciado diretamente no hardware.
Você pode usar a pilha para passar parâmetros .. mesmo que seja mais lento que o uso de registradores (diria um guru de microprocessador ou um bom livro do BIOS dos anos 80 ...)
O uso da pilha é mais rápido como:
fonte
malloc
é uma chamada de kernel?Fonte: Academind
fonte
Obrigado por uma discussão muito boa, mas como um noob real eu me pergunto onde as instruções são mantidas? No início, os cientistas decidiam entre duas arquiteturas (von NEUMANN, onde tudo é considerado DATA e HARVARD, onde uma área de memória era reservada para instruções e outra para dados). Por fim, seguimos o design de von Neumann e agora tudo é considerado 'o mesmo'. Isso me tornou difícil quando eu estava aprendendo a montagem https://www.cs.virginia.edu/~evans/cs216/guides/x86.html, porque eles falam sobre registros e ponteiros de pilha.
Tudo acima fala sobre DATA. Meu palpite é que, uma vez que uma instrução é uma coisa definida com um espaço específico de memória, ela fica na pilha e, portanto, todos os registros 'discutidos em assembly estão na pilha. É claro que então veio a programação orientada a objetos com instruções e dados inseridos em uma estrutura dinâmica, e agora as instruções também eram mantidas na pilha?
fonte