In C ++, quanto tempo del programmatore viene impiegato per la gestione della memoria


39

Le persone che sono abituate a spazzare via le lingue raccolte hanno spesso paura della gestione della memoria del C ++. Ci sono strumenti come auto_ptre shared_ptrche gestiranno molte delle attività di gestione della memoria per te. Molte librerie C ++ precedono quegli strumenti e hanno il loro modo di gestire le attività di gestione della memoria.

Quanto tempo dedichi alle attività di gestione della memoria?

Ho il sospetto che dipenda fortemente dall'insieme di librerie che usi, quindi per favore di 'a quali si applica la tua risposta e se la rendono migliore o peggiore.


1
Non molto, davvero ... Soprattutto con C ++ 0x, riferimenti e STL. Puoi persino scrivere codice senza alcuna gestione della memoria.
Coder

9
In generale: non molto se si ha esperienza. Molto se sei alle prime armi con C ++ (-> di solito cerca perdite di memoria / risorse).
MaR,

1
Trovo la vera domanda, in questi giorni, più che riguarda l'inseguimento di riferimenti stantii. Ed è di solito abbastanza evidente ogni volta, solo fastidioso che non sia stato catturato prima: p
Matthieu M.

So che questo è vecchio, ma la gestione della memoria IMO è parte integrante dell'essere un buon programmatore. Astrazioni come i contenitori STL sono belle, ma l'ignoranza della memoria è contraria all'idea stessa del calcolo stesso. Si potrebbe anche chiedere come si possono eliminare la manipolazione algebrica, la logica e il loop dall'arsenale del programmatore.
imallett,

Che ne dici di "quanto tempo viene impiegato per il debug della gestione della memoria andato storto?" Di per sé, la gestione della memoria è possibile e non così difficile in C ++. Il fatto è: impostarlo è un mestiere preciso ed è molto incline a incazzature. Quando fai una cazzata, potresti anche non accorgertene, e rintracciare vecchi errori con comportamenti irregolari accumulati nel tempo, è il tempo reale che dovresti avere paura. Ecco perché i moderni linguaggi non immondizia raccolti (sto pensando alla ruggine) hanno spostato molte responsabilità nel controllare gli errori tipici nel compilatore.
ZJR il

Risposte:


54

Il moderno C ++ non ti preoccupa della gestione della memoria fino a quando non è necessario, cioè fino a quando non è necessario organizzare la memoria manualmente, principalmente per scopi di ottimizzazione o se il contesto ti costringe a farlo (pensa all'hardware con grandi vincoli). Ho scritto interi giochi senza manipolare la memoria grezza, preoccupandomi solo di usare contenitori che sono lo strumento giusto per il lavoro, come in qualsiasi lingua.

Quindi dipende dal progetto ma la maggior parte delle volte non è la gestione della memoria che devi gestire, ma solo la durata dell'oggetto. Ciò viene risolto utilizzando i puntatori intelligenti , ovvero uno strumento idiomatico C ++ derivante da RAII .

Una volta capito RAII , la gestione della memoria non sarà un problema.

Quindi, quando avrai bisogno di accedere alla memoria grezza, lo farai in un codice molto specifico, localizzato e identificabile, come nelle implementazioni di oggetti pool, non "ovunque".

Al di fuori di questo tipo di codice, non sarà necessario manipolare la memoria, ma solo la durata degli oggetti.

La parte "difficile" è capire RAII.


10
Assolutamente vero. Negli ultimi 5 anni, ho scritto "elimina" solo quando lavoro con il codice legacy.
drxzcl,

3
Lavoro in un ambiente incorporato con dimensioni di stack elevate. Per quanto bello sia RAII, non funziona bene se lo spazio dello stack è un premio. Quindi è tornato alla microgestione dei puntatori.
bastibe

1
@nikie Uso i puntatori intelligenti delle librerie nel codice che manipolano la loro API, quindi uso i puntatori intelligenti standard o potenziati nel codice specifico per la mia applicazione (se sono io a decidere). Se è possibile isolare il codice della libreria in alcuni moduli che astraggono il modo in cui vengono utilizzati nell'applicazione, si evita l'inquinamento delle API dalle dipendenze.
Klaim,

12
@Paperflyer: RAII non occuperà più spazio dello stack che deletemanualmente, a meno che tu non abbia un'implementazione di merda.
DeadMG

2
@Paperflyer: il puntatore intelligente sull'heap occupa lo stesso spazio; la differenza è che il compilatore inserisce il codice di deallocazione delle risorse su tutte le uscite da una funzione. E poiché questo è così ampiamente usato, questo è in genere ben ottimizzato (ad esempio, piegando più uscite insieme in modi che non puoi - non puoi mettere il codice dopo un return)
MSalters

32

La gestione della memoria viene utilizzata per spaventare i bambini, ma è solo un tipo di risorsa che un programmatore deve occuparsi. Pensa agli handle di file, alle connessioni di rete e ad altre risorse che ottieni dal sistema operativo.

Le lingue che supportano la garbage collection di solito non solo ignorano l'esistenza di queste risorse, ma rendono anche più difficile gestirle correttamente non fornendo un distruttore.

Quindi, in breve, suggerirei di non perdere gran parte del tempo di uno sviluppatore C ++ preoccupandosi della gestione della memoria. Come indica la risposta di Klaim , una volta ottenuto il controllo su RAII, il resto è solo riflesso.


3
In particolare, adoro il modo in cui HttpWebRequest.GetResponse perde le maniglie e inizia a bloccarsi nei linguaggi GC. GC è fantastico, fino a quando non inizia a succhiare perché le risorse perdono ancora. msdn.microsoft.com/en-us/library/… Vedi "Attenzione".
Coder

6
+1 per visualizzare la memoria come risorsa. Codice legacy o no, quante volte abbiamo bisogno di gridare ad alta voce: la gestione della memoria è un'abilità e non una maledizione .
aquaherd,

4
@Coder Non sono sicuro se seguo .. GC fa schifo perché è possibile abusare delle risorse comunque ..? Penso che C # faccia un buon lavoro fornendo risorse deterministiche rilasciando usando IDisposable ...
Max

8
@Max: Perché se viene raccolta spazzatura, allora mi aspetto di non preoccuparmi di risorse stupide tramite l'utilizzo e IDisposables personalizzati. Le risorse hanno lasciato l'ambito, tutto qui, dovrebbero essere pulite. In realtà, tuttavia, devo ancora pensare e indovinare quali trapeleranno e quali no. In primo luogo, batte qualsiasi motivo per usare il linguaggio GC.
Coder

5
@deadalnix Hanno il finalizecostrutto. Tuttavia, non sai quando verrà chiamato. Sarà prima di rimanere senza socket o oggetti WebResponse? Troverai moltissimi articoli che ti dicono che non dovresti fare affidamento finalize- con una buona ragione.
Disastro

13

Praticamente nessuno. Anche vecchie tecnologie come COM, è possibile scrivere deleter personalizzati per i puntatori standard che li convertiranno in pochissimo tempo. Ad esempio, std::unique_ptrpuò essere convertito per contenere in modo univoco un riferimento COM con cinque righe di un deleter personalizzato. Anche se devi scrivere manualmente il tuo gestore di risorse, la prevalenza di conoscenze come SRP e copy-and-swap rende relativamente semplice scrivere una classe di gestione delle risorse da usare per sempre di più.

La realtà è che la condivisione, l'unicità e la non proprietà sono tutte fornite con il tuo compilatore C ++ 11 e devi solo scrivere piccoli adattatori per farli funzionare anche con il vecchio codice.


1
Quanta abilità con C ++ devi avere a) scrivere un cancellatore personalizzato b) sapere che un cancellatore personalizzato è ciò di cui hai bisogno? Lo chiedo perché sembra facile apprendere un nuovo linguaggio GC'd e avvicinarsi alla correzione senza sapere tutto - è facile anche nel C ++?
Sean McMillan,

1
@SeanMcMillan: i deleter personalizzati sono banali da scrivere e distribuire, quello COM che ho citato è di cinque righe per tutti i tipi di COM e chiunque abbia una formazione di base nel moderno C ++ dovrebbe conoscerli. Non puoi imparare un linguaggio GCed, perché a sorpresa il GC non raccoglierà oggetti COM. O handle di file. O memoria ottenuta da altri sistemi. O connessioni al database. RAII farà tutte queste cose.
DeadMG

2
Con "Pick up a GC'd language", intendevo che sono passato da Java / C # / Ruby / Perl / Javascript / Python e hanno tutti lo stesso stile di gestione delle risorse: la memoria è per lo più automatica e tutto il resto , devi gestire. Mi sembra che tu stia dicendo che gli strumenti di gestione di C ++ ti consentono di gestire handle di file / connessioni db / etc allo stesso modo della memoria, e che è relativamente semplice una volta appreso. Non un intervento chirurgico al cervello. Ho capito bene?
Sean McMillan,

3
@SeanMcMillan: Sì, è esatto, e non è complesso.
DeadMG

11

Quando ero un programmatore C ++ (molto tempo fa), passavo molto tempo a preoccuparmi dei bug di gestione della memoria quando cercavo di correggere i bug difficili da riprodurre .

Con il modem C ++, la gestione della memoria è molto meno problematica, ma puoi fidarti di tutti i componenti di una grande squadra per farlo bene. Qual è il costo / tempo di:

  • Formazione (non molti programmatori nascono con una buona comprensione dei problemi)
  • Revisioni del codice per trovare problemi di gestione della memoria
  • Debug di problemi di gestione della memoria
  • Bisogna sempre tenere presente che un bug in una parte dell'app potrebbe essere dovuto a un problema di gestione della memoria in una parte non correlata dell'app .

Quindi non è solo il tempo a " fare ", questo è più un problema per i grandi progetti.


2
Penso che alcuni progetti C ++ abbiano disperato di aver mai corretto alcune delle loro perdite di memoria a causa di un codice scritto male. Il cattivo codice sta per accadere e quando lo fa può richiedere anche molto tempo ad altre persone.
Jeremy,

@Jeremy, ho scoperto che quando sono passato da C ++ a C #, c'era ancora tanto codice scritto male (se non di più), ma almeno era molto facile trovare la parte del programma che aveva un determinato bug.
Ian,

1
sì, questo è il motivo per cui molti negozi si sono spostati su Java o .NET. La garbage collection mitiga l'inevitabile danno di un codice errato.
Jeremy,

1
Stranamente, non abbiamo questi problemi.
David Thornley,

1
@DavidThornley, penso che gran parte del problema derivasse dalla scrittura del codice UI in C ++, al giorno d'oggi la maggior parte del codice C ++ che vedo non è UI
Ian

2

Uso molto le librerie boost e TR1 e rendono la gestione della memoria in senso stretto (new / delete) un problema. D'altra parte, l'allocazione di memoria in C ++ non è economica e bisogna prestare attenzione a dove vengono creati questi puntatori condivisi fantasiosi. Si finisce per usare molto le aree di lavoro o lavorare con la memoria basata su stack. In generale, direi che è principalmente un problema di progettazione, non un problema di implementazione.


2

quanto tempo impiega come cliente? molto poco, una volta capito. quando un contenitore gestisce la vita e i riferimenti, è davvero molto semplice. imo, è molto più semplice del conteggio manuale dei riferimenti ed è praticamente trasparente se consideri il contenitore che usi come documentazione che il compilatore ti impedisce convenientemente di eseguire trasferimenti di proprietà non validi in un sistema di typesafe ben progettato.

la maggior parte del tempo che passo (come cliente) viene speso con tipi di altre API, quindi funzionano bene nel contesto dei tuoi programmi. Esempio: questo è il mio contenitore ThirdPartyFont, e supporta queste caratteristiche, e implementa la distruzione in questo modo, e riferimento a contare in questo modo, e la copia di questo modo, e ... . Molti di questi costrutti devono essere in atto, ed è spesso il luogo logico per metterli. se vuoi includerlo nel tempo o meno dipende dalla tua definizione (l'implementazione deve esistere quando si interfaccia con queste API, comunque, giusto?).

successivamente, dovrai prendere in considerazione la memoria e la proprietà. in un sistema di livello inferiore, è buono e necessario, ma possono essere necessari un po 'di tempo e impalcature per implementare il modo in cui spostare le cose. non lo vedo come un dolore poiché questo è un requisito di un sistema di livello inferiore. la proprietà, il controllo e la responsabilità sono evidenti.

quindi possiamo trasformarlo in API basate su c che utilizzano tipi opachi: i nostri contenitori ci consentono di astrarre tutti i piccoli dettagli di implementazione della gestione della vita e della copia di quei tipi opachi, il che alla fine rende la gestione delle risorse molto molto semplice e consente di risparmiare tempo, difetti, e riduce le implementazioni.

è davvero molto semplice utilizzarli: il problema (proveniente da GC) è che ora devi considerare la durata delle tue risorse. se sbagli, la risoluzione può richiedere molto tempo. l'apprendimento e l'integrazione della gestione esplicita della vita sono comprensibilmente complessi in confronto (non per tutte le persone): questo è il vero ostacolo. una volta che hai dimestichezza con il controllo delle vite e l'utilizzo di buone soluzioni, è davvero molto facile gestire le vite delle risorse. non è una parte significativa della mia giornata (a meno che non si sia insinuato un insetto difficile).

se non stai usando contenitori (puntatore automatico / condiviso), stai solo supplicando per il dolore.

ho implementato le mie librerie. mi ci vuole tempo per implementare queste cose, ma la maggior parte delle persone riutilizza (che di solito è una buona idea).


1

Intendi come dover liberare manualmente memoria, chiudere file, cose di questo tipo? In tal caso, direi il minimo e in genere meno della maggior parte delle altre lingue che ho usato, soprattutto se generalizziamo questo non solo per "gestione della memoria" ma "gestione delle risorse". In tal senso, in realtà penso che C ++ richieda una gestione manuale delle risorse inferiore rispetto a, diciamo Java o C #.

È principalmente dovuto ai distruttori che automatizzano la distruzione della risorsa (memoria o altro). In genere l'unica volta che devo liberare / distruggere una risorsa manualmente in C ++ è se sto implementando una struttura di dati di livello inferiore (cosa che la maggior parte delle persone non ha bisogno di fare) o usando un'API C dove passo solo un po 'di tempo wrapping della risorsa C che deve essere liberata / distrutta / chiusa manualmente in un wrapper C ++ conforme a RAII.

Naturalmente se un utente richiede di chiudere un'immagine in un software di modifica delle immagini, devo rimuovere l'immagine da una raccolta o qualcosa del genere. Ma si spera che ciò non valga come "memoria" o gestione delle "risorse" di un tipo che conta in questo contesto, dal momento che è praticamente richiesto in qualsiasi lingua se si desidera liberare la memoria associata a quell'immagine in quel momento. Ma tutto ciò che devi fare è rimuovere l'immagine dalla raccolta e il distruttore di immagini si occupa di tutto il resto.

Nel frattempo, se paragone, per esempio, a Java o C #, spesso trovi persone che devono chiudere manualmente i file lì, disconnettere manualmente i socket, impostare i riferimenti agli oggetti su null per consentire loro di essere immondizia, ecc. C'è molta più memoria manuale e gestione delle risorse in quelle lingue se me lo chiedi. In C ++ spesso non hai nemmeno bisogno di unlockun mutex manualmente, poiché l'armadietto mutex lo farà automaticamente quando il mutex esce dall'ambito. Ad esempio, non dovresti mai fare cose del genere in 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();
}

Non è necessario eseguire operazioni come la chiusura manuale dei file in C ++. Finiscono per chiudersi automaticamente nell'istante in cui escono dal campo di applicazione, di conseguenza, o se seguono percorsi di esecuzione normali o eccezionali. Cosa simile per risorse legate alla memoria come std::vector. Tale codice come file.Close()sopra sarebbe spesso disapprovato poiché, specialmente nel contesto di un finallyblocco, suggerisce che la risorsa locale deve essere liberata manualmente quando l'intera mentalità attorno a C ++ è quella di automatizzarla.

In termini di gestione manuale della memoria, direi che C richiede il massimo, Java / C # una quantità media e C ++ il minimo tra questi. Ci sono molte ragioni per essere un po 'timidi nell'uso del C ++ poiché è un linguaggio molto difficile da padroneggiare, ma la gestione della memoria non dovrebbe essere una di queste. Al contrario, in realtà penso che sia una delle lingue più semplici là fuori in questo aspetto.

Naturalmente C ++ ti consente di iniziare a allocare manualmente la memoria e invocare la operator delete/delete[]memoria libera manualmente. Ti permette anche di usare funzioni C come mallocefree. Ma si tratta di pratiche di codifica di tipo antico che credo siano diventate obsolete molto prima che le persone attribuissero credito, dal momento che Stroustrup sosteneva la RAII prima ancora che coniasse il termine fin dall'inizio. Quindi non penso nemmeno che sia giusto dire che il "moderno C ++" automatizza la gestione delle risorse, perché quello doveva essere lo scopo da sempre. Altrimenti non è praticamente possibile ottenere la sicurezza delle eccezioni. È solo che molti sviluppatori mal guidati all'inizio degli anni '90 hanno provato a usare C ++ come C con oggetti, spesso ignorando completamente la gestione delle eccezioni e non avrebbe mai dovuto essere usato in quel modo. Se usi C ++ nel modo in cui era praticamente sempre inteso che fosse usato, allora la gestione della memoria è totalmente automatizzata e in genere non è qualcosa che devi affrontare (o che dovresti trattare) molto manualmente.


1
Java moderno ha "prova con risorse" che rimuove tutto quel codice disordinato nel blocco finally. Raramente è necessario disporre di un blocco finally. Sembra che i designer abbiano copiato il concetto RAII.
Kiwiron,

0

Dipende dai lead tecnici senior della squadra. In alcune aziende (compresa la mia), non esiste un concetto chiamato smart poiner. È considerato elegante. Quindi, la gente semplicemente elimina dappertutto e c'è un disco per riparare la perdita di memoria ogni 2 mesi. La nuova ondata di dichiarazioni di eliminazione arriva ovunque. Quindi, dipende dalla compagnia e dal tipo di persone che ci lavorano.


1
C'è qualcosa nel tuo ambiente che ti impedisce di usare auto_ptre gli amici?
Sean McMillan,

2
sembra che la tua azienda non scriva codice C ++, stai scrivendo C.
gbjbaanb il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.