O que acontece com o lixo em C ++?

51

Java tem um GC automático que de vez em quando para o mundo, mas cuida do lixo em uma pilha. Agora, os aplicativos C / C ++ não têm esses congelamentos do STW; o uso de memória também não aumenta infinitamente. Como esse comportamento é alcançado? Como são tratados os objetos mortos?

Ju Shua
fonte
38
Nota: stop-the-world é uma opção de implementação de alguns coletores de lixo, mas certamente não todos. Existem GCs simultâneos, por exemplo, que são executados simultaneamente com o mutador (é o que os desenvolvedores de GC chamam de programa real). Acredito que você pode comprar uma versão comercial da JVM J9 de código aberto da IBM que possui um coletor sem pausas simultâneo. O Azul Zing possui um coletor " sem pausa " que não é realmente sem pausa, mas é extremamente rápido, para que não haja pausas perceptíveis (suas pausas no GC estão na mesma ordem que uma troca de contexto de encadeamento do sistema operacional, que geralmente não é vista como uma pausa) .
Jörg W Mittag
14
A maioria dos programas em C (de longa execução) ++ eu uso faz tem uso de memória que cresce tempo sobre unboundedly. É possível que você não tenha o hábito de deixar os programas abertos por mais de alguns dias de cada vez?
Jonathan fundido
12
Leve em consideração que, com o C ++ moderno e suas construções, você não precisa mais excluir manualmente a memória (a menos que esteja buscando uma otimização especial), porque é possível gerenciar a memória dinâmica por meio de ponteiros inteligentes. Obviamente, isso acrescenta alguma sobrecarga ao desenvolvimento do C ++ e você precisa ter um pouco mais de cuidado, mas não é uma coisa totalmente diferente, basta lembrar de usar a construção de ponteiro inteligente em vez de apenas chamar manual new.
Andy
9
Observe que ainda é possível haver vazamentos de memória em um idioma coletado pelo lixo. Não estou familiarizado com Java, mas infelizmente os vazamentos de memória são bastante comuns no mundo do .NET gerenciado pela GC. Objetos indiretamente referenciados por um campo estático não são coletados automaticamente, os manipuladores de eventos são uma fonte muito comum de vazamentos, e a natureza não determinística da coleta de lixo o torna incapaz de evitar completamente a necessidade de liberar recursos manualmente (levando ao IDisposable padronizar). Dito isso, o modelo de gerenciamento de memória C ++ usado corretamente é muito superior à coleta de lixo.
Cody Grey
26
What happens to garbage in C++? Geralmente não é compilado em um executável?
BJ Myers

Respostas:

100

O programador é responsável por garantir que os objetos que eles criaram via newsejam excluídos via delete. Se um objeto for criado, mas não destruído antes do último ponteiro ou referência a ele ficar fora do escopo, ele entrará em conflito e se tornará um vazamento de memória .

Infelizmente para C, C ++ e outras linguagens que não incluem um GC, isso simplesmente se acumula ao longo do tempo. Isso pode fazer com que um aplicativo ou o sistema fique sem memória e não consiga alocar novos blocos de memória. Nesse ponto, o usuário deve recorrer ao encerramento do aplicativo para que o sistema operacional possa recuperar a memória usada.

Quanto a mitigar esse problema, há várias coisas que facilitam a vida de um programador. Estes são suportados principalmente pela natureza do escopo .

int main()
{
    int* variableThatIsAPointer = new int;
    int variableInt = 0;

    delete variableThatIsAPointer;
}

Aqui, criamos duas variáveis. Eles existem no escopo do bloco , conforme definido pelas {}chaves. Quando a execução sair desse escopo, esses objetos serão excluídos automaticamente. Nesse caso, variableThatIsAPointercomo o próprio nome indica, é um ponteiro para um objeto na memória. Quando sai do escopo, o ponteiro é excluído, mas o objeto para o qual ele aponta permanece. Aqui, analisamos deleteesse objeto antes de sair do escopo para garantir que não haja vazamento de memória. No entanto, também poderíamos ter passado esse ponteiro em outro lugar e esperamos que ele seja excluído mais tarde.

Essa natureza do escopo se estende às classes:

class Foo
{
public:
    int bar; // Will be deleted when Foo is deleted
    int* otherBar; // Still need to call delete
}

Aqui, o mesmo princípio se aplica. Não precisamos nos preocupar barquando Fooé excluído. No entanto otherBar, apenas o ponteiro é excluído. Se otherBaré o único ponteiro válido para qualquer objeto que ele aponte, provavelmente devemos destruí- deletelo Foo. Este é o conceito de condução por trás da RAII

a alocação de recursos (aquisição) é feita durante a criação do objeto (especificamente a inicialização), pelo construtor, enquanto a alocação de recursos (liberação) é feita durante a destruição do objeto (especificamente a finalização), pelo destruidor. Assim, é garantido que o recurso seja mantido entre o momento em que a inicialização termina e a finalização inicia (manter os recursos é uma classe invariável) e deve ser mantido apenas quando o objeto estiver ativo. Portanto, se não houver vazamentos de objetos, não haverá vazamentos de recursos.

RAII também é a força motriz típica por trás dos ponteiros inteligentes . Na biblioteca padrão C ++, estes são std::shared_ptr, std::unique_ptre std::weak_ptr; embora eu tenha visto e usado outros shared_ptr/ weak_ptrimplementações que seguem os mesmos conceitos. Para isso, um contador de referência rastreia quantos ponteiros existem para um determinado objeto e automaticamente deleteo objeto quando não houver mais referências a ele.

Além disso, tudo se resume a práticas e disciplina adequadas para um programador garantir que seu código lide com objetos corretamente.

Thebluefish
fonte
4
excluído via delete- é o que eu estava procurando. Impressionante.
Ju Suá
3
Você pode querer adicionar sobre os mecanismos de escopo fornecidos no c ++ que permitem que grande parte do novo e da exclusão sejam feitos principalmente de forma automática.
Whatsisname
9
@whatsisname não é tão novo e de exclusão são feitas automática, é que eles não ocorrem em tudo, em muitos casos
Caleth
10
O ponteirodelete é chamado automaticamente por ponteiros inteligentes, se você os usar, portanto, considere usá-los sempre que um armazenamento automático não puder ser usado.
Marian Spanik
11
@JuShua Observe que, ao escrever C ++ moderno, você não precisa realmente ter deleteo código do aplicativo (e a partir do C ++ 14 em diante, o mesmo com new), mas use ponteiros inteligentes e RAII para excluir objetos de heap. std::unique_ptrtipo e std::make_uniquefunção são a substituição direta e mais simples newe deleteno nível do código do aplicativo.
Hyde
82

C ++ não tem coleta de lixo.

Os aplicativos C ++ são necessários para descartar seu próprio lixo.

Os programadores de aplicativos C ++ são necessários para entender isso.

Quando eles esquecem, o resultado é chamado de "vazamento de memória".

John R. Strohm
fonte
22
Você certamente se certificou de que sua resposta também não contém lixo, nem clichê ...
leftaroundabout
15
@leftaroundabout: Obrigado. Eu considero isso um elogio.
John R. Strohm
11
OK, esta resposta sem lixo possui uma palavra-chave para procurar: vazamento de memória. Também seria bom mencionar de alguma forma newe delete.
Ruslan
4
@Ruslan O mesmo também se aplica a malloce free, ou new[]e delete[], ou quaisquer outros alocadores (como Windows, da GlobalAlloc, LocalAlloc, SHAlloc, CoTaskMemAlloc, VirtualAlloc, HeapAlloc, ...), e memória alocada para você (por exemplo, via fopen).
user253751
43

Em C, C ++ e outros sistemas sem um Garbage Collector, o desenvolvedor recebe recursos da linguagem e de suas bibliotecas para indicar quando a memória pode ser recuperada.

A instalação mais básica é o armazenamento automático . Muitas vezes, o próprio idioma garante que os itens sejam descartados:

int global = 0; // automatic storage

int foo(int a, int b) {
    static int local = 1; // automatic storage

    int c = a + b; // automatic storage

    return c;
}

Nesses casos, o compilador é responsável por saber quando esses valores não são utilizados e recuperar o armazenamento associado a eles.

Ao usar o armazenamento dinâmico , em C, a memória é tradicionalmente alocada malloce recuperada com free. Em C ++, a memória é tradicionalmente alocada newe recuperada com delete.

C não mudou muito ao longo dos anos, no entanto moderno C ++ evita newe deletecompletamente e se baseia em vez de instalações de biblioteca (que se usa newe deleteapropriadamente):

  • ponteiros inteligentes são os mais famosos: std::unique_ptrestd::shared_ptr
  • mas recipientes são muito mais difundido na verdade: std::string, std::vector, std::map, ... gerenciar todo internamente memória alocada dinamicamente transparente

Falando nisso shared_ptr, há um risco: se um ciclo de referências for formado, e não quebrado, poderá haver vazamento de memória. Cabe ao desenvolvedor evitar essa situação, a maneira mais simples é evitar shared_ptrcompletamente e a segunda mais simples é evitar ciclos no nível de tipo.

Como resultado vazamentos de memória não são um problema em C ++ , mesmo para novos usuários, enquanto eles se abstenham de usar new, deleteou std::shared_ptr. Isso é diferente de C, onde uma disciplina firme é necessária e geralmente insuficiente.


No entanto, essa resposta não seria completa sem mencionar a irmã gêmea dos vazamentos de memória: indicadores pendentes .

Um ponteiro pendente (ou referência pendente) é um risco criado ao manter um ponteiro ou referência a um objeto que está morto. Por exemplo:

int main() {
    std::vector<int> vec;
    vec.push_back(1);     // vec: [1]

    int& a = vec.back();

    vec.pop_back();       // vec: [], "a" is now dangling

    std::cout << a << "\n";
}

Usar um ponteiro oscilante ou referência é Comportamento indefinido . Em geral, felizmente, isso é um acidente imediato; muitas vezes, infelizmente, isso causa corrupção de memória primeiro ... e de tempos em tempos surge um comportamento estranho, porque o compilador emite um código realmente estranho.

Comportamento indefinido é o maior problema com C e C ++ até hoje, em termos de segurança / correção de programas. Convém verificar Rust para um idioma sem Garbage Collector e sem comportamento indefinido.

Matthieu M.
fonte
17
Re: "Usando um ponteiro pendente, ou referência, é comportamento indefinido . Em geral, felizmente, isso é um acidente imediato": Sério? Isso não corresponde à minha experiência; pelo contrário, minha experiência é que o uso de um ponteiro oscilante quase nunca causa uma falha imediata. . .
ruakh 16/06
9
Sim, uma vez que, para estar "pendurado", um ponteiro deve ter como alvo a memória alocada anteriormente em um ponto, e é improvável que essa memória tenha sido completamente não mapeada do processo, de modo que não seja mais acessível, porque será um bom candidato para reutilização imediata ... na prática, ponteiros pendurados não causam falhas, causam caos.
Leushenko
2
"Como resultado, vazamentos de memória não são um problema no C ++", com certeza existem, sempre existem ligações C às bibliotecas para estragar, além de shared_ptrs recursivos ou até mesmo únicos e recursivos, e outras situações.
Mooing Duck
3
“Não é um problema em C ++, mesmo para novos usuários” - eu qualificaria isso para “novos usuários que não vêm de uma linguagem semelhante a Java ou C ”.
leftaroundabout
3
@leftaroundabout: é qualificado "enquanto eles se abstenham de usar new, deletee shared_ptr"; sem newe shared_ptrvocê tem propriedade direta, portanto não há vazamentos. Claro, é provável que você tenha ponteiros pendentes, etc ... mas receio que você precise sair do C ++ para se livrar deles.
Matthieu M.
27

C ++ tem essa coisa chamada RAII . Basicamente, isso significa que o lixo é limpo à medida que você vai, em vez de deixá-lo em uma pilha e deixar o limpador arrumar depois de você. (imagine-me no meu quarto assistindo futebol - enquanto bebo latas de cerveja e preciso de novas, a maneira C ++ é levar a lata vazia para a lixeira a caminho da geladeira, a maneira C # é jogá-la no chão e espere a empregada buscá-los quando ela vier fazer a limpeza).

Agora é possível vazar memória no C ++, mas, para isso, é necessário deixar as construções usuais e reverter para o modo C de fazer as coisas - alocar um bloco de memória e acompanhar onde esse bloco está sem nenhuma assistência de idioma. Algumas pessoas esquecem esse ponteiro e, portanto, não podem remover o bloco.

gbjbaanb
fonte
9
Ponteiros compartilhados (que usam RAII) fornecem uma maneira moderna de criar vazamentos. Suponha que os objetos A e B se refiram por meio de ponteiros compartilhados e nada mais faça referência ao objeto A ou ao objeto B. O resultado é um vazamento. Essa referência mútua não é um problema em idiomas com coleta de lixo.
David Hammen
@DavidHammen com certeza, mas a um custo de percorrer quase todos os objetos para ter certeza. Seu exemplo dos ponteiros inteligentes ignora o fato de que o ponteiro inteligente em si ficará fora do escopo e os objetos serão liberados. Você assume que um ponteiro inteligente é como um ponteiro, não é, é um objeto que é passado na pilha como a maioria dos parâmetros. Isso não é muito diferente dos vazamentos de memória causados ​​nos idiomas do GC. por exemplo, o famoso em que a remoção de um manipulador de eventos de uma classe de interface do usuário o deixa silenciosamente referenciado e, portanto, vazando.
Gbjbaanb
11
@gbjbaanb no exemplo com os ponteiros inteligentes, nem ponteiro inteligente que nunca sai do escopo, é por isso que há um vazamento. Como os dois objetos de ponteiro inteligente são alocados em um escopo dinâmico , não lexical, cada um deles tenta esperar o outro antes de destruí-lo. O fato de ponteiros inteligentes serem objetos reais em C ++ e não apenas ponteiros é exatamente o que causa o vazamento aqui - os objetos adicionais de ponteiros inteligentes em escopos de pilha que também apontam para os objetos contêineres não podem desalocá-los quando se destruirem porque a refcount é diferente de zero.
Leushenko
2
O jeito .NET não é jogá- lo no chão. Apenas o mantém onde estava até a empregada aparecer. E devido à maneira como o .NET aloca memória na prática (não contratual), o heap é mais como uma pilha de acesso aleatório. É como ter uma pilha de contratos e papéis e passar por isso de vez em quando para descartar aqueles que não são mais válidos. E para facilitar, os que sobrevivem a cada descarte são promovidos para uma pilha diferente, para que você possa evitar percorrer todas as pilhas na maioria das vezes - a menos que a primeira pilha fique grande o suficiente, a empregada não toca nas outras.
Luaan 17/06/16
@Luaan, era uma analogia ... Acho que você ficaria mais feliz se eu dissesse que deixa latas deitadas sobre a mesa até que a empregada venha limpar.
Gbjbaanb
26

Deve-se notar que, no caso do C ++, é um equívoco comum que "você precisa executar o gerenciamento manual de memória". De fato, você geralmente não faz nenhum gerenciamento de memória no seu código.

Objetos de tamanho fixo (com vida útil do escopo)

Na grande maioria dos casos em que você precisa de um objeto, o objeto terá uma vida útil definida em seu programa e é criado na pilha. Isso funciona para todos os tipos de dados primitivos internos, mas também para instâncias de classes e estruturas:

class MyObject {
    public: int x;
};

int objTest()
{
    MyObject obj;
    obj.x = 5;
    return obj.x;
}

Os objetos de pilha são removidos automaticamente quando a função termina. Em Java, os objetos são sempre criados no heap e, portanto, precisam ser removidos por algum mecanismo como a coleta de lixo. Este não é um problema para objetos de pilha.

Objetos que gerenciam dados dinâmicos (com vida útil do escopo)

Usar espaço na pilha funciona para objetos de tamanho fixo. Quando você precisa de uma quantidade variável de espaço, como uma matriz, outra abordagem é usada: A lista é encapsulada em um objeto de tamanho fixo que gerencia a memória dinâmica para você. Isso funciona porque os objetos podem ter uma função de limpeza especial, o destruidor. É garantido que será chamado quando o objeto sair do escopo e fizer o oposto do construtor:

class MyList {        
public:
    // a fixed-size pointer to the actual memory.
    int* listOfInts; 
    // constructor: get memory
    MyList(size_t numElements) { listOfInts = new int[numElements]; }
    // destructor: free memory
    ~MyList() { delete[] listOfInts; }
};

int listTest()
{
    MyList list(1024);
    list.listOfInts[200] = 5;
    return list.listOfInts[200];
    // When MyList goes off stack here, its destructor is called and frees the memory.
}

Não há gerenciamento de memória no código em que a memória é usada. A única coisa que precisamos ter certeza é que o objeto que escrevemos tem um destruidor adequado. Não importa como deixemos o escopo listTest, seja por uma exceção ou simplesmente retornando, o destruidor ~MyList()será chamado e não precisamos gerenciar nenhuma memória.

(Eu acho que é uma decisão engraçada de design usar o operador binário NOT ,, ~para indicar o destruidor. Quando usado em números, ele inverte os bits; por analogia, aqui indica que o que o construtor fez é invertido.)

Basicamente, todos os objetos C ++ que precisam de memória dinâmica usam esse encapsulamento. Foi chamado RAII ("aquisição de recursos é inicialização"), que é uma maneira bastante estranha de expressar a idéia simples de que os objetos se preocupam com seu próprio conteúdo; o que eles adquirem é deles para limpar.

Objetos polimórficos e vida útil além do escopo

Agora, os dois casos foram para memória com uma vida útil claramente definida: a vida útil é igual ao escopo. Se não queremos que um objeto expire ao sair do escopo, existe um terceiro mecanismo que pode gerenciar a memória para nós: um ponteiro inteligente. Ponteiros inteligentes também são usados ​​quando você tem instâncias de objetos cujo tipo varia em tempo de execução, mas que possuem uma interface ou classe base comum:

class MyDerivedObject : public MyObject {
    public: int y;
};
std::unique_ptr<MyObject> createObject()
{
    // actually creates an object of a derived class,
    // but the user doesn't need to know this.
    return std::make_unique<MyDerivedObject>();
}

int dynamicObjTest()
{
    std::unique_ptr<MyObject> obj = createObject();
    obj->x = 5;
    return obj->x;
    // At scope end, the unique_ptr automatically removes the object it contains,
    // calling its destructor if it has one.
}

Há outro tipo de ponteiro inteligente std::shared_ptr, para compartilhar objetos entre vários clientes. Eles apenas excluem seu objeto contido quando o último cliente fica fora do escopo, para que possam ser usados ​​em situações em que é completamente desconhecido quantos clientes haverá e quanto tempo eles usarão o objeto.

Em resumo, vemos que você realmente não faz nenhum gerenciamento manual de memória. Tudo é encapsulado e, então, resolvido por meio de um gerenciamento de memória completamente automático e baseado em escopo. Nos casos em que isso não é suficiente, são usados ​​indicadores inteligentes que encapsulam a memória bruta.

É uma prática extremamente ruim usar ponteiros brutos como proprietários de recursos em qualquer lugar do código C ++, alocações brutas fora dos construtores e deletechamadas brutas fora dos destruidores, pois são quase impossíveis de gerenciar quando ocorrem exceções e geralmente difíceis de usar com segurança.

O melhor: isso funciona para todos os tipos de recursos

Um dos maiores benefícios do RAII é que ele não se limita à memória. Na verdade, fornece uma maneira muito natural de gerenciar recursos, como arquivos e soquetes (abertura / fechamento) e mecanismos de sincronização, como mutexes (bloqueio / desbloqueio). Basicamente, todos os recursos que podem ser adquiridos e devem ser liberados são gerenciados exatamente da mesma maneira em C ++, e nada desse gerenciamento é deixado para o usuário. Tudo é encapsulado em classes que são adquiridas no construtor e liberadas no destruidor.

Por exemplo, uma função que bloqueia um mutex geralmente é escrita assim em C ++:

void criticalSection() {
    std::scoped_lock lock(myMutex); // scoped_lock locks the mutex
    doSynchronizedStuff();
} // myMutex is released here automatically

Outras línguas tornam isso muito mais complicado, exigindo que você faça isso manualmente (por exemplo, em uma finallycláusula) ou geram mecanismos especializados que resolvem esse problema, mas não de uma maneira particularmente elegante (geralmente mais tarde na vida, quando há pessoas suficientes). sofria da deficiência). Esses mecanismos são try-with-resources em Java e a instrução using em C #, ambas aproximações do RAII do C ++.

Então, para resumir, tudo isso foi um relato muito superficial do RAII em C ++, mas espero que ajude os leitores a entender que a memória e até o gerenciamento de recursos em C ++ não são geralmente "manuais", mas na maioria das vezes automáticos.

Felix Dombek
fonte
7
Esta é a única resposta que não desinforma as pessoas nem pinta o C ++ mais difícil ou perigoso do que realmente é.
Alexander Revo
6
BTW, é apenas uma prática recomendada usar ponteiro bruto como proprietários de recursos. Não há nada de errado em usá-los se eles apontarem para algo que certamente sobreviverá ao próprio ponteiro.
Alexander Revo
8
Eu segundo Alexander. Estou perplexo ao ver que as deleterespostas "O C ++ não possui gerenciamento de memória automatizado, esqueça um e você está morto" subindo acima de 30 pontos e sendo aceito, enquanto este tem cinco. Alguém realmente usa C ++ aqui?
Quentin
8

Com relação ao C especificamente, o idioma não fornece ferramentas para gerenciar a memória alocada dinamicamente. Você é absolutamente responsável por garantir que todos *alloctenham um correspondente em freealgum lugar.

Onde as coisas ficam realmente desagradáveis ​​é quando uma alocação de recursos falha no meio; você tenta de novo, reverte e recomeça desde o início, retrocede e sai com um erro, apenas se livra completamente e deixa o sistema operacional lidar com isso?

Por exemplo, aqui está uma função para alocar uma matriz 2D não contígua. O comportamento aqui é que, se ocorrer uma falha de alocação no meio do processo, reverteremos tudo e retornaremos uma indicação de erro usando um ponteiro NULL:

/**
 * Allocate space for an array of arrays; returns NULL
 * on error.
 */
int **newArr( size_t rows, size_t cols )
{
  int **arr = malloc( sizeof *arr * rows );
  size_t i;

  if ( arr ) // malloc returns NULL on failure
  {
    for ( i = 0; i < rows; i++ )
    {
      arr[i] = malloc( sizeof *arr[i] * cols );
      if ( !arr[i] )
      {
        /**
         * Whoopsie; we can't allocate any more memory for some reason.
         * We can't just return NULL at this point since we'll lose access
         * to the previously allocated memory, so we branch to some cleanup
         * code to undo the allocations made so far.  
         */
        goto cleanup;
      }
    }
  }
  goto done;

/**
 * We encountered a failure midway through memory allocation,
 * so we roll back all previous allocations and return NULL.
 */
cleanup:
  while ( i )         // this is why we didn't limit the scope of i to the for loop
    free( arr[--i] ); // delete previously allocated rows
  free( arr );        // delete arr object
  arr = NULL;

done:
  return arr;
}

Esse código é muito feio com esses gotos, mas, na ausência de qualquer tipo de mecanismo estruturado de manipulação de exceções, é praticamente a única maneira de lidar com o problema sem apenas sair completamente, especialmente se o código de alocação de recursos estiver aninhado mais de um loop profundo. Este é um dos poucos momentos em que gotoé realmente uma opção atraente; caso contrário, você está usando um monte de sinalizadores e ifinstruções extras .

Você pode facilitar sua vida escrevendo funções dedicadas de alocador / desalocador para cada recurso, algo como

Foo *newFoo( void )
{
  Foo *foo = malloc( sizeof *foo );
  if ( foo )
  {
    foo->bar = newBar();
    if ( !foo->bar ) goto cleanupBar;
    foo->bletch = newBletch(); 
    if ( !foo->bletch ) goto cleanupBletch;
    ...
  }
  goto done;

cleanupBletch:
  deleteBar( foo->bar );
  // fall through to clean up the rest

cleanupBar:
  free( foo );
  foo = NULL;

done:
  return foo;
}

void deleteFoo( Foo *f )
{
  deleteBar( f->bar );
  deleteBletch( f->bletch );
  free( f );
}
John Bode
fonte
11
Esta é uma boa resposta, mesmo com as gotodeclarações. Esta é uma prática recomendada em algumas áreas. É um esquema comumente usado para proteger contra o equivalente a exceções em C. Dê uma olhada no código do kernel do Linux, que está repleto de gotoinstruções - e que não vaza.
David Hammen
"sem apenas sair completamente" -> com justiça, se você quiser falar sobre C, essa provavelmente é uma boa prática. C é uma linguagem melhor usada para manipular blocos de memória que vieram de outro lugar ou dividir pequenos pedaços de memória para outros procedimentos, mas, de preferência, não fazendo as duas coisas ao mesmo tempo de maneira intercalada. Se você estiver usando "objetos" clássicos em C, provavelmente não usará a linguagem em seus pontos fortes.
Leushenko
O segundo gotoé estranho. Seria mais legível se você mudasse goto done;para return arr;e arr=NULL;done:return arr;para return NULL;. Embora, em casos mais complicados, possa realmente haver vários gotos, começando a se desenrolar em diferentes níveis de prontidão (o que seria feito pela exceção da pilha de exceções em C ++).
Ruslan
2

Aprendi a classificar problemas de memória em várias categorias diferentes.

  • Uma vez pinga. Suponha que um programa vaze 100 bytes no momento da inicialização, apenas para nunca vazar novamente. Perseguir e eliminar esses vazamentos únicos é bom (eu gosto de ter um relatório limpo por um recurso de detecção de vazamentos), mas não é essencial. Às vezes, existem problemas maiores que precisam ser atacados.

  • Vazamentos repetidos. Uma função chamada repetidamente durante a vida útil de um programa que regularmente vaza memória um grande problema. Esses gotejamentos torturam o programa, e possivelmente o sistema operacional, até a morte.

  • Referências mútuas. Se os objetos A e B se referenciarem através de ponteiros compartilhados, será necessário fazer algo especial, no design dessas classes ou no código que implementa / usa essas classes para quebrar a circularidade. (Isso não é um problema para idiomas coletados pelo lixo.)

  • Lembrando demais. Este é o primo do mal do lixo / vazamentos de memória. O RAII não ajudará aqui, nem a coleta de lixo. Este é um problema em qualquer idioma. Se alguma variável ativa tiver um caminho que a conecte a algum pedaço aleatório de memória, esse pedaço aleatório de memória não será lixo. Tornar um programa esquecido para que ele possa ser executado por vários dias é complicado. Criar um programa que possa ser executado por vários meses (por exemplo, até o disco falhar) é muito, muito complicado.

Não tenho um problema sério de vazamento há muito, muito tempo. O uso de RAII em C ++ ajuda bastante a resolver esses vazamentos. (Porém, é preciso ter cuidado com os ponteiros compartilhados.) Muito mais importante: tive problemas com aplicativos cujo uso de memória continua a crescer cada vez mais por causa de conexões indesejadas à memória que não são mais úteis.

David Hammen
fonte
-6

Cabe ao programador C ++ implementar sua própria forma de coleta de lixo, quando necessário. Não fazer isso resultará no que é chamado de 'vazamento de memória'. É bastante comum que linguagens de 'alto nível' (como Java) tenham construído na coleta de lixo, mas linguagens de 'baixo nível', como C e C ++, não.

xDr_Johnx
fonte