Em C ++, quanto tempo do programador é gasto no gerenciamento de memória

39

As pessoas que estão acostumadas a usar as linguagens coletadas para o lixo geralmente têm medo do gerenciamento de memória do C ++. Existem ferramentas, como auto_ptre shared_ptrque manipularão muitas das tarefas de gerenciamento de memória para você. Muitas bibliotecas C ++ são anteriores a essas ferramentas e têm seu próprio modo de lidar com as tarefas de gerenciamento de memória.

Quanto tempo você gasta em tarefas de gerenciamento de memória?

Suspeito que ele seja altamente dependente do conjunto de bibliotecas que você usa; portanto, diga a quais perguntas sua resposta se aplica e se a tornam melhor ou pior.

Sean McMillan
fonte
1
Não muito, realmente ... Especialmente com C ++ 0x, referências e STL. Você pode até escrever código sem nenhum gerenciamento de memória.
quer
9
Em geral: não muito se você tiver experiência. Muito se você é iniciante em C ++ (-> geralmente procurando vazamentos de memória / recursos).
MaR
1
Hoje, acho que a verdadeira questão é mais sobre procurar referências obsoletas. E normalmente é bastante evidente a cada vez, apenas irritante que não foi pego antes: p
Matthieu M.
Sei que isso é antigo, mas o gerenciamento de memória da IMO é parte integrante de ser um bom programador. Abstrações como os contêineres da STL são agradáveis, mas a ignorância da memória é contrária à própria idéia da computação. Pode-se perguntar como se pode eliminar a manipulação algébrica, lógica e loop do arsenal do programador.
precisa saber é o seguinte
Que tal "quanto tempo é usado para depurar o gerenciamento de memória que deu errado?" Por si só, o gerenciamento de memória é possível e não tão difícil em C ++. O fato é: configurá-lo é um ofício preciso e muito propenso a foder. Quando você estraga tudo, você pode nem perceber, e rastrear erros antigos com comportamentos irregulares que se acumulam ao longo do tempo é o sumidouro em tempo real que você deve ter medo. É por isso que as linguagens modernas sem coleta de lixo (estou pensando em ferrugem) transferiram muita responsabilidade pela verificação de erros típicos no compilador.
ZJR

Respostas:

54

O C ++ moderno faz com que você não se preocupe com o gerenciamento de memória até que seja necessário, ou seja, até que você precise organizar sua memória manualmente, principalmente para fins de otimização ou se o contexto forçar você a fazê-lo (pense em hardware com grandes restrições). Eu escrevi jogos inteiros sem manipular a memória bruta, apenas me preocupando com o uso de contêineres que são a ferramenta certa para o trabalho, como em qualquer idioma.

Portanto, depende do projeto, mas na maioria das vezes não é com o gerenciamento de memória que você precisa lidar, mas apenas com o objeto durante a vida útil. Isso é resolvido usando ponteiros inteligentes , que é uma das ferramentas idiomáticas do C ++ resultantes do RAII .

Depois de entender o RAII , o gerenciamento de memória não será um problema.

Então, quando você precisar acessar a memória bruta, fará isso em código muito específico, localizado e identificável, como nas implementações de objetos de pool, e não "em todos os lugares".

Fora desse tipo de código, você não precisará manipular a memória, apenas o tempo de vida dos objetos.

A parte "difícil" é entender o RAII.

Klaim
fonte
10
Absolutamente verdadeiro. Nos últimos 5 anos, apenas escrevi "delete" s ao trabalhar com código legado.
Drxzcl
3
Trabalho em um ambiente incorporado com restrições de tamanho de pilha. Por mais legal que seja o RAII, ele não funciona bem se o espaço da pilha for muito alto. Então, voltamos ao microgerenciamento de ponteiro.
bastibe
1
@nikie Eu uso os ponteiros inteligentes das bibliotecas no código que manipula sua API e, em seguida, uso ponteiros inteligentes padrão ou aprimorados no código específico do meu aplicativo (se sou eu quem decide isso). Se você pode isolar o código da biblioteca em alguns módulos que abstraem como eles são usados ​​em seu aplicativo, evita a poluição da API por dependências.
`` Klaim
12
@ Paperflyer: RAII não ocupará mais espaço de pilha do que deletemanualmente, a menos que você tenha uma implementação de merda.
DeadMG
2
@ Paperflyer: O ponteiro inteligente na pilha ocupa o mesmo espaço; a diferença é que o compilador insere o código de desalocação de recursos em todas as saídas de uma função. E como isso é tão amplamente usado, isso normalmente é bem otimizado (por exemplo, dobrar várias saídas juntas de maneiras que você não pode - você não pode colocar o código após a return)
MSalters
32

O gerenciamento de memória é usado para assustar as crianças, mas é apenas um tipo de recurso que um programador precisa cuidar. Pense em identificadores de arquivo, conexões de rede e outros recursos que você obtém do SO.

Os idiomas que oferecem suporte à coleta de lixo geralmente não apenas ignoram a existência desses recursos, mas também tornam mais difícil lidar com eles adequadamente, não fornecendo um destruidor.

Portanto, resumindo, eu sugeriria que não se gaste muito tempo se desenvolvendo em C ++ se preocupando com o gerenciamento de memória. Como a resposta de klaim indica, depois de controlar o RAII, o resto é apenas reflexo.

Dysaster
fonte
3
Eu adoro especialmente como o HttpWebRequest.GetResponse vaza alças e começa a falhar nos idiomas do GC. O GC é legal, até começar a sugar porque os recursos ainda vazam. msdn.microsoft.com/en-us/library/… Consulte "Cuidado".
3/11
6
+1 para visualizar a memória como recurso. Código legado ou não, quantas vezes precisamos gritar em voz alta: o gerenciamento de memória é uma habilidade e não uma maldição .
aquaherd
4
@ Codificador Não tenho certeza se eu sigo .. GC é uma merda, porque é possível abusar de recursos de qualquer maneira ..? Acho C # faz um bom trabalho fornecendo recursos determinista liberando usando IDisposable ...
Max
8
@ Max: Porque se for coletado lixo, espero não se preocupar com recursos estúpidos usando IDisposables personalizados. Os recursos deixaram o escopo, é isso, eles devem ser limpos. Na realidade, porém, ainda tenho que pensar e adivinhar quais irão vazar e quais não. Supera qualquer razão para usar o idioma do GC em primeiro lugar.
3/11
5
@deadalnix Eles têm a finalizeconstrução. No entanto, você não sabe quando será chamado. Vai demorar para você ficar sem soquetes ou objetos WebResponse? Você encontrará muitos artigos que lhe dizem que não deve confiar finalize- por um bom motivo.
Dysaster
13

Praticamente nenhum. Mesmo em tecnologias antigas como COM, você pode escrever deleters personalizados para os ponteiros padrão que os converterão em um período muito curto. Por exemplo, std::unique_ptrpode ser convertido para manter exclusivamente uma referência COM com cinco linhas de um deleter personalizado. Mesmo se você precisar escrever manualmente seu próprio manipulador de recursos, a prevalência de conhecimento como SRP e copiar e trocar torna relativamente fácil escrever uma classe de gerenciamento de recursos para usar sempre mais.

A realidade é que todos os itens compartilhados, únicos e sem propriedade são fornecidos com o compilador C ++ 11, e você só precisa escrever pequenos adaptadores para fazê-los funcionar mesmo com o código antigo.

DeadMG
fonte
1
Quanta habilidade com C ++ você precisa para: a) escrever um deletômetro personalizado b) saber que um deletador personalizado é o que você precisa? Eu pergunto porque parece fácil pegar uma nova linguagem do GC e ficar quase correto sem saber tudo - é fácil acertar também em C ++?
Sean McMillan
1
@SeanMcMillan: Deleters personalizados são triviais para escrever e implantar, o COM que eu mencionei é de cinco linhas para todos os tipos COM, e qualquer pessoa com um treinamento básico em C ++ moderno deve estar familiarizado com eles. Você não pode escolher um idioma GCed, porque surpresa - o GC não coletará objetos COM. Ou identificadores de arquivo. Ou memória obtida de outros sistemas. Ou conexões de banco de dados. RAII fará todas essas coisas.
DeadMG
2
Por "Escolha uma linguagem de GC", eu quis dizer que pulei entre Java / C # / Ruby / Perl / Javascript / Python, e todos eles têm o mesmo estilo de gerenciamento de recursos - a memória é quase sempre automática e todo o resto , você precisa gerenciar. Parece-me que você está dizendo que as ferramentas de gerenciamento do C ++ permitem gerenciar identificadores de arquivo / conexões db / etc da mesma maneira que a memória, e que é relativamente simples quando você o aprende. Não é cirurgia no cérebro. Eu compreendo corretamente?
Sean McMillan
3
@SeanMcMillan: Sim, isso é exatamente correto e não é complexo.
913 DeadMG
11

Quando eu era um programador de C ++ (há muito tempo), gastei muito tempo me preocupando com os erros de gerenciamento de memória ao tentar corrigir os erros de reprodução .

Com o modem C ++, o gerenciamento de memória é muito menos problemático, mas você pode confiar em todos os membros de uma grande equipe para acertar. Qual é o custo / tempo de:

  • Treinamento (não há muitos programadores com uma boa compreensão dos problemas)
  • Revisões de código para encontrar problemas de gerenciamento de memória
  • Depuração de problemas de gerenciamento de memória
  • Sempre tendo em mente que um bug em uma parte do aplicativo pode ser devido a um problema de gerenciamento de memória em uma parte não relacionada ao aplicativo .

Portanto, não é apenas o tempo gasto “ fazendo ”, é mais um problema em grandes projetos.

Ian
fonte
2
Eu acho que alguns projetos em C ++ estavam desesperados por corrigir alguns vazamentos de memória devido a códigos mal escritos. Um código incorreto vai acontecer e, quando isso acontece, pode levar muito tempo para outras pessoas.
Jeremy
@ Jeremy, descobri que quando mudei de C ++ para C #, ainda havia muito código mal escrito (se não mais), mas pelo menos era muito fácil encontrar a parte do programa que apresentava um determinado bug.
Ian
1
Sim, é por isso que a maioria das lojas mudou para Java ou .NET. A coleta de lixo atenua os danos inevitáveis ​​do código incorreto.
Jeremy
1
Curiosamente, não temos esses problemas.
precisa
1
@DavidThornley, eu acho que uma grande parte do problema eram de escrever código UI em C ++, nos dias de hoje a maioria do código C ++ eu vejo não é UI
Ian
2

Eu uso muito as bibliotecas boost e TR1, e elas tornam o gerenciamento de memória no sentido estrito (novo / excluir) um problema. Por outro lado, a alocação de memória em C ++ não é barata e é preciso prestar atenção ao local em que esses ponteiros compartilhados sofisticados são criados. Você acaba usando muito os espaços de trabalho ou trabalhando com a memória baseada em pilha. Em geral, eu diria que é principalmente um problema de design, não um problema de implementação.

quant_dev
fonte
2

quanto tempo leva como cliente? muito pouco, quando você pegar o jeito. quando um contêiner gerencia a vida útil e as referências, é realmente muito fácil. imo, é muito mais simples que a contagem manual de referências e é praticamente transparente se você considerar o contêiner usado como documentação que o compilador convenientemente impede que você realize transferências de propriedade inválidas em um sistema typesafe bem projetado.

Na maioria das vezes, gasto (como cliente) é gasto contendo tipos de outras APIs, para que funcionem bem no contexto de seus programas. exemplo: este é o meu recipiente ThirdPartyFont, e suporta esses recursos, e destruição implementos desta maneira, e contagem de referência desta maneira, e copiar este caminho, e ... . Muitas dessas construções precisam estar no lugar, e geralmente é o lugar lógico para colocá-las. se você deseja incluir isso com o tempo ou não, depende da sua definição (a implementação precisa existir ao interagir com essas APIs, afinal, certo?).

depois disso, você precisará levar em consideração a memória e a propriedade. em um sistema de nível inferior, isso é bom e necessário, mas pode levar algum tempo e andaimes para implementar como você deve mudar as coisas. eu não vejo isso como uma dor, uma vez que este é um requisito de um sistema de nível inferior. propriedade, controle e responsabilidade são evidentes.

para que possamos direcionar isso para apis baseadas em c que usam tipos opacos: nossos contêineres nos permitem abstrair todos os pequenos detalhes de implementação de gerenciamento da vida útil e cópia desses tipos opacos, o que torna o gerenciamento de recursos muito simples e economiza tempo, defeitos, e reduz implementações.

é realmente muito simples usá-las - o problema (vindo do GC) é que você precisa considerar a vida útil dos seus recursos. se você errar, pode demorar muito tempo para resolver. aprender e integrar o gerenciamento explícito da vida é compreensivelmente complexo em comparação (não para todas as pessoas) - esse é o verdadeiro obstáculo. quando você estiver confortável controlando as vidas úteis e usando boas soluções, será realmente muito fácil gerenciar a vida útil dos recursos. não é uma parte significativa do meu dia (a menos que um bug difícil tenha surgido).

se você não estiver usando contêineres (ponteiro automático / compartilhado), estará implorando por dor.

Eu implementei minhas próprias bibliotecas. leva -me tempo para implementar essas coisas, mas a maioria das pessoas reutilizar (que geralmente é uma boa idéia).

justin
fonte
1

Você quer dizer manualmente ter que liberar memória, fechar arquivos, coisas desse tipo? Nesse caso, eu diria o mínimo e normalmente menos do que a maioria dos outros idiomas que usei, especialmente se generalizarmos isso não apenas para "gerenciamento de memória", mas "gerenciamento de recursos". Nesse sentido, acho que o C ++ requer menos gerenciamento manual de recursos do que, digamos, Java ou C #.

É principalmente devido a destruidores que automatizam a destruição do recurso (memória ou não). Normalmente, a única vez que tenho que liberar / destruir um recurso manualmente em C ++ é se estou implementando uma estrutura de dados no nível vlow (algo que a maioria das pessoas não precisa fazer) ou usando uma API C, onde passo apenas um pouco de tempo agrupando o recurso C que precisa ser liberado / destruído / fechado manualmente em um wrapper C ++ em conformidade com RAII.

É claro que se um usuário solicitar o fechamento de uma imagem em um software de edição de imagens, eu tenho que remover a imagem de uma coleção ou algo assim. Mas espero que isso não conte como gerenciamento de "memória" ou "recurso" de um tipo que importa nesse contexto, pois isso é praticamente necessário em qualquer idioma, se você quiser liberar a memória associada a essa imagem naquele momento. Mas, novamente, tudo o que você precisa fazer é remover a imagem da coleção e o destruidor de imagens cuidará do resto.

Enquanto isso, se eu comparar, digamos, Java ou C #, você geralmente encontrará pessoas que precisam fechar arquivos manualmente, desconectar soquetes manualmente, definir referências de objetos como nulas para permitir que sejam coletadas como lixo, etc. Há muito mais memória manual e gerenciamento de recursos nesses idiomas, se você me perguntar. No C ++, muitas vezes você nem precisa de unlockum mutex manualmente, pois o armário do mutex fará isso automaticamente quando o mutex estiver fora do escopo. Por exemplo, você nunca deve fazer coisas como esta em C ++:

System.IO.StreamReader file = new System.IO.StreamReader(path);
try
{
    file.ReadBlock(buffer, index, buffer.Length);
}
catch (System.IO.IOException e)
{
    ...
}
finally
{
    if (file != null)
        file.Close();
}

Não é necessário fazer coisas como fechar arquivos manualmente em C ++. Eles acabam se fechando automaticamente no instante em que ficam fora do escopo, como resultado ou em caminhos de execução normais ou excepcionais. Coisa semelhante para recursos relacionados à memória, como std::vector. Esse código, como file.Close()acima, muitas vezes seria desaprovado, pois, especialmente no contexto de um finallybloco, que sugere que o recurso local precisa ser liberado manualmente quando toda a mentalidade em torno do C ++ é automatizá-lo.

Em termos de gerenciamento manual de memória, eu diria que C requer o máximo, Java / C # uma quantidade média e C ++ o mínimo entre esses. Há muitas razões para ser um pouco tímido em usar C ++, pois é uma linguagem muito difícil de dominar, mas o gerenciamento de memória não deve ser um deles. Pelo contrário, acho que é uma das línguas mais fáceis por aí neste aspecto.

É claro que o C ++ permite que você comece a alocar manualmente a memória e a invocar operator delete/delete[]para liberar memória manualmente. Também permite usar funções C como mallocefree. Mas essas são práticas de codificação no estilo antigo que eu acho que se tornaram obsoletas muito antes das pessoas darem crédito, já que Stroustrup estava defendendo a RAII antes mesmo de cunhar o termo desde muito cedo. Portanto, nem acho justo dizer que "C ++ moderno" automatiza o gerenciamento de recursos, porque esse deveria ser o objetivo o tempo todo. Praticamente não é possível obter segurança de exceção. Só que muitos desenvolvedores mal orientados durante o início dos anos 90 tentaram usar C ++ como C com objetos, geralmente ignorando completamente o tratamento de exceções, e nunca era para ser usado dessa maneira. Se você usa C ++ da maneira que ele sempre pretendia ser usado, o gerenciamento de memória é totalmente automatizado e, geralmente, não é algo com o qual você tenha que lidar manualmente (ou deveria estar lidando com) muito.


fonte
1
O Java moderno tem "tentativa com recursos", que remove todo esse código confuso no bloco final. Raramente é necessário ter um bloqueio final. Parece que os designers copiaram o conceito RAII.
Kiwiron
0

Depende dos líderes técnicos sênior da equipe. Em algumas empresas (incluindo a minha), não existe um conceito chamado smart poiner. É considerado chique. Portanto, as pessoas simplesmente excluem todos os lugares e há uma unidade para correção de vazamento de memória a cada 2 meses. Nova onda de instruções de exclusão chega a todos os lugares. Então, depende da empresa e do tipo de pessoas que trabalham lá.

Jagannath
fonte
1
Existe algo em seu ambiente que o impede de usar auto_ptre de amigos?
Sean McMillan
2
Parece que a sua empresa não escrever código C ++, você está escrevendo C.
gbjbaanb