Por que os livros .Net falam sobre alocação de memória pilha versus pilha?


36

Parece que todo livro .net fala sobre tipos de valor versus tipos de referência e faz questão de indicar (geralmente incorretamente) onde cada tipo está armazenado - o heap ou a pilha. Geralmente, está nos primeiros capítulos e é apresentado como um fato muito importante. Eu acho que é coberto até nos exames de certificação . Por que o stack vs heap é importante para os desenvolvedores (iniciantes) .Net? Você aloca coisas e simplesmente funciona, certo?


11
Alguns autores têm um péssimo julgamento sobre o que é importante ensinar aos iniciantes e o que é ruído irrelevante. Em um livro que eu vi recentemente, a primeira menção de modificadores de acesso já incluídos protegidas interna , que eu nunca usei em 6 anos de C # ...
Timwi

1
Meu palpite é que quem escreveu a documentação original do .Net para essa parte fez muito disso, e é nessa documentação que os autores originalmente basearam seus livros, e então ficou por aí.
Greg

Dizer que os tipos de valor copiam a coisa toda e as referências não, faria mais sentido e seria mais fácil entender por que usar referências, pois onde esses valores são armazenados pode ser específico da implementação e até irrelevante.
Trinidad

Entreviste o culto de carga?
Den

Respostas:


37

Estou ficando convencido de que a principal razão pela qual essa informação é considerada importante é a tradição. Em ambientes não gerenciados, a distinção entre pilha e pilha é importante e temos que alocar e excluir manualmente a memória que usamos. Agora, a coleta de lixo cuida do gerenciamento, então eles ignoram isso. Não acho que a mensagem tenha realmente chegado, de que não precisamos nos preocupar com o tipo de memória usada.

Como Fede apontou, Eric Lippert tem algumas coisas muito interessantes a dizer sobre isso: http://blogs.msdn.com/b/ericlippert/archive/2010/09/30/the-truth-about-value-types.aspx .

À luz dessas informações, você pode ajustar meu primeiro parágrafo para basicamente ler: "A razão pela qual as pessoas incluem essas informações e assumem que são importantes é por causa de informações incorretas ou incompletas combinadas à necessidade desse conhecimento no passado".

Para aqueles que acham que isso ainda é importante por razões de desempenho: que ações você tomaria para mover algo da pilha para a pilha se você medisse as coisas e descobrisse que isso importava? Provavelmente, você encontrará uma maneira completamente diferente de melhorar o desempenho da área problemática.


6
Ouvi dizer que em algumas implementações de estrutura (compacta no Xbox especificamente) é melhor usar estruturas durante o período de renderização (o próprio jogo) para reduzir a coleta de lixo. Você ainda usaria tipos normais em outros lugares, mas pré-alocados para que o GC não funcione durante o jogo. Essa é a única otimização em relação à pilha versus pilha que eu conheço no .NET e é bastante específica para as necessidades da estrutura compacta e dos programas em tempo real.
CodexArcanum

5
Eu concordo principalmente com o argumento da tradição. Muitos programadores experientes em algum momento podem ter programado em uma linguagem de baixo nível onde esse material importava se você quisesse um código correto e eficiente. No entanto, considere o C ++, por exemplo, uma linguagem não gerenciada: a especificação oficial não diz que as variáveis ​​automáticas devem ir para a pilha, etc. O padrão do C ++ trata a pilha e a pilha como detalhes de implementação. +1
stakx

36

Parece que todo livro do .NET fala sobre tipos de valor versus tipos de referência e faz questão de indicar (geralmente incorretamente) onde cada tipo está armazenado - o heap ou a pilha. Geralmente, está nos primeiros capítulos e apresentado como um fato muito importante.

Eu concordo completamente; Eu vejo isso o tempo todo.

Por que os livros do .NET falam sobre alocação de memória pilha versus pilha?

Uma parte do motivo é que muitas pessoas acessaram o C # (ou outras linguagens .NET) com experiência em C ou C ++. Como esses idiomas não impõem para você as regras sobre a vida útil do armazenamento, você deve conhecê-las e implementar seu programa cuidadosamente para segui-las.

Agora, conhecer essas regras e segui-las em C não requer que você entenda "a pilha" e "a pilha". Mas se você entender como as estruturas de dados funcionam, geralmente será mais fácil entender e seguir as regras.

Ao escrever um livro para iniciantes, é natural que um autor explique os conceitos na mesma ordem em que os aprendeu. Essa não é necessariamente a ordem que faz sentido para o usuário. Recentemente, fui editor técnico do livro para iniciantes C # 4 de Scott Dorman e uma das coisas que mais gostei foi que Scott escolheu uma ordem bastante sensata para os tópicos, em vez de começar com tópicos realmente avançados sobre gerenciamento de memória.

Outra parte do motivo é que algumas páginas na documentação do MSDN enfatizam fortemente as considerações de armazenamento. Documentação do MSDN particularmente mais antiga que ainda existe nos primeiros dias. Grande parte dessa documentação possui erros sutis que nunca foram retirados, e você deve se lembrar que ela foi escrita em um momento específico da história e para um público específico.

Por que o stack vs heap é importante para desenvolvedores .NET (iniciantes)?

Na minha opinião, não. O que é muito mais importante entender são coisas como:

  • Qual é a diferença na semântica da cópia entre um tipo de referência e um tipo de valor?
  • Como um parâmetro "ref int x" se comporta?
  • Por que os tipos de valor devem ser imutáveis?

E assim por diante.

Você aloca coisas e simplesmente funciona, certo?

Esse é o ideal.

Agora, existem situações em que isso importa. A coleta de lixo é impressionante e relativamente barata, mas não é gratuita. Copiar pequenas estruturas é relativamente barato, mas não é gratuito. Existem cenários de desempenho realistas nos quais você precisa equilibrar o custo da pressão de coleta e o custo de cópias excessivas. Nesses casos, é muito útil ter uma forte compreensão do tamanho, localização e vida útil real de toda a memória relevante.

Da mesma forma, existem cenários realistas de interoperabilidade nos quais é necessário saber o que está na pilha e o que está na pilha e o que o coletor de lixo pode estar se movendo. É por isso que o C # possui recursos como "fixo", "stackalloc" e assim por diante.

Mas esses são todos os cenários avançados. Idealmente, um programador iniciante não precisa se preocupar com nada disso.


2
Obrigado pela resposta Eric. Suas postagens recentes sobre o assunto foram o que realmente me levou a postar a pergunta.
Greg

13

Vocês estão perdendo o ponto. A razão pela qual a distinção pilha / pilha é importante é por causa do escopo .

struct S { ... }

void f() {
    var x = new S();
    ...
 }

Depois que x sai do escopo, o objeto que foi criado desaparece categoricamente . Isso é apenas porque está alocado na pilha, não na pilha. Não há nada que possa ter entrado na parte "..." do método que possa mudar esse fato. Em particular, quaisquer atribuições ou chamadas de método só poderiam ter feito cópias da estrutura S, não criado novas referências a ele para permitir que ele continuasse vivo.

class C { ... }

void f() {
     var x = new C();
     ...
}

História totalmente diferente! Como x está agora no heap , seu objeto (isto é, o próprio objeto , não uma cópia dele) poderia muito bem continuar vivendo após o x sair do escopo. De fato, a única maneira de não continuar a viver é se x for a única referência a ele. Se atribuições ou chamadas de método na parte "..." criaram outras referências que ainda estão "ativas" quando x sai do escopo, esse objeto continuará ativo.

Esse é um conceito muito importante, e a única maneira de realmente entender "o que e por quê" é saber a diferença entre alocação de pilha e heap.


Não tenho certeza se já vi esse argumento apresentado nos livros junto com a discussão sobre pilha / pilha, mas é bom. 1
Greg

2
Devido à maneira como o C # gera fechamentos, o código no ...pode fazer xcom que seja convertido em um campo de uma classe gerada pelo compilador e, portanto, supera o escopo indicado. Pessoalmente, acho desagradável a idéia de içamento implícito, mas os designers de linguagem parecem aceitá-lo (em vez de exigir que qualquer variável que seja içada tenha algo em sua declaração para especificar isso). Para garantir a correção do programa, geralmente é necessário prestar contas de todas as referências que possam existir para um objeto. Saber que, quando uma rotina retornar, nenhuma cópia de uma referência passada permanecerá útil.
Supercat 27/10

1
Quanto às 'estruturas estão na pilha', a declaração adequada é que, se um local de armazenamento for declarado como structType foo, o local de armazenamento fooreterá o conteúdo de seus campos; se fooestiver na pilha, também estarão seus campos. Se fooestiver na pilha, os campos também serão. se fooestiver em um Apple II em rede, seus campos também. Por outro lado, se foofosse um tipo de classe, ele conteria nulluma referência a um objeto. A única situação em que o tipo de classe foopoderia ser dito manter os campos do objeto seria se este fosse o único campo de uma classe e mantivesse uma referência a si mesma.
Supercat 27/10

+1, adoro a sua visão aqui e acho que é válida ... No entanto, não acho que seja uma justificativa para o motivo dos livros abordarem esse tópico com tanta profundidade. Parece que o que você explicou aqui pode substituir os 3 ou 4 capítulos do referido livro e ser MUITO mais útil.
Frank V

1
pelo que eu sei, as estruturas não precisam, nem elas sempre ficam na pilha.
Sara

5

Quanto a POR QUE eles cobrem o tópico, concordo com o @Kirk que é um conceito importante que você precisa entender. Quanto melhor você conhecer os mecanismos, melhor poderá fazer para criar ótimos aplicativos com bom desempenho.

Agora, Eric Lippert parece concordar com você que o tópico não é abordado corretamente pela maioria dos autores. Eu recomendo que você leia o blog dele para obter uma ótima compreensão do que está por trás do capô.


2
Bem, a postagem de Eric enfatiza que tudo que você precisa saber são as características evidentes dos tipos de valor e referência e não deve esperar que a implementação permaneça a mesma. Eu acho que é bastante implícito sugerir que existe uma maneira eficiente de reimplementar o C # sem uma pilha, mas o ponto dele é correto: não faz parte da especificação da linguagem. Portanto, a única razão para usar essa explicação em que consigo pensar é que é uma alegoria útil para programadores que conhecem outras línguas, particularmente C. Contanto que eles conheçam uma alegoria, o que muita literatura não deixa claro.
Jeremy

5

Bem, achei que esse era o objetivo dos ambientes gerenciados. Eu chegaria a chamar isso de um detalhe de implementação do tempo de execução subjacente sobre o qual você NÃO deve fazer suposições, porque isso pode mudar a qualquer momento.

Eu não sei muito sobre o .NET, mas, tanto quanto eu sei, ele foi lançado antes da execução. O JIT, por exemplo, poderia executar uma análise de escape e, de repente, você teria objetos na pilha ou apenas em alguns registros. Você não pode saber disso.

Suponho que alguns livros o cubram simplesmente porque os autores atribuem grande importância a ele, ou porque eles assumem que seu público o faz (por exemplo, se você escreveu um "C # para programadores de C ++", provavelmente deveria cobrir o tópico).

No entanto, acho que não há muito mais a dizer do que "a memória é gerenciada". Caso contrário, as pessoas podem tirar conclusões erradas.


2

Você precisa entender como a alocação de memória funciona para usá-la com eficiência, mesmo que não precise gerenciá-la explicitamente. Isso se aplica a praticamente todas as abstrações em ciência da computação.


2
Em idiomas gerenciados, você precisa saber a diferença entre um tipo de valor e um tipo de referência, mas, além disso, é fácil entender o eixo pensando em como ele é gerenciado sob o capô. Veja aqui um exemplo: stackoverflow.com/questions/4083981/…
Robert Harvey

Eu devo concordar com Robert

A diferença entre a pilha alocada e a pilha alocada é exatamente o que explica a diferença entre os tipos de valor e referência.
Jeremy


1
Jeremy, a diferença entre alocação de pilha e pilha não pode explicar o comportamento diferente entre tipos de valor e tipos de referência, porque há momentos em que os tipos de valor e de referência estão na pilha e, no entanto, se comportam de maneira diferente. A coisa mais importante a entender é (por exemplo) quando você precisa usar passagem por referência para tipos de referência versus tipos de valor. Isso depende apenas de "é um tipo de valor ou tipo de referência", não "é na pilha".
Tim Goodman

2

Pode haver alguns casos extremos onde isso pode fazer a diferença. O espaço de pilha padrão é de 1meg enquanto o heap é de vários gig. Portanto, se sua solução possui um grande número de objetos, você pode ficar sem espaço na pilha enquanto possui bastante espaço na pilha.

No entanto, na maioria das vezes é bastante acadêmico.


Sim, mas duvido que algum desses livros se esforce para explicar que as próprias referências são armazenadas na pilha - portanto, não importa se você tem muitos tipos de referência ou muitos tipos de valor, ainda pode haver um estouro de pilha.
Jeremy

0

Como você diz, o C # deve abstrair o gerenciamento de memória, e a alocação de pilha contra pilha é detalhes de implementação que, em teoria, o desenvolvedor não precisa conhecer.

O problema é que algumas coisas são realmente difíceis de explicar de maneira intuitiva sem se referir a esses detalhes de implementação. Tente explicar o comportamento observável ao modificar tipos de valores mutáveis ​​- é quase impossível fazer isso sem se referir à distinção pilha / pilha. Ou tente explicar por que ainda possui tipos de valor no idioma e quando você os usaria? Você precisa entender a distinção para entender o idioma.

Observe que livros sobre digamos Python ou JavaScript não são muito importantes se eles mencionarem. Isso ocorre porque tudo é alocado por pilha ou imutável, o que significa que as diferentes semânticas de cópia nunca entram em cena. Nessas linguagens, a abstração da memória funciona, em C # ela está vazando.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.